/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY.                         *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.  Contact information: bergmark@cs.cornell.edu     *
 ******************************************************************************/

#include <stdio.h>
#include <process.h>
#include <string.h>
#include <conio.h>
#include <errno.h>
#include <windows.h>
#include <winbase.h>
#include <fcntl.h>

/* Dialogic includes */
#include <srllib.h>
#include <dxxxlib.h>
#include <sctools.h>

#include "cnrg_itx_gtwy_Gateway.h"
#include "cnrg_itx_gtwy_ListenChannel.h"
#include "cnrg_itx_gtwy_PlayChannel.h"
#include "cnrg_itx_gtwy_PlayDestination.h"
#include "cnrg_itx_gtwy_RecordChannel.h"
#include "convert.table"		// table for converting from linear PCM to mu-law format

const int SILENCE = 127;		// user-defined silence value (set according to mu-law format)
const int VCHAN_RECORD	= 0x90;	// user-defined event
const int initialfd = 41;		// user-defined file descriptor
const int pread_timeout = 64;	// how long my_read will wait for a voice packet
const int pwrite_timeout = 200;	// how long a writer thread will wait before my_read plays a packet

int greetingfd;					// file descriptor for the initial greeting message
int againfd;					// file descriptor for the message that again asks for the extension number
int rejectfd;					// file descriptor for the call rejection message
int buffersize;					// size of the data transfer between the Dialogic card and phone
int numberoflines;				// number of gateway lines
unsigned char** play_buffer;	// buffer that contains voice data to play out to a phone
int play_factor;				// factor multiplied by buffersize to determine packet transfer size to phone
int* rec_insession;				// indicates whether or not record session  from phone to PC is still alive
								// 1 - record session is still alive  0 - record session has been terminated

HANDLE* wait_to_read;			// handle for waiting until enough data has been written on play_buffer
HANDLE* read_finished;			// handle for waiting until reader is finished reading
HANDLE* record_done;			// handle for checking if record session from phone to PC has been terminated
HANDLE* play_done;				// handle for checking if play session from PC to phone has been terminated

DX_UIO* uio;					// user-defined input/output structure.  Used for playing and recording

/* user-defined write method.  Places voice data events from the phone onto a queue.
   Called by dx_reciottdata */
int my_write(int fd, char* ptr, unsigned count)
{
	DWORD isDone;

	/* check to see if record session has been terminated */
	isDone = WaitForSingleObject(record_done[fd - initialfd - 1], 0);
	if (isDone == WAIT_TIMEOUT)
	{
		/* Place voice data event on queue */
		sr_putevt(fd, VCHAN_RECORD, count, ptr, 0);
		return count;
	}
	else
	{
		/* Terminate dx_reciottdata */
		rec_insession[fd - initialfd - 1] = 0;
		sr_putevt(fd, TDX_RECORD, count, ptr, 0);
		return 0;
	}
}

/* user-defined read method.  Copies PC-originated voice data from buffer to where ptr is referencing
   Called by dx_play */
int my_read(int fd, char* ptr, unsigned count)
{
	DWORD result, isDone;
	int index = fd - numberoflines - initialfd - 1;

	/* check to see if playout session has been terminated */
	isDone = WaitForSingleObject(play_done[index], 0);
	if (isDone == WAIT_TIMEOUT)
	{
		/* check to see if enough bytes have been written to the buffer */
		result = WaitForSingleObject(wait_to_read[index], pread_timeout);

		if (result == WAIT_OBJECT_0)
		{
			/* copy voice data */
			memcpy((unsigned char *)ptr, play_buffer[index],count);
			SetEvent(read_finished[index]);
			return count;
		}
		else
		{
			/* play silence */
			memset(ptr,SILENCE,count);
			return count;
		}
	}
	else
	{
		/* Terminate dx_play */
		SetEvent(read_finished[index]);
		return 0;
	}
}

/* user-defined seek method.  Does not do anything */
long my_seek(int fd, long offset, int origin)
{
	return offset;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_initialize(JNIEnv *env, jobject thisObj, jint numLines, jint bufferSize, jint pXferFactor)
{
	/* Set user-defined input/output structure */
	uio = new DX_UIO;
	uio->u_write = my_write;
	uio->u_read = my_read;
	uio->u_seek = my_seek;
	dx_setuio(*uio);

	/* initialize global variables */
	greetingfd = dx_fileopen("greeting.wav", _O_RDONLY|_O_BINARY);
	againfd = dx_fileopen("again.wav", _O_RDONLY|_O_BINARY);
	rejectfd = dx_fileopen("reject.wav", _O_RDONLY|_O_BINARY);
	play_buffer = new unsigned char*[numLines];
	rec_insession = new int[numLines];
	wait_to_read = new HANDLE[numLines];
	read_finished = new HANDLE[numLines];
	record_done = new HANDLE[numLines];
	play_done = new HANDLE[numLines];

	for(int i = 0; i < numLines; i++)
	{
		play_buffer[i] = new unsigned char[bufferSize*pXferFactor];
		rec_insession[i] = 0;
		wait_to_read[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
		read_finished[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
		record_done[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
		play_done[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
	}

	buffersize = bufferSize;
	numberoflines = numLines;
	play_factor = pXferFactor;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_shutDown(JNIEnv *env, jobject thisObj, jintArray chDevArray)
{
	jboolean isCopy;
	jint* intArrayElems = env->GetIntArrayElements(chDevArray, &isCopy);
	jint size = env->GetArrayLength(chDevArray);

	/* close Dialogic channels */
	for (int i = 0; i < size; i++)
		dx_close(intArrayElems[i]);
	if (isCopy == JNI_TRUE)
		env->ReleaseIntArrayElements(chDevArray, intArrayElems, 0);

	/* clear up memory and close handles */
	delete play_buffer;
	delete rec_insession;
	delete uio;

	for (int j = 0; j < numberoflines; j++)
	{
		CloseHandle(wait_to_read[j]);
		CloseHandle(read_finished[j]);
		CloseHandle(record_done[j]);
		CloseHandle(play_done[j]);
	}
	delete wait_to_read;
	delete read_finished;
	delete record_done;
	delete play_done;
}

JNIEXPORT jint JNICALL
Java_cnrg_itx_gtwy_Gateway_channelSetUp(JNIEnv *env, jobject thisObj, jint channelNum, jint xferFactor)
{
	char mychannel[9];
	jint chdev;
	int parmval = 1;
	
	/* open Dialogic channel */
	sprintf(mychannel, "dxxxB1C%d", channelNum);
	chdev = dx_open(mychannel, 0);

	/* set up channel to detect one ring event */
	dx_setparm(chdev, DXCH_RINGCNT, (void *)&parmval);

	/* set transfer count between channel and phone */
	dx_setchxfercnt(chdev, buffersize*xferFactor);
	return chdev;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_reroute(JNIEnv *env, jobject thisObj, jint chDev, jint secDev)
{
	nr_scroute(secDev, SC_VOX, chDev, SC_LSI, SC_HALFDUP);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_unroute(JNIEnv *env, jobject thisObj, jint chDev)
{
	nr_scroute(chDev, SC_VOX, chDev, SC_LSI, SC_FULLDUP);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_channelOnHook(JNIEnv *env, jobject thisObj, jint chDev)
{
	dx_sethook(chDev, DX_ONHOOK, EV_SYNC);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_channelOffHook(JNIEnv *env, jobject thisObj, jint chDev)
{
	dx_sethook(chDev, DX_OFFHOOK, EV_SYNC);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_dialPhone(JNIEnv *env, jobject thisObj, jstring digits, jobject lineInfo)
{
	DX_CAP mycap;
	const char* utf_string;
	jboolean isCopy;
	jclass clazz;
	jfieldID fid;
	jint chDev;

	clazz = env->GetObjectClass(lineInfo);
	fid = env->GetFieldID(clazz, "myChDev", "I");
	chDev = env->GetIntField(lineInfo, fid);
	printf("%d\n",chDev);

	/* clear call analysis parameters */
	dx_clrcap(&mycap);

	/* set to return on the first ring */
	mycap.ca_nbrdna = 1;

	/* get phone number and call phone */
	utf_string = env->GetStringUTFChars(digits, &isCopy);
	dx_dial(chDev,utf_string,&mycap,DX_CALLP|EV_SYNC);
	if (isCopy == JNI_TRUE)
		env->ReleaseStringUTFChars(digits, utf_string);

	/* if phone rings, then return, otherwise throw an exception */
	if (ATDX_CPTERM(chDev) == CR_BUSY || ATDX_CPTERM(chDev) == CR_FAXTONE || 
		ATDX_CPTERM(chDev) == CR_NORB)
	{
		clazz = env->FindClass("cnrg/itx/gtwy/GatewayException");
		env->ThrowNew(clazz, "Call could not go through");
	}
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_playTones(JNIEnv *env, jobject thisObj, jstring digits, jint chDev)
{
	const char* utf_string;
	jboolean isCopy;

	utf_string = env->GetStringUTFChars(digits, &isCopy);
	dx_dial(chDev,utf_string,NULL,EV_SYNC);
	if (isCopy == JNI_TRUE)
		env->ReleaseStringUTFChars(digits, utf_string);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_resetRecordSequence(JNIEnv *env, jobject thisObj, jint lineNumber, jboolean isStartCall)
{
	int index = lineNumber - 1;

	if (isStartCall == JNI_TRUE)
		ResetEvent(record_done[index]);
	rec_insession[index] = 1;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_resetPlaySequence(JNIEnv *env, jobject thisObj, jint lineNumber, jboolean isStartCall)
{
	int index = lineNumber - 1;

	ResetEvent(play_done[index]);
	if (isStartCall == JNI_TRUE)
	{
		ResetEvent(wait_to_read[index]);
		ResetEvent(read_finished[index]);
	}
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_endRecordConnection(JNIEnv *env, jobject thisObj, jint lineNumber)
{
	SetEvent(record_done[lineNumber - 1]);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_Gateway_endPlayConnection(JNIEnv *env, jobject thisObj, jint lineNumber)
{
	SetEvent(play_done[lineNumber - 1]);
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_ListenChannel_waitForCall(JNIEnv *env, jobject thisObj, jint chDev)
{
	DX_EBLK* eblk = new DX_EBLK;

	/* set Dialogic channel to detect ring events */
	dx_setevtmsk(chDev, DM_RINGS);

	/* return when a ring event occurs */
	while (eblk->ev_event != DE_RINGS)
	{
		dx_getevt(chDev, eblk, -1);
	}

	delete eblk;
}

JNIEXPORT jstring JNICALL
Java_cnrg_itx_gtwy_ListenChannel_getExtension(JNIEnv *env, jobject thisObj, jint chDev, jboolean isSecondTime)
{
	DV_DIGIT digp;

	/* set up file playout parameters */
	DX_IOTT* mess_iott = new DX_IOTT;

	mess_iott->io_type = IO_DEV|IO_EOT;
	mess_iott->io_offset = 0;
	mess_iott->io_length = -1;
	
	if (isSecondTime == JNI_FALSE)
	{
		mess_iott->io_fhandle = greetingfd;
		dx_setchxfercnt(chDev, 1024);
	}
	else
		/* play different message if extension is being redialed during a session */
		mess_iott->io_fhandle = againfd;

	DV_TPT mess_tpt[4];

	/* set up recorded message's termination conditions */
	mess_tpt[0].tp_type = IO_CONT;
	mess_tpt[0].tp_termno = DX_MAXDTMF;
	mess_tpt[0].tp_length = 1;
	mess_tpt[0].tp_flags = TF_MAXDTMF;

	mess_tpt[1].tp_type = IO_CONT;
	mess_tpt[1].tp_termno = DX_MAXSIL;
	mess_tpt[1].tp_length = 500;
	mess_tpt[1].tp_flags = TF_MAXSIL;

	mess_tpt[2].tp_type = IO_EOT;
	mess_tpt[2].tp_termno = DX_LCOFF;
	mess_tpt[2].tp_length = 1;
	mess_tpt[2].tp_flags = TF_LCOFF;

	mess_tpt[3].tp_type = IO_EOT;
	mess_tpt[3].tp_termno = DX_LCOFF;
	mess_tpt[3].tp_length = 1;

	DV_TPT dig_tpt[4];

	/* set up termination conditions for when Dialogic channel reads DTMF digits */
	dig_tpt[0].tp_type = IO_CONT;
	dig_tpt[0].tp_termno = DX_DIGMASK;
	dig_tpt[0].tp_length = DM_P;
	dig_tpt[0].tp_flags = TF_DIGMASK;

	dig_tpt[1].tp_type = IO_CONT;
	dig_tpt[1].tp_termno = DX_MAXSIL;
	dig_tpt[1].tp_length = 500;
	dig_tpt[1].tp_flags = TF_MAXSIL;

	dig_tpt[2].tp_type = IO_EOT;
	dig_tpt[2].tp_termno = DX_LCOFF;
	dig_tpt[2].tp_length = 1;
	dig_tpt[2].tp_flags = TF_LCOFF;

	dig_tpt[3].tp_type = IO_EOT;
	dig_tpt[3].tp_termno = DX_LCOFF;
	dig_tpt[3].tp_length = 1;

	/* set up format for recorded message */
	DX_XPB* xpb = new DX_XPB;
	xpb->wFileFormat = FILE_FORMAT_WAVE;
	xpb->wDataFormat = DATA_FORMAT_PCM;
	xpb->nSamplesPerSec = DRT_8KHZ;
	xpb->wBitsPerSample = 8;

	/* clear digit buffer and play greeting message */
	dx_clrdigbuf(chDev);
	dx_playiottdata(chDev,mess_iott,mess_tpt,xpb,EV_SYNC);
	if (ATDX_TERMMSK(chDev) & (TM_MAXDTMF | TM_EOD))
	{
		/* read digits */
		dx_getdig(chDev,dig_tpt,&digp,EV_SYNC);
	}
	if (ATDX_TERMMSK(chDev) & (TM_LCOFF | TM_MAXSIL))
	{
		/* user failed to leave extension number */
		delete mess_iott;
		delete xpb;
		return NULL;
	}
	else
	{
		/* extension number has been left */
		delete mess_iott;
		delete xpb;
		return env->NewStringUTF(digp.dg_value);
	}
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_ListenChannel_rejectCallMessage(JNIEnv *env, jobject thisObj, jint chDev)
{
	DX_IOTT* mess_iott = new DX_IOTT;

	/* set up file playout parameters */
	mess_iott->io_type = IO_DEV|IO_EOT;
	mess_iott->io_offset = 0;
	mess_iott->io_length = -1;
	mess_iott->io_fhandle = rejectfd;

	DV_TPT mess_tpt[2];

	/* set up recorded message's termination conditions */
	mess_tpt[0].tp_type = IO_EOT;
	mess_tpt[0].tp_termno = DX_LCOFF;
	mess_tpt[0].tp_length = 1;
	mess_tpt[0].tp_flags = TF_LCOFF;

	mess_tpt[1].tp_type = IO_EOT;
	mess_tpt[1].tp_termno = DX_LCOFF;
	mess_tpt[1].tp_length = 1;

	/* set up format for recorded message */
	DX_XPB* xpb = new DX_XPB;
	xpb->wFileFormat = FILE_FORMAT_WAVE;
	xpb->wDataFormat = DATA_FORMAT_PCM;
	xpb->nSamplesPerSec = DRT_8KHZ;
	xpb->wBitsPerSample = 8;

	/* play rejection message */
	dx_playiottdata(chDev,mess_iott,mess_tpt,xpb,EV_SYNC);

	delete mess_iott;
	delete xpb;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_PlayChannel_playPacket(JNIEnv *env, jobject thisObj, jint chDev, jint lineNumber)
{
	/* set up playout parameters */
	DX_IOTT* play_iott = new DX_IOTT;
	play_iott->io_type = IO_DEV|IO_EOT|IO_UIO;
	play_iott->io_length = -1;
	play_iott->io_offset = 0;
	play_iott->io_fhandle = initialfd + numberoflines + lineNumber;

	/* begin playout session */
	dx_play(chDev, play_iott, NULL, EV_SYNC|MD_PCM|RM_SR8);

	delete play_iott;
}

JNIEXPORT void JNICALL
Java_cnrg_itx_gtwy_PlayDestination_writeToPlay(JNIEnv *env, jobject thisObj, jint lineNumber, jbyteArray data)
{
	jboolean iscopy;
	jint arraysize = env->GetArrayLength(data);
	jbyte* copyarray = env->GetByteArrayElements(data,&iscopy);
	int index = lineNumber - 1;
	int bufferindex = buffersize*play_factor;

	/* convert voice data from linear PCM to mu-law and write to play_buffer */
	for (int i = 0; i < arraysize; i++)
		play_buffer[index][i] = lin2mu[((unsigned char *)copyarray)[i]];

	/* inform the reader that it could read data and wait until it is done */
	SetEvent(wait_to_read[index]);
	WaitForSingleObject(read_finished[index], pwrite_timeout);

	if (iscopy == JNI_TRUE)
		env->ReleaseByteArrayElements(data,copyarray,0);
}

JNIEXPORT jstring JNICALL
Java_cnrg_itx_gtwy_RecordChannel_recordPacket(JNIEnv *env, jobject thisObj, jobject dataChannel, jint chDev, jint lineNumber)
{
	int event_result, event_length;		// event_result - if an event has occurred  event_length - length of the event
    long event_type, evt_handle;		// event_type - type of event  evt_handle - refers to the event handle
	long handlarray[2];					// refers to the devices where waitevtEX is waiting for an event
	jbyteArray bytearray = env->NewByteArray(buffersize);
	signed char* rec_buffer = new signed char[buffersize];
	
	jclass clazz; 
	jmethodID mid;
	DV_DIGIT digp;
								
	/* some record parameters */
	DX_IOTT* rec_iott = new DX_IOTT;
	rec_iott->io_type = IO_DEV|IO_EOT|IO_UIO;
    rec_iott->io_offset = 0;
    rec_iott->io_length = -1;
    rec_iott->io_fhandle = initialfd + lineNumber;

	/* set up the record termination conditions */
	DV_TPT rec_tpt[2];

	rec_tpt[0].tp_type = IO_CONT;
	rec_tpt[0].tp_termno = DX_MAXDTMF;
	rec_tpt[0].tp_length = 1;
	rec_tpt[0].tp_flags = TF_MAXDTMF;

	rec_tpt[1].tp_type = IO_EOT;
    rec_tpt[1].tp_termno = DX_LCOFF;
    rec_tpt[1].tp_length = 1;
	rec_tpt[1].tp_flags = TF_LCOFF;

	/* set up the record format */
	DX_XPB* xpb = new DX_XPB;
	xpb->wFileFormat = FILE_FORMAT_WAVE;
	xpb->wDataFormat = DATA_FORMAT_PCM;
	xpb->nSamplesPerSec = DRT_8KHZ;
	xpb->wBitsPerSample = 8;

	/* set up some initial parameters.  */
	handlarray[0] = rec_iott->io_fhandle;
	handlarray[1] = chDev;
	dx_clrdigbuf(chDev);
	dx_setchxfercnt(chDev, buffersize);

	// Get JNI call objects
	clazz = env->GetObjectClass(dataChannel);
	mid = env->GetMethodID(clazz,"sendToNetwork","([B)V");

	/* begin recording to a PC */
	int check = dx_reciottdata(chDev,rec_iott,rec_tpt,xpb,EV_ASYNC);

	/* begin only if dx_reciottdata does not fail */
	while (check == 0)
	{
		/* Get the next event off the queue */
		event_result = sr_waitevtEx(handlarray, 2, 10, &evt_handle);
	
		if (event_result != -1)
		{
			event_type = sr_getevttype(evt_handle);
			event_length = sr_getevtlen(evt_handle);

			/* gateway can only handle events of size less than or equal to the gateway transfer rate */
			/* Now switch based on event type */
			switch( event_type)
			{

			/* termination condition found -- exit loop */
			case TDX_RECORD:
				check = -1;
				break;

			case VCHAN_RECORD: /* Our own event type- contains sampled data */
				if (event_length == buffersize)
				{
					/* send out packets only when there is enough data */

					memcpy(rec_buffer,sr_getevtdatap(evt_handle),event_length);
					env->SetByteArrayRegion(bytearray,0,event_length,rec_buffer);
					env->CallVoidMethod(dataChannel,mid,bytearray);
				}
			}
		}
	}

	/* DTMF tone event */
	if (ATDX_TERMMSK(chDev) == TM_MAXDTMF)
	{
		dx_getdig(chDev,rec_tpt,&digp,EV_SYNC);

		delete rec_buffer;
		delete rec_iott;
		delete xpb;
		env->DeleteLocalRef(bytearray);

		/* somehow, hangup from PC is considered to be a DTMF event.  Make sure this is not the case */
		if (rec_insession[lineNumber - 1] == 1)
			return env->NewStringUTF(digp.dg_value);
		else
			return env->NewStringUTF("HANGUP");
	}

	/* phone hangup event */
	else if (ATDX_TERMMSK(chDev) == TM_LCOFF)
	{
		delete rec_buffer;
		delete rec_iott;
		delete xpb;
		env->DeleteLocalRef(bytearray);
		return env->NewStringUTF("HANGUP");
	}

	/* default case: just hangup */
	delete rec_buffer;
	delete rec_iott;
	delete xpb;
	env->DeleteLocalRef(bytearray);
	return env->NewStringUTF("HANGUP");
}
