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

	FILE....: cidg.cpp
	TYPE....: C Program
	AUTHOR..: Peter Wintulich
	DATE....: 12/11/02
	AUTHOR..: Ron Lee
	DATE....: 25/2/07

	Module to generate caller ID signals.

	CID_EMPTY this is not a field. if used in vpb_cid_set() as the field
       	type it will initalise the structure with default values for an cid 
	string with no numbers or text, only a valid date time.
	
	CID_DATE_TIME field, the date and time stored in this field is
       	dependent on the message type. If CID_CALL_TYPE value 
	<128 then it will be current time, 
	==129 date time of recived or transfered meaasge.

      	CID_CALLING_LINE_DN, CID_CALLED_DN fields, 18 digits maximum.

	CID_RFA_CLDN, CID_RFA_CN fields, 'O'== Number Unavailable
	'P'== Number withheld

	CID_CALLER_NAME and CID_TEXT_PARAMETER field are the same field. 
	If the Caller name is presented then the RFA_CN is not sent. The 
	CID_TEST_PARAMETER is intended to assist in identification of the 
	call type and may contain "Payphone", "International", 
	"Ring-back Call". Caller name should take priority if available.
	
\*---------------------------------------------------------------------------*/

#include "apifunc.h"
#include "mapdev.h"
#include "vpbdial.h"
#include "modulate.h"
#include "mess.h"

#include <unistd.h>
#include <cstdio>
#include <cstring>


int WINAPI vpb_cid_set(VPB_CID *cid, CidDataType field, void *value)
{ //{{{
	int valid = 0;
	int len;

	if( cid == NULL ) return -1;

	switch(field)
	{
	    case VPB_CID_EMPTY:		// Clear all fields 
		cid->clear();
		break;

	    case VPB_CID_DATE_TIME:
		if( value == NULL ) {
			strcpy(cid->date_time, "AUTO");
			break;
		}
		if( strlen((char*)value) != 8 )
			return -1;

		// do a very basic validity check on the range of the time data
		// this does not guarantee the result is a valid calendar date.
		// XXX should this just atoi the components and numeric compare?

		// start with the Month
		switch( *((char*)value) )
		{
		    case '0':
			if( strchr("0123456789", ((char*)value)[1]) ) ++valid;
			break;

		    case '1':
			if( strchr("012", ((char*)value)[1]) ) ++valid;
		}

		// Day
		if( strchr("0123", ((char*)value)[2]) ) ++valid;
		if( ((char*)value)[2] == '3' ) {
			if( strchr("01",         ((char*)value)[3]) ) ++valid;
		} else  if( strchr("0123456789", ((char*)value)[3]) ) ++valid;

		// Hour
		if( strchr("012", ((char*)value)[4]) ) ++valid;
		if( ((char*)value)[4] == '2' ) {
			if( strchr("0123",       ((char*)value)[5]) ) ++valid;
		} else  if( strchr("0123456789", ((char*)value)[5]) ) ++valid;

		// Minute
		if( strchr("012345",     ((char*)value)[6]) ) ++valid;
		if( strchr("0123456789", ((char*)value)[7]) ) ++valid;

		// Final result
		if(valid == 7) strcpy(cid->date_time, (char*)value);
		else           return -1;

		break;

	    case VPB_CID_CALLING_LINE_DN:
		len = strlen((char*)value);
		for(int i = 0; i < len; ++i)
			if( strchr("0123456789", ((char*)value)[i]) ) ++valid;

		if(len > 18 || valid != len) return -1;

		strcpy(cid->cldn, (char*)value);
		cid->ra_cldn[0] = '\0';		// remove absence message
		break;

	    case VPB_CID_CALLED_DN:
		len = strlen((char*)value);
		for(int i = 0; i < len; ++i)
			if( strchr("0123456789", ((char*)value)[i]) ) ++valid;

		if(len > 18 || valid != len) return -1;

		strcpy(cid->cdn, (char*)value);
		break;

	    case VPB_CID_RFA_CLDN:
		if( *(char*)value == 'O' ) {
			strcpy(cid->ra_cldn, "O");  // Number Unavailable
			cid->cldn[0] = '\0';        // remove number
		}
		else if( *(char*)value == 'P' ) {
			strcpy(cid->ra_cldn, "P");  // Number Withheld
			cid->cldn[0] = '\0';	    // remove number
		}
		else return -1;                     //error Invalid value

		break;

	    case VPB_CID_CALLER_NAME:
		if(strlen((char*)value) > 20) return -1;

		strcpy(cid->cn, (char*)value);
		cid->ra_cn[0] = '\0';		// remove absence reason
		break;

	    case VPB_CID_RFA_CN:            // Reason For Absence of Calling Name
		if( *(char*)value == 'O' ) {
			strcpy(cid->ra_cn, "O");    // Name unavailable
			cid->cn[0] = '\0';	    // remove name
		}
		else if( *(char*)value == 'P' ) {
			strcpy(cid->ra_cn, "P");    // Name withheld
			cid->cn[0] = '\0';	    // remove name
		}
		else return -1;                   //error Invalid value

		break;

	    case VPB_CID_CALL_TYPE:
		switch( *(CidCallType*)value ) {
		    case VPB_CID_CT_VOICE:
		    case VPB_CID_CT_RBWF:
		    case VPB_CID_CT_MW:
			cid->call_type = *(CidCallType*)value;

		    default:
			return -1;
		}
		break;

	    case VPB_CID_NMSS:

		if((*(int*)value) > 0 && (*(int*)value) < 256)
			cid->nmss = *(int*)value;
		else	return -1;
		break;

	    default:
		return -1;   // ERROR
	}
	return 0;
} //}}}

int WINAPI vpb_cid_compose_dlp(const VPB_CID &cid, char *dlp)
{ //{{{
	char   temp[20];
	time_t curtime;
	struct tm loctime;
	int    ptr = 2;	// pointer into dlp[n], skip header and len to start.

	dlp[0] = 0x80;	// Header byte;

	// VPB_CID_CALL_TYPE
	if(cid.call_type != VPB_CID_CT_UNKNOWN)
	{
	    dlp[ptr]   = VPB_CID_CALL_TYPE; // label
	    dlp[ptr+1] = 1;		    // size
	    dlp[ptr+2] = cid.call_type;     // data
	    ptr += 3;			    // update dlp byte count
	}

	// VPB_CID_DATE_TIME
	if(cid.date_time[0] != 0)	    // Always 10 bytes
	{
	    dlp[ptr]   = VPB_CID_DATE_TIME; // label
	    dlp[ptr+1] = 8;		    // message length
	    if(strcmp(cid.date_time, "AUTO") == 0)
	    {
		/* Get the current time. */
		curtime = time (NULL);
    		/* Convert it to local time representation. */
		localtime_r(&curtime,&loctime);
		sprintf(temp,"%.2d%.2d%.2d%.2d\n",
				(loctime.tm_mon)+1,
			       	loctime.tm_mday,
			       	loctime.tm_hour,
			       	loctime.tm_min);
		strcpy((&dlp[ptr])+2, temp);
	    }
	    else
		strcpy(dlp + ptr + 2, cid.date_time);

	    ptr += 10;
	}

	// VPB_CID_CALLING_LINE_DN	Calling Line Directory #
	if(cid.cldn[0] != 0)			// Max 18 bytes
	{
	    dlp[ptr]   = VPB_CID_CALLING_LINE_DN;
	    dlp[ptr+1] = strlen(cid.cldn);
	    strcpy(dlp + ptr + 2, cid.cldn);
	    ptr += 2 + strlen(cid.cldn);
	}

	// VPB_CID_CALLED_DN	Called Directory #
	if(cid.cdn[0] != 0)			// Max 18 bytes
	{
	    dlp[ptr]   = VPB_CID_CALLED_DN;
	    dlp[ptr+1] = strlen(cid.cdn);
	    strcpy(dlp + ptr + 2, cid.cdn);
	    ptr += 2 + strlen(cid.cdn);
	}

	// VPB_CID_RFA_CLDN	Reason For Absence of Calling Line Directory #
	if(cid.ra_cldn[0] != 0)		// 3 bytes
	{
	    dlp[ptr]   = VPB_CID_RFA_CLDN;
	    dlp[ptr+1] = 1;
	    dlp[ptr+2] = cid.ra_cldn[0];
	    ptr += 3;
	}

	// VPB_CID_CALLER_NAME	Caller Name/Text Parameter
	if(cid.cn[0] != 0)		// Max 20 ascii bytes
	{
	    dlp[ptr]   = VPB_CID_CALLER_NAME;
	    dlp[ptr+1] = strlen(cid.cn);
	    strcpy(dlp + ptr + 2, cid.cn);
	    ptr += 2 + strlen(cid.cn);
	}

	// VPB_CID_RFA_CN	Reason For Absence of Caller Name/Text Parameter
	if(cid.ra_cn[0] != 0)	// 3 bytes
	{
	    dlp[ptr]   = VPB_CID_RFA_CN;
	    dlp[ptr+1] = 1;
	    dlp[ptr+2] = cid.ra_cn[0];
	    ptr += 3;
	}

	// VPB_CID_NMSS		Network Message System Status 
	// (number of waiting messages in message bank)
	if(cid.nmss != 0)		// 3 bytes
	{
	    dlp[ptr]   = VPB_CID_NMSS;
	    dlp[ptr+1] = 1;
	    dlp[ptr+2] = cid.nmss;
	    ptr += 3;
	}

	// Save Presentation layer message length
	dlp[1] = ptr - 2;

	// Now calculate & write checksum byte
	unsigned char csb = 0;
	for(int i = 0; i < ptr; i++)
		csb += dlp[i];
	dlp[ptr] = 0 - csb;

	return ++ptr;
} //}}}

int WINAPI vpb_cid_compose_dlp(const VPB_CID *cid, char *dlp)
{ //{{{
	return vpb_cid_compose_dlp( *cid, dlp );
} //}}}

void WINAPI vpb_cid_compose_wav(const char *dlp, int dlp_len,
				short *wav_buf, int *wav_len,
				const Country *country)
{ //{{{
	int	CSS = 300;	 // CSS bit count
	int	MSS = 180;	 // MSS bit count
	int	sc;		 // sample count returned by f(n).
	int	accs = 0;	 // Accumulated samples in wav_buf 

	//  These are required to keep phase and bit timing in sync.
	int	bit_time    = 0; // used by bit_encode to track samples/bit
	double	sample_time = 0; // used by bit_encode to track sinewave cycles

	if(country == NULL) country = vpb_get_country_data(61); // AUSTRALIA

	// Compose CSS (alternate 1's & 0's)
	for(int i=0; i < CSS; ++i) {
	    bit_encode((i & 1 ? 0 : 1),
		       &sc, wav_buf + accs, &sample_time, &bit_time, country);
  	    accs +=sc;
	}

	// Compose MSS (all 1's)
	for(int i=0; i < MSS; ++i) {
		bit_encode(1, &sc, &wav_buf[accs], &sample_time, &bit_time, country);
		accs +=sc;
	}

	// Compose data frame
	for(int i=0; i < dlp_len; ++i)
		byte_encode(dlp[i], &accs, wav_buf, &sample_time, &bit_time, country);

	// Trail some bits at the end (not required)
	for(int i=0; i < 5; ++i) {
		bit_encode(1, &sc, &wav_buf[accs], &sample_time, &bit_time, country);
		accs +=sc;
	}
	*wav_len = accs;	// Accumulated sample count
} //}}}

void WINAPI vpb_cid_t2_compose_wav(const char *dlp, int dlp_len,
				   short *wav_buf, int *wav_len,
				   const Country *country)
{ //{{{
	int     MSS = 80;        // MSS bit count << Reduced for Type II
	int     sc;              // sample count returned by f(n).
	int     accs = 0;        // Accumulated samples in wav_buf

	//  These are required to keep phase and bit timing in sync.
	int     bit_time    = 0; // used by bit_encode to track samples/bit
	double  sample_time = 0; // used by bit_encode to track sinewave cycles

	if(country == NULL) country = vpb_get_country_data(61); // AUSTRALIA

	// Compose MSS (all 1's)
	for(int i=0; i < MSS; ++i) {
		bit_encode(1, &sc, &wav_buf[accs], &sample_time, &bit_time, country);
		accs +=sc;
	}

	// Compose data frame
	for(int i=0; i < dlp_len; ++i)
		byte_encode(dlp[i], &accs, wav_buf, &sample_time, &bit_time, country);

	// Trail some bits at the end (not required)
	for(int i=0; i < 5; ++i) {
		bit_encode(1, &sc, &wav_buf[accs], &sample_time, &bit_time, country);
		accs +=sc;
	}
	*wav_len = accs;       // Accumulated sample count
} //}}}

void WINAPI vpb_ring_with_cid(int handle, const VPB_CID &cid, int cadence)
{ //{{{
	// Eventually this should decide exactly what to do based on
	// the country for handle, but for now assume we need a 1 sec
	// guard tone before the cid data is transmitted.

	vpb_ring_station_async(handle, 1);  // validates the handle

	unsigned short  b, ch;
	maphndletodev(handle, &b, &ch);

	char            dlp[VPB_CID_MAX_DLP];
	short           wav_buf[VPB_CID_MAX_BUF];
	const Country  *country = get_country(b,ch);
	int             n       = vpb_cid_compose_dlp( cid, dlp );
	int             wav_len;

	vpb_cid_compose_wav( dlp, n, wav_buf, &wav_len, country );

	usleep( 1000 * 1000 );
	ring_station_async(b, ch, 255);

	vpb_play_buf_start( handle, VPB_LINEAR );
	vpb_play_buf_sync( handle, (char*)wav_buf, wav_len * sizeof(short) );
	vpb_play_buf_finish_sync( handle );

	usleep( 1000 * 1000 );
	ring_station_async(b, ch, cadence);
} //}}}

void WINAPI vpb_send_cid_t2_sync(int handle, const VPB_CID &cid)
{ //{{{
	/*
	1) (OPTIONAL)	send 300ms of 440Hz tone. To alert caller of incoming 
			Call Waiting CID.
	2)		50ms silence
	3)		100ms of Dual tone 2130 & 2750 at -10dB 
	4)		Wait for 200ms for DTMF A or D reply,
			If no reply inside 200ms terminate, else 5)
	5)		transmit C.W.CID wave
	6)		50ms silence
	*/

	static VPB_TONE cas1 = { 2130, 2750, 0, -25, -25, 0, 100, 150, NULL  };
	static VPB_TONE cas  = {    1,    0, 0,   0,   0, 0,   0,  50, &cas1 };

	static const int timer_id = 21302750; // Arbitrary (unique) magic number

	unsigned short  b, ch;

	try {
		ValidHandleCheck(handle);
		maphndletodev(handle, &b, &ch);
	}
	catch(const Wobbly&) {
		throw VpbException("vpb_send_cid_t2_sync: invalid_handle");
	}

	char            dlp[VPB_CID_MAX_DLP];
	short           wav_buf[VPB_CID_MAX_BUF];
	const Country  *country = get_country(b,ch);
	int             n       = vpb_cid_compose_dlp( cid, dlp );
	int             wav_len;
	VPB_EVENT	e;
	void           *timer;

	vpb_timer_open( &timer, handle, timer_id, 500 );
	vpb_cid_t2_compose_wav( dlp, n, wav_buf, &wav_len, country );

	mprintf("[%d/%d] vpb_send_cid_t2_sync: dlp %d, wav %d\n", b, ch, n, wav_len);

	//XXX If the port is bridged (highly likely) then have to mute the
	//    bridge while we send this or the squawks (and CID info!!) will
	//    go to the bridged port also...  XXX

	vpb_play_buf_start( handle, VPB_LINEAR );
	vpbdial_playtone( handle, cas );
	vpb_timer_start( timer );

	for(;;) {
		if( vpb_get_event_ch_sync(handle, &e) != VPB_OK ) {
			vpb_play_buf_finish( handle );
			vpb_timer_close( timer );
			throw VpbException("vpb_send_cid_t2_sync: unexpected event error");
		}

		if( e.type == VPB_DTMF && (e.data == 'A' || e.data == 'D') )
			goto send_cid;

		if( e.type == VPB_TIMEREXP && e.data == timer_id) {
			mprintf("[%d/%d] vpb_send_cid_t2_sync: not supported by handset\n",
				b, ch);
			goto done;
		}

		char s[VPB_MAX_STR];

		vpb_translate_event( &e, s );
		mprintf("[%d/%d] vpb_send_cid_t2_sync: %s", b, ch, s);
	}

    send_cid:
	mprintf("[%d/%d] vpb_send_cid_t2_sync: sending type2 CID\n", b, ch);
	vpb_play_buf_sync( handle, (char*)wav_buf, wav_len * sizeof(short) );

    done:
	vpb_play_buf_finish_sync( handle );
	vpb_flush_digits( handle );
	vpb_timer_close( timer );
} //}}}

