#ver 0.06 (C)Cyr
use strict;
use warnings;
use diagnostics;
use IO::Socket;
use Convert::ASN1;
#use Data::Dumper;
use Carp;
use POSIX;
my $CSTAapdu;
my $socket;
my %calls;
sub decode_msg_header {
	my $bin = shift;
	my $len = unpack('n', $bin);
	return $len;
}
sub encode_msg_header {
	my $len = shift;
	die "Message larger than allowed!" unless ($len <= 240);
	my $bin = pack('n', $len);
	return $bin;
}
sub convert_to_hex {
	my $pdu = $_[0];
	my $hexdata = unpack('H*', $pdu);
	$hexdata =~ tr/a-z/A-Z/;
	$hexdata =~ s/(..)/$1 /g;
	$hexdata =~ s/ $//g;
	return $hexdata;
}
sub send_pdu {
	my $pdu = $_[0];
	my $header = encode_msg_header(length($pdu));
	$socket->write($header); 
	$socket->write($pdu);
#	my $hexdata = convert_to_hex($pdu);
#	print("SENT:    [$hexdata]\n"); 
} 
sub csta_connect {
	my %args = %{$_[0]}; 
	open_csta_socket($args{host}, $args{port});
	# A-ASSOCIATE Request
	my $pdu = "602380020780A10706052B0C00815ABE14281206072B0C00821D8148A007A0050303000800";
    $pdu = pack('H*', $pdu);
	send_pdu($pdu);
	# A-ASSOCIATE Result 
	$pdu = receive_stuff();
	my $hexdata = convert_to_hex($pdu);
	if ($hexdata =~/A2 03 02 01 01/){
		print "rejected-permanent\n";
		exit 0
	}
	#SystemStatus Request
	$pdu = receive_stuff();
	my $out = $CSTAapdu->decode($pdu);
	my $invokeID  = $out->{svcRequest}->{invokeID};
	SystemStatusResult($invokeID);
}
	
sub open_csta_socket {
	my $host = shift;
	my $port = shift;
	$socket = new IO::Socket::INET(
        PeerAddr => $host,
        PeerPort => $port,
		Blocking => 1,
		Proto => 'tcp') || die "Error creating socket: $!\n";
	$socket->autoflush(1);
	print("opened a connection to $host on port $port\n");
}	
sub receive_stuff {
	my $header = '';
	my $pdu = '';
	my $nbytes = $socket->sysread($header, 2);
	if ($nbytes==1) { # фрагмент пакета
		my $header2;
		my $nbytes2= $socket->sysread($header2, 1);
		$header = $header.$header2;
		$nbytes = 2;
	} 
	croak "Didn't receive the specified amount of data (2 bytes)!\n".chr(7) unless ($nbytes == 2);
	my $len = decode_msg_header($header);
	$nbytes = $socket->sysread($pdu, $len);
	if ($nbytes<$len) { # фрагмент пакета
		my $pdu2;
		my $nbytes2= $socket->sysread($pdu2, $len-$nbytes);
		$pdu = $pdu.$pdu2;
		$nbytes = $nbytes + $nbytes2;
	} 
	croak "Didn't receive the specified amount of data ($len bytes)!\n".chr(7) unless ($nbytes == $len);
#	my $hexdata = convert_to_hex($pdu);
#	print("RECEIVED:[$hexdata]\n");
	return $pdu;
}

sub SystemStatusResult{
	my $invokeID = shift;
	my $pdu = $CSTAapdu->encode({svcResult=>{invokeID=>$invokeID,result=>{serviceID=>211,serviceResult=>{noData=>1}}}});
	send_pdu($pdu); # send SystemStatus Result
}
sub playOGM{
	my $ogmId = shift;
	if (!$ogmId) {$ogmId=10}
	my $connectionID = shift;
	my $pdu = $CSTAapdu->encode({svcRequest=>{
								invokeID=>11,
								serviceID=>51,
								serviceArgs=>{	
									privateData=>{
										private =>{
											kmeResourceControl=>{
												ogmStart=>{
													connection=>$connectionID,
													ogmId=>$ogmId
														}
																}
												}
												}
											}
											}
								});
 	send_pdu($pdu);
}
sub numberOfMsgPort{
#EscapeArgument.privateData.private.kmeSystemData.getSystemData.request
my $pdu = $CSTAapdu->encode({svcRequest=>{
                                invokeID=>8,
								serviceID=>51,
                                serviceArgs=>{
									privateData=>{
										private 	=>{
											kmeSystemData=>{
												getSystemData=>{
													request 	=>{
														systemData=>18
																  }
																}
															}
													   }
												  }
												}
											}
							});
send_pdu($pdu);
while (1) {
	$pdu = receive_stuff();
	my $out = $CSTAapdu->decode($pdu);
	if (defined $out->{svcRequest}->{serviceArgs}->{privateData}->{private}->{kmeSystemData}->{systemDataLinkedReply}->{sysData}->{numberOfMsgPort}) {
		my $MsgPorts = $out->{svcRequest}->{serviceArgs}->{privateData}->{private}->{kmeSystemData}->{systemDataLinkedReply}->{sysData}->{numberOfMsgPort};
		return $MsgPorts
		}
	}
}
sub	answer{
	my $callerID     = shift;
	my $connectionID = shift;
	my $MsgPorts = numberOfMsgPort();
	#берём трубку
	my $pdu = $CSTAapdu->encode({svcRequest=>{
									invokeID=>10,
									serviceID=>2,
									serviceArgs=>{
										callToBeAnswered=>$connectionID
												}
											}
								});	
	send_pdu($pdu);
	my $callID = $connectionID->{both}->{callID};
	print "answered callID:",$callID," callerID:",$callerID, "\n";
	my $numberOfFreePort = $MsgPorts->{numberOfFreePort};
	print "numberOfFreePort:",$numberOfFreePort,"\n"; 
	if (!$numberOfFreePort) {
		my $pdu = $CSTAapdu->encode({svcRequest=>{
	    		                    	invokeID=>13,
	    		                    	serviceID=>5,
	    		                    	serviceArgs=>{
	    		                    		connectionToBeCleared=>$connectionID
													}
												}
	    		                    });
	    send_pdu($pdu);
		print "connection callID:",$callID," cleared\n";	
		next
	} 
	my $digits = [split('',$callerID)];
	$calls{$callID} = $digits;
	print "digits in the array:", scalar @{$calls{$callID}}, "\n";
	my $digit = shift @{$calls{$callID}};
	playOGM($digit, $connectionID)	
}			
# parse ASN.1 desciptions
my $asn = Convert::ASN1->new;
$asn->prepare_file('d:\111\asn_perl\kxtde.asn') or die "prepare: ", $asn->error;
my %serviceArgs = ( 2 => 'AnswerCallArgument',
					5 => 'ClearConnectionArgument',
					21=> 'CSTAEventReportArgument',
					51=> 'EscapeArgument',
#					10=> 'MakeCallArgument',
					71=> 'MonitorStartArgument',
					211=> 'SystemStatusArg');
foreach (keys %serviceArgs) {
	$asn->registertype('serviceArgs',$_,$asn->find($serviceArgs{$_}));
}
my %serviceResults = (  51=> 'EscapeResult',
#						10=> 'MakeCallResult',
						71=> 'MonitorStartResult',
						211=> 'SystemStatusRes');
 foreach (keys %serviceResults) {
	$asn->registertype('serviceResult',$_,$asn->find($serviceResults{$_}));
} 
$CSTAapdu = $asn->find('CSTAapdu');
csta_connect({'host'=>'192.168.0.101', 'port'=>33333});
my $Number=177; # ! ICD номер
my $MsgPorts = numberOfMsgPort();
print "numberOfMsgPort:", $MsgPorts->{numberOfMsgPort}, "\n"; 
#EscapeArgument.privateData.private.kmeSystemData.setSystemData
my $pdu = $CSTAapdu->encode({svcRequest=>{
                                invokeID=>9,
								serviceID=>51,
                                serviceArgs=>{
									privateData=>{
										private =>{
											kmeSystemData=>{
												setSystemData=>{
													acdQueue =>{
														device =>{
															deviceIdentifier=>{
																dialingNumber=>$Number
																			  }
																  },
															attribute=>{
																acdMode =>1
																		}
																}
																}
															}
													}		
												  }
											 }
										  }
                            });
send_pdu($pdu);
#EscapeArgument.privateData.private.kmeSystemData.setSystemData
$pdu = $CSTAapdu->encode({svcRequest=>{
                                invokeID=>9,
								serviceID=>51,
                                serviceArgs=>{
									privateData=>{
										private =>{
											kmeSystemData=>{
												setSystemData=>{
													acdQueue =>{
														device =>{
															deviceIdentifier=>{
																dialingNumber=>$Number
																			  }
																  },
															attribute=>{
																ctiWaitTime =>20
																		}
																}
																}
															}
													}		
												  }
											 }
										  }
                        });
send_pdu($pdu);
#monitor
$pdu = $CSTAapdu->encode({svcRequest=>{
                                invokeID=>9,
								serviceID=>71,
                                serviceArgs=>{
										monitorObject=>{
											deviceObject=>{
												deviceIdentifier=>{
													dialingNumber=>$Number
																}
															}
														},
										requestedMonitorFilter=>{}
											}
										}
                        });
send_pdu($pdu);
while (1) {
	$pdu = receive_stuff();
	#Convert::ASN1::asn_dump($pdu);
	my $out = $CSTAapdu->decode($pdu);
	# $Data::Dumper::Indent=1;
	# $Data::Dumper::Pair=' ';
	# $Data::Dumper::Quotekeys=0;
	# $Data::Dumper::Varname='CSTAapdu';
	#print Dumper($out);
	if  (defined  $out->{svcRequest})   {
			my $serviceID = $out->{svcRequest}->{serviceID};
			my $invokeID  = $out->{svcRequest}->{invokeID};
			my $serviceArgs=$out->{svcRequest}->{serviceArgs};
			if ($serviceID==21) {
				if (defined $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}) {
					my $connectionID = $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}->{connection};
					if (defined $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}->{callingDevice}->{deviceIdentifier}->{deviceIdentifier}->{dialingNumber}) {
						my $callerID = $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}->{callingDevice}->{deviceIdentifier}->{deviceIdentifier}->{dialingNumber};
						answer($callerID, $connectionID);
					} elsif ($serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}->{extensions}->{privateData}[0]->{private}->{kmeAdditionalData}->{device}->{deviceIdentifier}->{other}) {
						my $callerID = $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{delivered}->{extensions}->{privateData}[0]->{private}->{kmeAdditionalData}->{device}->{deviceIdentifier}->{other};
						substr($callerID, 0, 1) = ""; 
						answer($callerID, $connectionID);
					}
					else {print "not number\n"}
				} elsif ($serviceArgs->{eventSpecificInfo}->{callControlEvents}->{transferred}) {
					my $callID  = $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{transferred}->{transferredConnections}[0]->{newConnection}->{both}->{callID};
					my $oldCallID = $serviceArgs->{eventSpecificInfo}->{callControlEvents}->{transferred}->{primaryOldCall}->{both}->{callID};
					$calls{$callID} = $calls{$oldCallID};
					delete $calls{$oldCallID};
					print "transferred: new callID:",$callID,"\n";
				} elsif (defined $serviceArgs->{eventSpecificInfo}->{vendorSpecEvents}->{privateEvent}->{privateData}->{private}->{kmePrivateEvent}->{ogmStatus}) {
					my $ogmStatus=$serviceArgs->{eventSpecificInfo}->{vendorSpecEvents}->{privateEvent}->{privateData}->{private}->{kmePrivateEvent}->{ogmStatus};					
					print "ogmPortNumber:",$ogmStatus->{ogmPortNumber}->{deviceIdentifier}->{deviceNumber} & 0xFFFF;
					print " ogmId:",$ogmStatus->{ogmId};
					if ($ogmStatus->{state}) {
						print " state:stop\n";
						my $connectionID = $ogmStatus->{connection};
						my $callID = $connectionID->{both}->{callID};
						if  (scalar @{$calls{$callID}}) {	
							print "digits in the array:", scalar @{$calls{$callID}}, "\n";
							my $digit = shift @{$calls{$callID}};
							playOGM($digit, $connectionID)
						} else {
							delete $calls{$callID};
							# кладём трубку
			                my $pdu = $CSTAapdu->encode({svcRequest=>{
			                		                    	invokeID=>13,
			                		                    	serviceID=>5,
			                		                    	serviceArgs=>{
			                		                    		connectionToBeCleared=>$connectionID
																		}
																	}
			                		                    });
			                send_pdu($pdu);
							print "connection callID:",$callID," cleared\n"
						}
					}
					else {print " state:start\n"}
				}
			} elsif ($serviceID==211) { # if SystemStatus Request
				SystemStatusResult($invokeID);
			}
	
	}
}