# $Id: Conference.pm,v 1.20 2005/04/01 19:22:57 swick Exp $

package Conference;
use strict;
use Carp;

my %Conferences;
my %ConfListeners;
my %MATConference;
my %PartyListeners;
my %PartyAttributeListeners;

sub new
{
    my ($proto, $bridge, $id, $name) = @_;
    my $class = ref($proto) || $proto;

    print( "Conference $id ($name) added\n" );

    my ($resv, $list) = Schedule::Search($name);

    my $self  = {
	'_bridge'	=> $bridge,
	'_id'		=> $id,
	'_maxParties'	=> undef, # from ActiveConferenceView
	'_name'		=> $name,
	'_parties'	=> undef,
	'_schedID'	=> undef, # from ActiveConferenceView
	'_features'	=> undef, # from ActiveConferenceView
	'_reservation'	=> $resv,
	'_passcode'	=> undef, # from schedule or from first caller
    };

    bless ($self, $class);

    $Conferences{$bridge.'.'.$id} = $self;
    Conference->notifyConfListeners($self, 'start');

    enumerate();

    return $self;
}

sub DESTROY
{
    my $self = shift;
    print( "Conference $self->{_id} ($self->{_name}) destroyed.\n" );
}

sub delete
{
    my ($class, $bridge, $id) = @_;
    $id = $bridge.'.'.$id;
    my $conf = $Conferences{$id};
    my $confName = $conf->name();

    Conference->notifyConfListeners($conf, 'end');

    $conf->deleteallparties();

    delete $Conferences{$id};
    print "Conference $confName deactivated\n";

    enumerate();
}

sub rename
{
    my ($class, $bridge, $id, $name) = @_;
    $id = $bridge.'.'.$id;
    my $conf = $Conferences{$id};
    if ($name ne $conf->name()) {
	my $oldName = $conf->name();
	$conf->name($name);
	print "Conference $oldName renamed to $name\n";
    }
}

sub maxParties
{
    my $self = shift;
#    if (@_) { $self->{_maxParties} = shift }
    return $self->{_maxParties};
}

sub name
{
    my $self = shift;
    if (@_) { $self->{_name} = shift }
    return $self->{_name};
}

sub schedID
{
    my $self = shift;
#    if (@_) { $self->{_name} = shift }
    return $self->{_schedID};
}

sub endTime
{
    my $self = shift;
    return $self->reservation
	? $self->reservation->start + $self->reservation->duration*60
	: undef;
}

# features is ABCDEF:
#	A: Roll Call? (not for Unattended)
#	B: Taped? (not for Unattended)
#	C: Voting
#	D: Q&A
#	E: AdHoc Q&A?
#	F: SubConf

sub features
{
    my $self = shift;
    return $self->{_features};
}

sub reservation
{
    my $self = shift;
    unless ($self->{_reservation}) {
	my ($resv, $list) = Schedule::Search($self->name);
	$self->{_reservation} = $resv;
    }
    return $self->{_reservation};
}

sub passcode
{
    my $self = shift;

    if (@_) {
	$self->{_passcode} = shift;
    }
    else {
	unless ($self->{_passcode}) {
	    if ($self->reservation) {
		$self->{_passcode} = $self->reservation->code;
	    }
	}
    }

    return $self->{_passcode};
}

sub find
{
    my ($class, $bridge, $confID) = @_;
    return $Conferences{$bridge.'.'.$confID};
}

sub addConfListener
# Registers a listener function to invoke when a conference is
# is activated or deactivated
{
    my ($self, $proc, $obj) = @_;
    $ConfListeners{$proc} = [ $proc, $obj ];

#    for my $conf (values %Conferences) {
#	if (defined $conf) {	# invoke listener on currently active confs
#	    $proc->($obj ? ($obj, $conf, 'start')
#			 : ($conf, 'start'));
#	}
#    }
}

sub addPartyListener
# Registers a listener function to invoke when an active party
# is added or dropped
{
    my ($self, $proc, $obj) = @_;
    $PartyListeners{$proc} = [ $proc, $obj ];
}

sub addAttributeListener
# Registers a listener function to invoke when an active party
# changes name or mute status
{
    my ($self, $proc, $obj) = @_;
    $PartyAttributeListeners{$proc} = [ $proc, $obj ];
}

sub addParty
# External
# Adds a new party to a conference
{
    my ($self, $party) = @_;
    my $id = $party->id();
    $self->{_parties}->{$id} = $party;
}

sub dropParty
# External
# Forgets a Party object
{
    my ($self, $party) = @_;
    my $id = $party->id();
    delete $self->{_parties}->{$id};
}

sub notify
# invokes any listener functions after a change in party state
{
    my ($self, $party) = @_;
    if (%PartyListeners) {
	my $direction = $party->isactive() ? "enter" : "exit";
	for my $f (values %PartyListeners) {
	    $f->[0]->($f->[1] ? ($f->[1], $self, $party, $direction)
		              : ($self, $party, $direction));
	}
    }
}

sub notifyPartyAttributes
# invokes any listener functions after a change in party name or mute
{
    my ($self, $party) = @_;
    if (%PartyAttributeListeners) {
	for my $f (values %PartyAttributeListeners) {
	    $f->[0]->($f->[1] ? ($f->[1], $self, $party)
		              : ($self, $party));
	}
    }
}

sub notifyConfListeners
# invokes any listener functions after a change in party state
{
    my ($class, $conf, $direction) = @_;
    if (%ConfListeners) {
	for my $f (values %ConfListeners) {
	    $f->[0]->($f->[1] ? ($f->[1], $conf, $direction)
		              : ($conf, $direction));
	}
    }
}

sub deleteallparties
{
    my $self = shift;

    if (defined $self->{_parties}) {
	for my $party (values %{$self->{_parties}}) {
	    Party->delete( $self->{_bridge}, $party->id );
	}
    }
}

sub enumerate
{
    my ($self, $proc, @args) = @_;
    
    if (ref($self)) {		# called from an instance
	if ($proc && defined $self->{_parties}) {
	    my %parties = %{$self->{_parties}};
	    for my $party (values %parties) {
		$proc->($party, @args);
	    }
	}
	return;
    }

    for my $conf (values %Conferences) {
	if (defined $conf) {
	    my $confName = $conf->name();
	    print "Conference $confName is active";
	    if (defined $conf->{_parties}) {
		print " and holds";
		my %parties = %{$conf->{_parties}};
		my $sep = '';

		for my $party (values %parties) {
		    my $n = $party->name();
		    print "$sep $n";
		    $sep = ',';
		}
	    }
	    else {
		print " and is empty";
	    }
	    print "\n";
	    print "List: ", $conf->present(), "\n";
	}
    }
}

sub present
{
    my $self = shift;
    my $partylist = "";

    if (defined $self->{_parties}) {
	$partylist = "";
	my %parties = %{$self->{_parties}};
	my $sep = '';

	for my $id (sort keys %parties) {
	    my $party = $parties{$id};
	    if ($party->isactive()) {
		my $name = $party->name();
		$partylist .= "$sep$name";
		$sep = ', ';
		$partylist .= " (muted)" if $party->ismuted();
	    }
	}
    }
    return $partylist;
}

sub bridge
{
    my $self = shift;
    return $self->{_bridge};
}

sub id
{
    my $self = shift;
    return $self->{_id};
}

sub request
{
    my ($self, @rest) = @_;
    print "Control: ", @rest, "\n";
    if ($rest[0] =~ /^ACC/) {
	if (defined $self->{controlSession}) {
	    $self->{controlSession}->request(@rest);
	}
	else {
	    carp "No ControlSession";
	}
    }
    elsif ($rest[0] =~ /^ACV/) {
	if (defined $self->{viewSession}) {
	    $self->{viewSession}->request(@rest);
	}
	else {
	    carp "No ViewSession";
	}
    }
    else {
	carp "Not an ACC request: @rest";
    }
}

sub requestWithOperator
{
    my ($self, $request) = @_;
    print "Control: ", $request, "\n";
    if (!defined $self->{controlSession}) {
	print "RequestWithOperator() called without a control session\n";
	return;
    }
    my $opPhone = "6177616200";
    $self->{controlSession}->requestWithReply("ACC.P.CREATE~$self->{_id}~Bot~$opPhone~0~~~~", \&_WithOperator, ($self, $request));
}

sub _WithOperator
{
    my ($self, $request, $response) = @_;

    # typical response to ACC.P.CREATE is c1~1~ACC.P.CREATE~c2
    my ($session, $sequence, $msg, $operatorID) = split(/~/, $response);

    if ($msg =~ m/^-ACC.P.CREATE/) {
	print "Create operator error: $response\n";
	$operatorID = undef;
	for my $party (values %{$self->{_parties}}) {
	    if ($party->name() eq 'Bot') {
		$operatorID = $party->id();
		last;
	    }
	}
	return if (not defined $operatorID);
    }

    print "Operator id is $operatorID\n";
    $self->request("ACC.O.AA~$operatorID"); # associate operator audio
    sleep 1;			# @@ why is this necessary?
    $self->request($request);
#    Seems to work more reliably without the TA request;
#    else operator connection often is left in 'hold' state.
#    $self->request("ACC.O.TA");	# terminate operator audio
    # Delete the operator party, with forceddisconnect
    $self->request("ACC.P.DESTROY~$operatorID~1");
}

sub activate
{
    my ($self, $passcode) = @_;

    if (defined $self->{_id}) {
	carp "Conference.activate: conference ".$self->{_name}.'('.$self->{_id}.") is already active";
    }

    if (!defined $self->{controlSession}) {
	print "Activate() called without a control session\n";
	return;
    }

    # SSNID~SEQ~ACC.C.ACTIVATE~BRIDGEID~PASSCODE~STARTIMMEDIATELY
    $self->{controlSession}->requestWithReply("ACC.C.ACTIVATE~".$self->{_bridge}."~$passcode~1",
			    \&_GetConfID, ($self));
}

sub _Activate
{
    my ($self, $passcode) = @_;
    $self->activate($passcode);	# should now have a Conference Control session.
}

sub _GetConfID
{
    my ($self, $response) = @_;

    # typical response to ACC.C.ACTIVATE is ssnid~seq~ACC.C.ACTIVATE~confid
    my ($session, $sequence, $msg, $confID) = split(/~/, $response);

    if ($msg =~ m/^-ACC.C.ACTIVATE/) {
	print "Activate passcode conference error: $response\n";
	return;
    }

    $self->{_id} = $confID;
}

sub dtmf
{
    my ($self, $value) = @_;

    if (defined $self->{viewSession}) {
	# ACV.SA.ALTER~FIELDNAME~VALUE
	$self->{viewSession}->request("ACV.SA.ALTER~EnablePartyDTMF~$value");
    }
    else {
	carp "No ViewSession for Conference->dtmf";
    }
}

sub MATActivateHandler($)
{
    my ($conn, $msg) = @_;

    # z3~confID~name~a~b~c~d~e~f~g~h~i~j~k~l~m~n
    my ($msgType, $conf, $name, $schedID) = split('~', $msg);

    return if $msgType ne 'z3';

    $MATConference{$conf}->{name} = $name;
    $MATConference{$conf}->{schedID} = $schedID;
}

sub MATDeactivateHandler($)
{
    my ($conn, $msg) = @_;

    # z2~confID
    my ($msgType, $conf) = split('~', $msg);

    return if $msgType ne 'z2';

    delete $MATConference{$conf};
}

sub MATconfID($)
{
    my ($self) = @_;

    my $name = $self->name;
    my $schedID = $self->schedID;

    foreach my $id (keys %MATConference) {
	return $id if ($MATConference{$id}->{name} eq $name &&
		       $MATConference{$id}->{schedID} eq $schedID);
    }
    return undef;
}

sub MATrequest
{
    my ($self, $request) = @_;
    if (defined $self->{controlSession}) {
	return $self->{controlSession}->MATrequest($request);
    }
    elsif (defined $self->{viewSession}) {
	return $self->{viewSession}->MATrequest($request);
    }
    else {
	carp "No MAT session for request: $request";
	return undef;
    }
}

1;
