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

  FILE..: CID.CPP
  AUTHOR: David Rowe
  DATE..: 23/4/01
  AUTHOR: Ron Lee
  DATE..: 25/2/07

  Caller ID decoding module.
  

         Voicetronix Voice Processing Board (VPB) Software
         Copyright (C) 1999-2007 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., 51 Franklin St, Fifth Floor, Boston,
         MA  02110-1301  USA

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

#include <assert.h>
#include <cmath>
#include <cstdio>
#include <cstring>

#include "apifunc.h"
#include "modulate.h"
#include "wobbly.h"
#include "mess.h"


#define N	  20	// MSS/CSS processing block in samples
#define NB	  3	// number of bits in each CSS/MSS processing block
#define NCSS	  50	// number of alternating 0101 needed for CSS detect
#define NMSS	  50	// number of 11111 needed for MSS detect
#define NASCII    67	// approx ASCII processing block in samples  
#define MAX_ASCII 256	// max number of ASCII chars in CID message

// states

#define WAIT_FOR_CSS      0
#define WAIT_FOR_MSS      1
#define WAIT_FOR_ASCII    2
#define GET_LENGTH        3
#define GET_MESSAGE       4
#define VALIDATE_CHECKSUM 5
#define FINISHED_OK       6

#define F1	1200
#define F2	2200
#define FS	8000	// sampling rate
#define NCOEF	7	// number of filter coefficients
#define NAV	6	// number of samples to average energy over
#define NDC	400	// number of sam. of CSS signal used for DC removal
#define NDCTMP	40	// number of sam. of CSS signal used for temp DC removal

/* fields for North American caller id */
#define PARAMETER_DATE    1  // data is ascii mmddhhmm
#define PARAMETER_CLI     2  // calling line identity (number)
#define PARAMETER_ALT_CLI 3
#define PARAMETER_NOCLI   4  // data is "O" unavailable, "P" private
#define PARAMETER_CPN     7  // calling party name
#define PARAMETER_NOCPN   8  // same as PARAMETER_NOCLI
#define PARAMETER_TYPE 0x11  // 1:voice, 2:ringback-when-free, 3:CPN-only, 
                             // 0x80:message-waiting
// CID message types
#define MDMF	0x80	// Multi Data Message Format
#define SDMF	0x04	// Single Data Message Format


static int cid_debug = 0;
static FILE *fcd = NULL;

// FIR filter coefficients

static float coef[] = {
	(float)1.0000000e+000,
	(float)1.0515795e+000,
	(float)6.5481993e-001,
	(float)4.8815834e-001,
	(float)6.5481993e-001,
	(float)1.0515795e+000,
	(float)1.0000000e+000
};

// filter coeffs for averaging

static float avcoef[] = {
	(float)(1.0/6.0),
	(float)(1.0/6.0),
	(float)(1.0/6.0),
	(float)(1.0/6.0),
	(float)(1.0/6.0),
	(float)(1.0/6.0),
};


void WINAPI vpb_cid_debug_on(const char *debug_file_name)
{ //{{{
	cid_debug = 1;
	fcd = fopen(debug_file_name, "wt");
	assert(fcd != NULL);
} //}}}


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

  FUNCTION: demodulate
  AUTHOR..: David Rowe
  DATE....: 29/4/01

  Demodulate array of input samples, assuming it is 1200 baud FSK sampled
  at 8kHz.

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

static void demodulate( float       *demod,	// output demodlated samples	
			const short *s,		// input samples
			int          n )	// length of s and demod
{ //{{{
	int   i,j;
	float pi = (float)4.0*(float)atan((float)1.0);
	float w1 = 2*pi*F1/FS;
	float w2 = 2*pi*F2/FS;
	float x_sinF1[NCOEF], x_cosF1[NCOEF], x_sinF2[NCOEF], x_cosF2[NCOEF];
	float xlpsf1, xlpcf1, xlpsf2, xlpcf2;
	float ef1[NAV], ef2[NAV];
	float avef1, avef2;

	// initialise memories

	for(i=0; i<NCOEF; i++) {
		x_sinF1[i] = (float)0.0;
		x_cosF1[i] = (float)0.0;
		x_sinF2[i] = (float)0.0;
		x_cosF2[i] = (float)0.0;
	}

	for(i=0; i<NAV; i++) {
		ef1[i] = (float)0.0;
		ef2[i] = (float)0.0;
	}

	for(i=0; i<n; i++) {
		// subject signal to mixers
		x_sinF1[NCOEF-1] = s[i]*(float)sin(w1*i);
		x_cosF1[NCOEF-1] = s[i]*(float)cos(w1*i);
		x_sinF2[NCOEF-1] = s[i]*(float)sin(w2*i);
		x_cosF2[NCOEF-1] = s[i]*(float)cos(w2*i);

		// low pass filter
		xlpsf1 = filter(&x_sinF1[NCOEF-1], coef, NCOEF);
		xlpcf1 = filter(&x_cosF1[NCOEF-1], coef, NCOEF);
		xlpsf2 = filter(&x_sinF2[NCOEF-1], coef, NCOEF);
		xlpcf2 = filter(&x_cosF2[NCOEF-1], coef, NCOEF);

		// sum the energies for each arm
		ef1[NAV-1] = xlpsf1*xlpsf1 + xlpcf1*xlpcf1;
		ef2[NAV-1] = xlpsf2*xlpsf2 + xlpcf2*xlpcf2;

		// average energies over past 6 samples
		avef1 = filter(&ef1[NAV-1], avcoef, NAV);
		avef2 = filter(&ef2[NAV-1], avcoef, NAV);

		// output is difference of upper and lower arms
		demod[i] = avef1 - avef2;

		// update memories
		for(j=0; j<NCOEF-1; j++) {
			x_sinF1[j] = x_sinF1[j+1];
			x_cosF1[j] = x_cosF1[j+1];
			x_sinF2[j] = x_sinF2[j+1];
			x_cosF2[j] = x_cosF2[j+1];
		}
		for(j=0; j<NAV-1; j++) {
			ef1[j] = ef1[j+1];
			ef2[j] = ef2[j+1];
		}
	}
} //}}}

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

  FUNCTION: correct_dc_offset
  AUTHOR..: David Rowe
  DATE....: 29/4/01

  Determines and corrects for the DC offset in the CID signal.  Requires
  the location of the CSS segment to be determined as an input,
  as the symmetrical nature (equal number of 1's and 0's) of the CSS signal 
  is used to determine the DC offset (average DC offset should be zero).

  Removing the DC offset is a crude but effective form of channel,
  equalisation as it corrects for the effect of different energy in the 
  F1 or F2 tones.

  The DC offset is measured across 400 samples (60 bits) of CSS signal.
  
\*-------------------------------------------------------------------------*/

inline void correct_dc_offset( float samples[],	// demodulated samples
			       int   n,	        // size of samples
			       int   ncss )     // sample index of CSS segment
{ //{{{
	// determine DC offset
	float dc_offset = 0.0;
	for(int i=ncss; i < ncss+NDC; ++i)
		dc_offset += samples[i];
	dc_offset /= NDC;

	// correct DC offset across entire signal
	for(int i=0; i < n; ++i)
		samples[i] -= dc_offset;
} //}}}

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

  FUNCTION: bit_sync_css
  AUTHOR..: David Rowe
  DATE....: 23/4/01

  Performs bit sync to extract NB bits from a block of N demodulated
  samples. Assumes input buffer extends at least N samples past start

  Designed to extract bits from CSS stage, up to start of MSS stage.

  Returns est position of last maxima to use as start of search
  window for next zero crossing in next buffer.

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

static int bit_sync_css(
	int          *bits_out,		// output buffer of NB bits
	float        *samples_in,	// input buffer of all samples
	unsigned int  start )		// start of zero crossing search window
{ //{{{
	float sign;
	int   zcross = 0, zcross_found = 0;

	assert(bits_out != NULL);
	assert(samples_in != NULL);
	assert(start >=1);

	// scan for first zero crossing

	for(unsigned int i=start; i < start+N; ++i) {
		sign = samples_in[i]*samples_in[i-1];
		if ((sign < 0.0) && !zcross_found) {
			zcross = i;
			zcross_found = 1;
		}
	}

	// if zero crossing found, use to adjust timing est

	if (zcross_found) {

		// find closest sample to zero crossing
		if (fabs(samples_in[zcross-1]) < fabs(samples_in[zcross]))
			zcross--;
	}
	else {
		// else approximate with guess
		zcross = start+3;
	}

	// now extract bits by sampling at approximate position of maxima

	bits_out[0] = sgn(samples_in[zcross + 3]);
	bits_out[1] = sgn(samples_in[zcross + 10]);
	bits_out[2] = sgn(samples_in[zcross + 17]);

	// return position of start of next search window

	return (zcross + 17);
} //}}}

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

  FUNCTION: bit_sync_ascii
  AUTHOR..: David Rowe
  DATE....: 24/4/01

  Performs bit sync to extract NBASCII bits from a buffer of demodulated
  input samples.  Assumes input buffer extends 2*NASCII samples past start.
  
  First looks for start-stop bit (0-1) zero crossing.  This must occur for
  every valid 10 bit ASCII word (data octet).  Then extract next 10 bits 
  based on position of this zero crossing.  Start and stop bits are stripped
  to leave NBASCII bits to be returned.

  The block is considered valid if a zero-crossing is found.
  
  Returns est position of last maxima to use as start of search window for 
  next zero crossing in next buffer.

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

int bit_sync_ascii(
	int   *bits_out,	// output buffer of NBASCII bits
	float *samples_in,	// input buffer of all samples
	int   start,		// start of zero crossing search window
	int   *valid )		// returns non-zero if valid block
{ //{{{
	float sign;
	int   zcross = 0, zcross_found = 0;

	assert(bits_out != NULL);
	assert(samples_in != NULL);
	assert(start >=1);
	assert(valid != NULL);

	// scan for first zero crossing (stop-start bit transition)
	*valid = 0;
	for(int i=start; i< start+NASCII; ++i) {
		sign = samples_in[i]*samples_in[i-1];
		if ((samples_in[i] < 0.0) && (sign < 0.0) && !zcross_found) {
			zcross = i;
			zcross_found = 1;
			*valid = 1;
		}
	}

	// if zero crossing found, use to adjust timing est
	if (zcross_found) {

		// find closest sample to zero crossing
		if (fabs(samples_in[zcross-1]) < fabs(samples_in[zcross]))
			zcross--;
	}
	else {
		// else approximate with guess
		zcross = start+3;
	}

	// now extract bits by sampling at approximate position of maxima
	// discard start (offset 3) and stop (offset 63) bit

	bits_out[0] = sgn(samples_in[zcross + 10]);
	bits_out[1] = sgn(samples_in[zcross + 17]);
	bits_out[2] = sgn(samples_in[zcross + 23]);
	bits_out[3] = sgn(samples_in[zcross + 30]);
	bits_out[4] = sgn(samples_in[zcross + 37]);
	bits_out[5] = sgn(samples_in[zcross + 43]);
	bits_out[6] = sgn(samples_in[zcross + 50]);
	bits_out[7] = sgn(samples_in[zcross + 57]);

	// return position of start of next search window, just past
	// mid-point of stop bit.

	return (zcross + 63);
} //}}}

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

  FUNCTION: extract_presentation_layer_message
  AUTHOR..: David Rowe
  DATE....: 23/4/01

  Extracts the presentation layer message from the buffer of demodulated 
  samples.  Performs bit level sync, and data link layer processing.
  
  Uses features of the CID signal to determine if a valid CID signal
  is present in the input buffer. Throws exceptions on various error
  conditions.

  A valid CID signal has the following data link layer format:
  
  300 bits of Channel Seizure Stage (CSS) - a 1010... sequence
  180 bits of Mark Signal Stage (MSS) - a 1111... sequence
  ASCII data in 10 bit octets - start bit (0) - 8 data bits - stop bit (1)

  The ASCII data link layer is organised as:

  message type
  message length
  presentation layer message
  checksum
  
\*-------------------------------------------------------------------------*/

static void extract_presentation_layer_message(
		unsigned char  *pres,		// output array of pres layer data
		int            *npres,		// number of bytes of pres layer data
		float          *samples_in,	// array of input samples
		int            n )		// number of samples in samples_in
{ //{{{
	int             msgtype=0;
	int             bits_out[NBASCII];
	int             start, i, j;
	int             valid_bits, last_bit;
	int             state, valid;
	unsigned char   ascii_char;
	int             length = 0, count = 0;
	unsigned char   checksum = 0, sum;

	assert(NBASCII >= NB); // make sure enough storage in bits_out

	// CSS/MSS processing ---------------------------------------------

	start = 1;
	last_bit = 0;
	valid_bits = 0;
	state = WAIT_FOR_CSS;
	i = 0;


	while((start < (n-2*N)) && (state != WAIT_FOR_ASCII)) {
		start = bit_sync_css(bits_out, samples_in, start);
		if (cid_debug) {
			fprintf(fcd,"state: %d start: %4d valid_bits: %4d [ %d %d %d ]\n",
			state, start, valid_bits,
			bits_out[0], bits_out[1],bits_out[2]);
		}

		for(j=0; j<NB; j++) {

			switch(state) {
			    case WAIT_FOR_CSS:

				// Need NCSS 0-1-0-1 for valid CSS

				if (last_bit) {
					// last bit 1, need 0
					if (bits_out[j] == 0)
						valid_bits++;
					else
						valid_bits = 0;
				}
				else {
					// last bit 0, need 1
					if (bits_out[j] == 1)
						valid_bits++;
					else
						valid_bits = 0;
				}

				if (valid_bits > NCSS) {
					// remove DC offset here
					correct_dc_offset(samples_in, n, start);

					state = WAIT_FOR_MSS;
					valid_bits = 0;
					if (cid_debug)
						fprintf(fcd,"WAIT_FOR_MSS\n");
				}
				break;

			    case WAIT_FOR_MSS:

				// need NMSS consecutive 1s

				if (bits_out[j] == 1)
					valid_bits++;
				else
					valid_bits = 0;
				if (valid_bits > NCSS) {
					state = WAIT_FOR_ASCII;
					valid_bits = 0;
					if (cid_debug)
						fprintf(fcd,"WAIT_FOR_ASCII\n");
				}
				break;
			}
			last_bit = bits_out[j];
		}
	}

	// If we havent reached WAIT_FOR_ASCII state then this is not
	// a valid CID signal
	if (state != WAIT_FOR_ASCII){
		throw Wobbly(VPBAPI_CID_NO_SIGNAL);
	}

	// OK, we are in MSS, and need to look for start of ASCII
	// 1-0 transition signals start of first ASCII char

	while((start < (n-NASCII)) && (state != FINISHED_OK)) {
		start = bit_sync_ascii(bits_out, samples_in, start, &valid);

		if (cid_debug) {
			fprintf(fcd,"state: %d start: %4d valid_bits: %4d\t[ ",
			state, start, valid_bits);
			for(i=0; i<NBASCII; i++)
				fprintf(fcd," %d", bits_out[i]);
			if (valid) {
				ascii_char = bits2char(bits_out);
				fprintf(fcd, " ] %c 0x%02x\n", ascii_char,
					(int)ascii_char&0xff);
			}
			else {
				fprintf(fcd, " ]\n");
			}
		}

		// state machine to perform data link layer processing

		ascii_char = bits2char(bits_out);
		switch(state) {

		    case WAIT_FOR_ASCII:

			// wait for valid ASCII data

			if (valid) {
				// Check the message types
				msgtype=ascii_char;
				if ((msgtype != MDMF)&&(msgtype != SDMF)) {
					if (cid_debug){
						fprintf(fcd,"Bad Messages type: 0x%02x",(int)msgtype);
					}
					throw Wobbly(VPBAPI_CID_BAD_MESS_TYPE);
				}

				checksum = ascii_char;
				state = GET_LENGTH;
			}
			break;

		    case GET_LENGTH:
			length = ascii_char;
			*npres = length;
			count = 0;
			checksum += ascii_char;
			if (cid_debug){
				fprintf(fcd,"Message length: %d \n",(int)ascii_char);
			}
			state = GET_MESSAGE;
			break;

		    case GET_MESSAGE:
			pres[count++] = ascii_char;
			checksum += ascii_char;
			if (count == length)
				state = VALIDATE_CHECKSUM;
			break;

		    case VALIDATE_CHECKSUM:
			sum = checksum + ascii_char;
			if (sum != 0)
				throw Wobbly(VPBAPI_CID_BAD_CHECKSUM);
			state = FINISHED_OK;
			if (cid_debug){
				fprintf(fcd,"Message string: %s [ %d ] \n",pres,length);
			}
			break;

		    default:
			assert(0);
			break;
		}
	}
	if (msgtype == SDMF){
		// convert to MDMF
		// We need to add a paramter type and length to the SDMF
		// so we need to loose the first 4 words and replace then with 2 bytes
		// i,j,count
		count = length - (4*2);
		*npres = count + 2;
		pres[0]=PARAMETER_CLI;
		pres[1]=count;

		for (i=2;i<length;i++){
			pres[i]=pres[i+6];
		}
	}

	if (state != FINISHED_OK){
		throw Wobbly(VPBAPI_CID_BAD_CHECKSUM);
	}
} //}}}


int WINAPI vpb_cid_decode( char *number, const short *in, int n )
{ //{{{
	*number = '\0';	// Start with a terminated string in case of error

	try {
		float          demod[n];
		unsigned char  pres[MAX_ASCII];
		int            npres;

		demodulate(demod, in, n);
		extract_presentation_layer_message(pres, &npres, demod, n);

		if(cid_debug)
			fprintf(fcd,"Extracted : %s [ %d ]\n",pres,npres);

		for( int i = 0; i < npres; i += 2 + pres[i+1] ) {
			// is this field a "calling line identity" field?
			if (pres[i] == PARAMETER_CLI || pres[i] == PARAMETER_ALT_CLI) {
				memcpy(number, &pres[i+2], pres[i+1]);
				number[pres[i+1]] = 0;
				return VPB_OK;
			}
		}
	} catch(const Wobbly &w)
	{
		throw VpbException("vpb_decode_cid: decode failure");
	}
	return -1;
} //}}}

int WINAPI vpb_cid_decode2( VPB_CID *cli_struct, const short *in, int n )
{ //{{{
	float           demod[n];
	unsigned char	pres[MAX_ASCII];	// presentation layer message
	int	        npres;			// number of chars in pres

	cli_struct->clear();

	try {
		demodulate(demod, in, n);
		extract_presentation_layer_message(pres, &npres, demod, n);
	}
	catch (const Wobbly &w) {
		mprintf("wobbly! on line %d in %s\n",w.line,w.file);
		return -1;
	}

	// now sca pct fields 
	memcpy(cli_struct->pres, &pres[0], npres);
	cli_struct->pres[npres+1] = '\0';

	//printf("Decoding");
	for(int i = 0; i < npres; i += 2 + pres[i+1] )
	{
		//printf(".");
		switch(pres[i])
		{
		    case VPB_CID_DATE_TIME:
			if(pres[i+1] != 8) {
				mprintf("VPB_CID_DATE_TIME error\n");
				return -1;
			}
			//printf("DT");
			memcpy(cli_struct->date_time, &pres[i+2], 8);
			cli_struct->date_time[8] = '\0';
			break;

		    case VPB_CID_CALLING_LINE_DN:   // "calling party number"
			if(pres[i+1] > 18) {
				mprintf("VPB_CID_CALLING_LINE_DN error\n");
				return -1;
			}
			//printf("CLDN");
			memcpy(cli_struct->cldn, &pres[i+2], pres[i+1]);
			cli_struct->cldn[pres[i+1]] = '\0';
			cli_struct->ra_cldn[0] = '\0';	// remove absence reason
			break;

		    case VPB_CID_CALLED_DN:   // "called number"
			if(pres[i+1] > 18) {
				mprintf("VPB_CID_CALLED_DN error\n");
				return -1;
			}
			//printf("DN");
			memcpy(cli_struct->cdn, &pres[i+2], pres[i+1]);
			cli_struct->cdn[pres[i+1]] = '\0';
			break;

		    case VPB_CID_RFA_CLDN:	// Reason For Absence of CLDN
			if(pres[i+1] != 1) {
				mprintf("VPB_CID_RFA_CLDN error\n");
				return -1;
			}
			//printf("RFACLDN");
			cli_struct->ra_cldn[0] = pres[i+2];
			cli_struct->ra_cldn[pres[1]] = '\0';
			break;

		    case VPB_CID_CALLER_NAME:	// Caller Name/text parameter
			if(pres[i+1] > 20) {
				mprintf("VPB_CID_CALLER_NAME error\n");
				return -1;
			}
			//printf("CN");
			memcpy(cli_struct->cn, &pres[i+2], pres[i+1]);
			cli_struct->cn[pres[i+1]] = '\0';
			cli_struct->ra_cn[0] = '\0';	// remove absence reason
			break;

		    case VPB_CID_RFA_CN:	// Reason For Absence of CN
			if(pres[i+1] != 1) {
				mprintf("VPB_CID_RFA_CN error\n");
				return -1;
			}
			//printf("RFACN");
			cli_struct->ra_cn[0] = pres[i+2];
			cli_struct->ra_cn[1] = '\0';
			break;

		    case VPB_CID_CALL_TYPE: // Call type Unsigned Char
			if(pres[i+1] != 1) {
				mprintf("VPB_CID_CALL_TYPE error\n");
				return -1;
			}
			//printf("CT");
			cli_struct->call_type = (CidCallType)pres[i+2];
			break;

		    case VPB_CID_NMSS:	// Network Message System Status
			if(pres[i+1] != 1) {
				mprintf("VPB_CID_NMSS error\n");
				return -1;
			}
			//printf("NMSS");
			cli_struct->nmss = pres[i+2];
			break;

		    default:	// Hasnt found a known field code
			    //printf("???");
			    break;
		}
	}
	//printf("[%d]\n\n",error);

	return VPB_OK;
} //}}}

