/*---------------------------------------------------------------------------*\
	 priplayrecord.cpp
	 
	 An OpenPRI card span places/receives an ISDN call to/from a 
	 primary rate (E1/T1) Line or another OpenPRI span. 

	 In the case when the card is placing a call, once connected it then 
	 plays audio to the called party and subsequently hangs up.
	 In the case when the card answers a call, it then commences recording 
	 until hangup.
	 
 	 This will work with an OpenPRI span connected to an E1/T1 ISDN line or 
	 to another OpenPRI span via a cross over cable. In the later, ensure 
	 one of the OpenPRI spans is configured for network mode and the other 
	 for client mode.    
	 
 	 Voicetronix Voice Processing Board (VPB) Software

         Copyright (C) 1999-2009 Voicetronix www.voicetronix.com.au

         This library is free software; you can redistribute it and/or
         modify it under the terms of the GNU Lesser General Public
         License as published by the Free Software Foundation; either
         version 2.1 of the License, or (at your option) any later version.

         This library is distributed in the hope that it will be useful,
         but WITHOUT ANY WARRANTY; without even the implied warranty of
         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
         Lesser General Public License for more details.

         You should have received a copy of the GNU Lesser General Public
         License along with this library; if not, write to the Free Software
         Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
	 USA

\*---------------------------------------------------------------------------*/

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <pthread.h>

#include "vpbreg.h"
#include "vpbapi.h"
#include "verbose.h"
#include "../kbhit.h"

#include "../threads.h"

#define MAX_DIAL_DIGITS	21

// call states
#define CALL_IDLE		0
#define CALL_ANSWERED		1
#define CALL_WAIT_FOR_ANSWER 	2

// run states
#define RUN_STOP 	0
#define RUN_WAIT 	1
#define RUN_GO		2

void *gen_port_thread(void *pv);
void *rec_port_thread(void *pv);

int             threads_active; // the number of active threads.
pthread_mutex_t mutex;          // mutex to protect shared data
int             finito;         // flag to signal the program is finished.
float           gain;           // option software record gain
typedef struct {
	int	handle;
	char	filename[VPB_MAX_STR];
	char	callednumber[MAX_DIAL_DIGITS];
	char	callingnumber[MAX_DIAL_DIGITS];	
	int	callstate;
	int	run;
	int 	callgen;
	int	numberofcalls;
} CHAN_INFO;

static int arg_exists(int argc, char *argv[], const char *arg)
{ //{{{
	for(int i = 0; i < argc; ++i)
		if(strcmp(argv[i],arg) == 0) return i;

	return 0;
} //}}}

int main(int argc, char * argv[])
{
	int		x,j,i;
	int		ChannelNumber=-1;
	char		CalledNumber[MAX_DIAL_DIGITS];
	char		CallerID[MAX_DIAL_DIGITS];
	char		AudioFileName[VPB_MAX_STR];
	int		arg;
	int		NumberOfCalls = -1;
	int 		LoopBack = 0;
	CHAN_INFO	*cinfo;	

//	verbose(1);
	pthread_mutex_init(&mutex,NULL);

	if(arg_exists(argc, argv, "-?") || arg_exists(argc, argv, "--help")){
		printf("usage: %s [options]\n\n",argv[0]); 
		printf("--out <ChannelNumber> <CalledNumber> <CallerID> <AudioFileName>		; Arguments required for a single outbound call.\n");
		printf("--lpbk <AudioFileName> <NumberOfCalls>					; Arguments required in loopback mode. <NumberOfCalls> is optional.\n"); 
		printf("									; By default, it generates calls until keyboard is hit.\n");
		printf("									; Loopback mode requires at least 2 PRI spans in the PC/server to operate.\n");
		printf("									; Inbound calls on any channel are handled by default.\n");
		exit(EXIT_SUCCESS);
	}
	
	// count the cards and ports
	int num_cards = vpb_get_num_cards();
	int num_ch = 0;

	int ports_per_card[num_cards];

	for (int i = 0; i < num_cards; ++i)
		num_ch += (ports_per_card[i] = vpb_get_ports_per_card(i));

	printf("Found [%d] channels...\n",num_ch);
	pthread_t	allthread[num_ch];
		
	cinfo = new CHAN_INFO[num_ch];
	
	if((arg = arg_exists(argc, argv, "--out"))!=0) {
		if(argv[arg+1]==NULL || argv[arg+2]==NULL || argv[arg+3]==NULL || argv[arg+4]==NULL){
			printf("Error in passing command line arguments for outbound calls. Type --help.\n");
			exit(EXIT_FAILURE);
		}
		else{	
			ChannelNumber = atoi(argv[arg+1]);
			strcpy(CalledNumber, argv[arg+2]);
			strcpy(CallerID, argv[arg+3]);
			strcpy(AudioFileName, argv[arg+4]);
		}
	}
	else if((arg = arg_exists(argc,argv, "--lpbk"))!=0){
		if(argv[arg+1]==NULL){
			printf("Error in passing command line arguments for outbound calls. Type --help.\n");
			exit(EXIT_FAILURE);
		}
		else{
			strcpy(AudioFileName, argv[arg+1]);
			if (argv[arg+2]!=NULL)
				NumberOfCalls = atoi(argv[arg+2]);
			LoopBack = 1;
		}	
	}

	
	pthread_mutex_init(&mutex, NULL);

	// now init each port and start one thread per port
	x=0;
	for(i=0; i<num_cards; i++) {
		for(j=0;j<ports_per_card[i];j++){
			cinfo[x].handle = vpb_open(i,j);
			cinfo[x].callstate = CALL_IDLE;
			cinfo[x].run=RUN_WAIT;
			if(!LoopBack){
				if(x==ChannelNumber){
					cinfo[x].numberofcalls = 1;
					strcpy(cinfo[x].callingnumber,CallerID);
					strcpy(cinfo[x].callednumber,CalledNumber); 
					strcpy(cinfo[x].filename,AudioFileName);	
					pthread_create(&allthread[x], NULL, gen_port_thread, (void*)&cinfo[x]);
				}
				else {
			 		pthread_create(&allthread[x], NULL, rec_port_thread, (void*)&cinfo[x]);

				}	
			}
			else{
				if(x<num_ch/2){
					cinfo[x].callgen = 1;
					cinfo[x].numberofcalls=NumberOfCalls;
					strcpy(cinfo[x].callingnumber,"0882329112");
					strcpy(cinfo[x].callednumber,"0292314800"); 
					strcpy(cinfo[x].filename,AudioFileName);	
					pthread_create(&allthread[x], NULL, gen_port_thread, (void*)&cinfo[x]);
				}
				else{
			 		pthread_create(&allthread[x], NULL, rec_port_thread, (void*)&cinfo[x]);
				}
			}
			
			x++;
		}
	}
	printf("%d ports opened!\n", num_ch);

	if(LoopBack){
		for(i=num_ch;i>=num_ch/2;i--)
			cinfo[i].run=RUN_GO;
		for(i=0;i<num_ch/2;i++)
			cinfo[i].run=RUN_GO;
	}
	else{
		if(ChannelNumber == -1){
			for(i=0;i<num_ch;i++)
				cinfo[i].run=RUN_GO;
		}
		else{
			cinfo[ChannelNumber].run=RUN_GO;
		}
	}
	// do nothing in main loop until return pressed
	while(!kbhit()){
		vpb_sleep(50);
	}

	for(i=0;i<num_ch;i++)
		cinfo[i].run=RUN_STOP;

	printf("shutting down....\n");
	while(threads_active) {
		vpb_sleep(500);
		printf("threads active: %d\r", threads_active);
	}

	for(i=0; i<num_ch; i++) {
		vpb_close(cinfo[i].handle);
	}

	return 0;
}

void *gen_port_thread(void *pv)
{
	CHAN_INFO 	*cinfo = (CHAN_INFO *)pv;
	int		ret;
	VPB_EVENT 	e;
	char      	s[VPB_MAX_STR];
	int		newstate;


	pthread_mutex_lock(&mutex);
	threads_active++;
	pthread_mutex_unlock(&mutex);
	while(cinfo->run == RUN_WAIT){
		vpb_get_event_ch_sync(cinfo->handle,&e,60);
		vpb_sleep(50);
	}
	while(cinfo->run != RUN_STOP) {
		ret = vpb_get_event_ch_sync(cinfo->handle, &e,500);
		if (ret == VPB_OK){
			vpb_translate_event(&e, s);
			printf("Event type[%d] chan %s",e.type,s);
			newstate=cinfo->callstate;
			switch(cinfo->callstate){
				case CALL_WAIT_FOR_ANSWER:
					switch(e.type){
						case VPB_ISDN_ANS:
							printf("chan[%d]: Outbound call has been answered, playing file %s!\n",cinfo->handle,cinfo->filename);
							vpb_play_file_async(cinfo->handle,cinfo->filename,69);
							newstate = CALL_ANSWERED;
							break;
						case VPB_DROP:
							printf("chan[%d]: Outbound call hungup before being answered!\n",cinfo->handle);
							vpb_sethook_async(cinfo->handle, VPB_ONHOOK);
							newstate = CALL_IDLE;
							break;
						default:
							printf("chan[%d]: Event %d is not handled!\n",cinfo->handle, e.type);
							
					}
					break;
				case CALL_ANSWERED:
					switch(e.type){
						case VPB_DROP:
							printf("chan[%d]: Call hung up in CALL_ANSWERED state!\n",cinfo->handle);
							vpb_sethook_async(cinfo->handle, VPB_ONHOOK);
							newstate = CALL_IDLE;
							break;
						
						case VPB_DTMF:
							printf("chan[%d]: Detected DTMF [%d]/n!",cinfo->handle,e.data);
							break;
						case VPB_PLAYEND:
							printf("chan[%d]: Finished playing file %s, hanging up!\n",cinfo->handle,cinfo->filename);
							vpb_sethook_async(cinfo->handle, VPB_ONHOOK);
							newstate=CALL_IDLE;
							break;
						
						default:
							printf("chan[%d]: Event %d is not handled!\n",cinfo->handle, e.type);
						
					}
					break;
			}
			cinfo->callstate=newstate;	
		}
		else if(cinfo->callstate == CALL_IDLE){
			vpb_sleep(100*cinfo->handle);
			if(cinfo->callgen){
				if(cinfo->numberofcalls==-1){
					printf("chan[%d]: Making Call from [%s] to [%s]!\n",cinfo->handle,cinfo->callingnumber,cinfo->callednumber); 
					vpb_isdn_call(cinfo->handle,cinfo->callednumber,cinfo->callingnumber,0,0);
					cinfo->callstate=CALL_WAIT_FOR_ANSWER;
				}
				if(cinfo->numberofcalls>0){
					printf("chan[%d]: Making Call from [%s] to [%s]!\n",cinfo->handle,cinfo->callingnumber,cinfo->callednumber); 
					vpb_isdn_call(cinfo->handle,cinfo->callednumber,cinfo->callingnumber,0,0);
					cinfo->numberofcalls--;
					cinfo->callstate=CALL_WAIT_FOR_ANSWER;
				}
			}
			else{	
				if(cinfo->numberofcalls>0){
					printf("chan[%d]: Making Call from [%s] to [%s]!\n",cinfo->handle,cinfo->callingnumber,cinfo->callednumber); 
					vpb_isdn_call(cinfo->handle,cinfo->callednumber,cinfo->callingnumber,0,0);
					cinfo->callstate=CALL_WAIT_FOR_ANSWER;
					cinfo->numberofcalls--;
				}
			}
		} 
	}
	printf("chan[%d]: Call generator stopping!\n",cinfo->handle);
	if(cinfo->callstate != CALL_IDLE){
		vpb_sethook_async(cinfo->handle,VPB_ONHOOK);
		cinfo->callstate = CALL_IDLE;
	}
	pthread_mutex_lock(&mutex);
	threads_active--;
	pthread_mutex_unlock(&mutex);

	return NULL;
}

void *rec_port_thread(void *pv)
{
	CHAN_INFO 	*cinfo = (CHAN_INFO *)pv;
	char    	file_name[VPB_MAX_STR];
	int		ret;
	VPB_EVENT 	e;
	char      	s[VPB_MAX_STR];
	int		newstate;

	pthread_mutex_lock(&mutex);
	threads_active++;
	pthread_mutex_unlock(&mutex);
	sprintf(file_name,"linear%02d.wav",cinfo->handle);
	while(cinfo->run == RUN_WAIT){
		vpb_get_event_ch_sync(cinfo->handle,&e,60);
		vpb_sleep(50);
	}
	while(cinfo->run != RUN_STOP) {
		ret = vpb_get_event_ch_sync(cinfo->handle, &e,500);
		if (ret == VPB_OK){
			vpb_translate_event(&e, s);
			printf("Event type[%d] chan %s",e.type,s);
			newstate=cinfo->callstate;
			switch(cinfo->callstate){
				case CALL_IDLE:
					if(e.type==VPB_RING){
						vpb_isdn_proceeding(cinfo->handle);
						vpb_sethook_async(cinfo->handle,VPB_OFFHOOK);
						printf("chan[%d]: Answered call!\n",cinfo->handle);
						vpb_record_file_async(cinfo->handle,file_name,VPB_LINEAR);
						printf("chan[%d]: Start recording!\n",cinfo->handle);
						newstate=CALL_ANSWERED;	 
					}
					else if (e.type==VPB_DROP){
						printf("chan[%d]: Caller hung up before call answered!\n",cinfo->handle);
						vpb_sethook_async(cinfo->handle, VPB_ONHOOK);
						newstate=CALL_IDLE;
					}	
					else{
						printf("chan[%d]: Event [%d] has no impact in the IDLE state!\n",cinfo->handle,e.type);
					}
					break;
				case CALL_ANSWERED:
					if(e.type==VPB_DROP){
						vpb_record_terminate(cinfo->handle);
						printf("chan[%d]: Stop recording!\n",cinfo->handle);
						vpb_sethook_async(cinfo->handle, VPB_ONHOOK);
						newstate=CALL_IDLE;
					}
					else if(e.type==VPB_DTMF_DOWN){
						printf("chan[%d]: DTMF %d detected!\n",cinfo->handle,e.data); 
					}
					else{	
						printf("chan[%d]: Event [%d] has no impact in the CALL_ANSWER state!\n",cinfo->handle,e.type);
					}
					break;
			}
			cinfo->callstate=newstate;	
			
		}
	}
	printf("chan[%d]: Call receiver stopping!\n",cinfo->handle);
	if(cinfo->callstate != CALL_IDLE){
		vpb_record_terminate(cinfo->handle);
		vpb_sethook_async(cinfo->handle,VPB_ONHOOK);
		cinfo->callstate = CALL_IDLE;
	}
	pthread_mutex_lock(&mutex);
	threads_active--;
	pthread_mutex_unlock(&mutex);

	return NULL;
}

