# $Id: Party.pm,v 1.30 2005/04/01 17:18:48 swick Exp $

package Party;
use strict;
use Carp;

my %Parties;
my %Ports;			# {p} -> {partyName} {phone} {conf}
				#	 {inConf} {status} {muted} {party}

sub new
{
    my ($proto, $bridge, $conf, $id, $partyName, $phone, $ani) = @_;
    my $class = ref($proto) || $proto;

    print "Party $id ($partyName) added; phone '$phone', ani '$ani'\n";

    my $self  = {
	'_ani'		=> $ani,
	'_bridge'	=> $bridge,
	'_conf'		=> $conf,
	'_id'		=> $id,
	'_location'	=> "(unknown)",
	'_name'		=> $partyName,
	'_phone'	=> $phone,
	'_port'		=> undef,
	'_state'	=> "(unknown)",
	'_startTime'	=> time,
    };

    bless ($self, $class);

    $conf->addParty($self);

    $Parties{$bridge.'.'.$id} = $self;

    return $self;
}

sub DESTROY
{
    my $self = shift;
    print( "Party $self->{_id} ($self->{_name}) destroyed.\n" );
    # can't safely invoke Conference methods during global destroy
    if (my $port = $self->{_port}) {
	my $status = $Ports{$port}->{status}; # forget all but status
	delete $Ports{$port};
	$Ports{$port}->{status} = $status;
    }
}

sub find
{
    my ($class, $bridge, $id) = @_;
    my $partyID = $bridge.'.'.$id;
    return $Parties{$partyID};
}

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

sub delete
{
    my ($class, $bridge, $id) = @_;
    $id = $bridge.'.'.$id;

    my $party = $Parties{$id};

    unless ($party) {
	carp "Attempt to delete unknown party $id";
	return;
    }

    my $partyName = $party->name();

    $party->conf->dropParty($party);
    delete $party->{_conf};

    delete $Parties{$id};

    print "Party $partyName deleted\n";
}

sub setName
{
    my ($class, $bridge, $id, $name) = @_;
    $id = $bridge.'.'.$id;
    my $party = $Parties{$id};
    if ($name ne $party->name()) {
	my $oldName = $party->name();
	$party->name($name);
	print "Party $oldName renamed to $name\n";
	Conference->enumerate();
    }
}

sub name
# Sets or returns the name
{
    my ($self, $name) = @_;
    my $was = $self->{_name};
    if ((defined $name) && ($name ne $was)) {
	$self->{_name} = $name;
	my $id = $self->id();
	# ACC.P.A.ALTER~PARTYID~FIELDNAME~VALUE
	$self->conf->request( "ACC.P.A.ALTER~$id~PartyName~$name" );
    }
    return $was;
}

sub location
{
    my $self = shift;
    if (@_) {
	$self->{_location} = shift;
	my $where;
	for ($self->{_location}) {
	    /^1$/	&& do { $where = "present"; last; };
	    /^0$/	&& do { $where = "disconnected"; last; };
	    /1026/	&& do { $where = "on hold"; last; };
	    /1024/	&& do { $where = "in conf hold"; last; };
	    /256/	&& do { $where = "in sub1"; last; };
	    $where = "in location $_";
	}
	$self->{_location} = $where;
    }
    return $self->{_location};
}

sub where
{
    my $self = shift;
    my $where = $self->location();
    my $state = $self->state();
    my $port = $self->port();
    print "Party $self->{_name} '$self->{_phone}'"
	.($port ? " (port $port)" : '')." is $where and $state\n";
    Conference->enumerate();
}

sub state
{
    my $self = shift;
    if (@_) {
	$self->{_state} = shift;
	my $state;
	for ($self->{_state}) {
	    /0/	&& do { $state = "idle"; last; };
	    /1/	&& do { $state = "disconnecting"; last; };
	    /2/	&& do { $state = "arriving"; last; };
	    /3/	&& do { $state = "on hold"; last; };
	    /4/	&& do { $state = "muted"; last; };
	    /5/	&& do { $state = "live"; last; };
	    $state = "in state $_";
	}
	$self->{_state} = $state;
    }
    return $self->{_state};
}

sub startTime
{
    my $self = shift;
    return $self->{_startTime};
}

sub enumerate
{
    for my $party (values %Parties) {
	if (defined $party) {
	    my $partyName = $party->name();
	    print "Party $partyName is active\n";
	}
    }
}

sub isactive
{
    my $self = shift;

    return 1 if ($self->{_location} =~ /(?:present|conf hold)/i);

    # CAS ticket 5862; location is occasionally not tracked correctly
    if (($self->{_location} =~ /disconnected/i) &&
	($self->{_state} =~ /(?:on hold|muted|live)/i)) {
	my $name = $self->{_name};
	# @@ hack, hack -- could examine moderator privileges, but then
	# we might not display someone else who had entered with privs.
	if ($name ne 'Bot') {
	    print "Participant '$name' connected but in unknown location -- presumed active\n";
	    return 1;
	}
    }

    return 0;
}

sub mute
{
    my $self = shift;
    my $wasmuted = $self->ismuted();

    return $wasmuted
	if (defined $self->port) && $self->conf->MATrequest('U1~'.$self->port);

    my $id = $self->id();
    # ACC.P.MODE~MODESTATE~PARTYCOUNT[~PARTYID]
    $self->conf->requestWithOperator( "ACC.P.MODE~0~1~$id" );
    return $wasmuted;
}

sub unmute
{
    my $self = shift;
    my $wasmuted = $self->ismuted();

    return $wasmuted
	if (defined $self->port) && $self->conf->MATrequest('U0~'.$self->port);

    my $id = $self->id();
    # ACC.P.MODE~MODESTATE~PARTYCOUNT[~PARTYID]
    $self->conf->requestWithOperator( "ACC.P.MODE~1~1~$id" );
    return $wasmuted;
}

sub ismuted
{
    my $self = shift;
#    return (($self->{_state} =~ /(?:muted)/i) ||
#	    ($self->{_location} =~ /(?:conf hold)/i)) ? 1 : 0;
    return ($self->{_state} =~ /(?:muted)/i) ? 1 : 0;
}

sub phone
# Sets or returns the phone number
{
    my $self = shift;
    my $was = $self->{_phone};
    if (@_) {
	my $phone = shift;
	if ($phone ne $was) {
	    $self->{_phone} = $phone;
	    my $id = $self->id();
	    # ACC.P.A.ALTER~PARTYID~FIELDNAME~VALUE
	    $self->conf->request( "ACC.P.A.ALTER~$id~PartyPhone~$phone" );
	}
    }
    return $was;
}

sub disconnect
{
    my $self = shift;
    my $id = $self->id();
    # ACC.P.DESTROY~id~forcedisconnect
    $self->conf->request("ACC.P.DESTROY~$id~1");
}

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

    # P~port~partyName~DNIS-ANI~confID~inConf~b
    my ($msgType, $port, $partyName, $phone, $conf, $inConf)
	= split('~', $msg);

    return if ($msgType ne 'P');

    $phone =~ s/ *$//;		# trim trailing spaces

    $Ports{$port}->{partyName} = $partyName;
    $Ports{$port}->{phone} = $phone;
    $Ports{$port}->{conf} = $conf;
    $Ports{$port}->{inConf} = $inConf;
}

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

    # A~port~a~b~c~d
    my ($msgType, $port, $status, $muted, $c, $d) = split('~', $msg);

    return if ($msgType ne 'A');

    if ($status eq '0') {	# disconnected/idle
	if (my $party = $Ports{$port}->{party}) {
	    delete $party->{_port};
	}
	delete $Ports{$port};
	$Ports{$port}->{status} = $status; # forget all but this
	return;
    }

    if (defined $Ports{$port}->{muted} && $muted != $Ports{$port}->{muted}) {
	my $partyName = $Ports{$port}->{partyName};
	$partyName = '(unknown)' unless $partyName;
	$conn->trace($partyName.' '.($muted ? '' : 'un').'muted');
    }
    $Ports{$port}->{status} = $status;
    $Ports{$port}->{muted} = $muted;
}

# returns the conference object to which a party is connected
sub conf($)
{
    my ($self) = @_;
    return $self->{_conf};
}

# returns the port number on which a party is connected
sub port($)
{
    my ($self) = @_;

    return $self->{_port} if defined $self->{_port};

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

    return unless defined $name && defined $phone;

#    print '*** ',__LINE__,' confID=',$self->conf->MATconfID,"\n";
    my $confID = $self->conf->MATconfID || '';

    if ($name =~ m/^\?\?P(\d+)$/ ||
	$name =~ m/^(?:Guest|Host|Party|In Call)\s+P(\d+)/) {

	if (defined (my $p = $1)) {
	    my $p_Phone = $Ports{$p}->{phone} || '';
	    my $p_Conf = $Ports{$p}->{conf} || '';
	    print '*** ',__LINE__, " port $p name extracted from $name\n";
	    print '*** \'', $p_Phone,
		(($p_Phone eq $phone) ? '\' eq \'' : '\' ne \''),
		$phone, "'\n";
	    print '*** \'',$p_Conf,
		(($p_Conf eq $confID) ? '\' eq \'' : '\' ne \''),
		$confID, "'\n";

	    $Ports{$p}->{party} = $self;
	    $self->{_port} = $p;
	    return $p;
	}
    }

    foreach my $p (keys %Ports) { # exhaustively search all ports
	next unless defined $p && $p < 120; # ignore utility ports

	if ($Ports{$p}->{partyName} && # out-of-service ports don't have names
	    $Ports{$p}->{partyName} eq $name) {
	    print '*** ',__LINE__, " port $p name matches $name\n";
	    print '*** \'',$Ports{$p}->{phone},
		(($Ports{$p}->{phone} eq $phone) ? '\' eq \'' : '\' ne \''),
		$phone, "'\n";
	    print '*** \'',$Ports{$p}->{conf},
		(($Ports{$p}->{conf} eq $confID) ? '\' eq \'' : '\' ne \''),
		$confID, "'\n";
	}

	if ($Ports{$p}->{partyName} && # out-of-service ports don't have names
	    $Ports{$p}->{partyName} eq $name &&
	    $Ports{$p}->{phone} eq $phone &&
	    ($Ports{$p}->{conf} eq $confID || $confID eq ''))
	{
	    print '*** ',__LINE__," port is $p\n";
	    $Ports{$p}->{party} = $self;
	    $self->{_port} = $p;
	    return $p;
	}
#	printf "Port '%s' uninitialized: name='%s', phone='%s', conf='%s'\n",
#		$p,
#		$Ports{$p}->{partyName} || '',
#		$Ports{$p}->{phone} || '',
#		$Ports{$p}->{conf} || ''
#		    unless $Ports{$p}->{partyName} &&
#			   $Ports{$p}->{phone} &&
#			   $Ports{$p}->{conf};
    }
    return undef;
}

# accepts a port number
# returns the Party on that port, or undef
# not really a class method
sub port2party($)
{
    my ($p) = @_;

    return $Ports{$p}->{party} if $Ports{$p}->{party};

    my $name = $Ports{$p}->{partyName};
    my $phone = $Ports{$p}->{phone};
    my $confID = $Ports{$p}->{conf};

    return unless (defined $name) && (defined $phone);

    foreach my $party (keys %Parties) {
	return $Ports{$p}->{party} = $Parties{$party}
	    if ($Parties{$party}->state ne 'idle' &&
		$Parties{$party}->name eq $name &&
		$Parties{$party}->phone eq $phone &&
		$Parties{$party}->conf->MATconfID eq $confID);
    }
    return undef;
}

# Handles port status download (typically at startup)
sub PortStatusHandler($$)
{
    my ($conn, $msg) = @_;

    # B~port~port_type~ID~DNIS-ANI~~state~MONITOR~FID~conference_no~fgs.fl.assignable~in_conf_flag
    my ($msgType, $port, $type, $id, $phone, undef, $state, $muted) = split('~', $msg);

    return if ($msgType ne 'B' || $port eq 'E1');

    if ($type eq '13') {	# ISDN
	$Ports{$port}->{status} = $state;
	$Ports{$port}->{muted} = $muted;
    }

    $conn->MATsend('B~'.($port+1)) if $port < 137;
    return;
}

# Return number of ports that are useable (not out of service) and
# number of ports idle
sub PortsAvailable()
{
    my ($count, $idle) = (0, 0);

    foreach my $p (keys %Ports) {
	next unless $p < 120 && defined($Ports{$p}->{status}); # ignore utility ports
	my $status = hex $Ports{$p}->{status};
	next unless defined $status;
	$count++ if $status < hex '40' || $status >= hex 'f0';
	$idle++ if $status == 0;
    }
    return ($count, $idle);
}

1;
