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

    FILE....: VPBDIAL.CPP
    TYPE....: C++ Module
    AUTHOR..: David Rowe
    DATE....: 13/2/98
    AUTHOR..: Ron Lee
    DATE....: 9/12/06

    This file contains the implementation of the vpb_dial API function.


         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 "vpbdial.h"
#include "mapdev.h"
#include "apifunc.h"
#include "objtrack.h"
#include "config.h"
#include "mess.h"

#ifdef _OPENPRI
 #include "openpri.h"
#endif

#include <cassert>
#include <cstring>
#include <cmath>
#include <unistd.h>

using std::string;


#define	FS	8000	// sample rate in Hz


//! Structure to map tones to an identifying character in a dial string.
struct TONE
{ //{{{
	//! Container type for a list of @c TONE's.
	typedef std::vector<TONE>   List;

	//! The identifying character to map to this tone.
	char	    c;

	//! The tone definition data.
	VPB_TONE    tone;
}; //}}}

// This container holds the list of all tones that we can play, 
static TONE::List tones;


// Basic prototype for a DTMF tone.
// The values -8.725 and -5.7 here correspond to a measured -11dBV and -9dBV
// into a Euro load.  Or at least they did when David tested it in 2002 and
// determined the V4PCI message values to be 12000 and 17000 respectively.
// XXX confirm these are still valid for current hardware and gain settings.
static VPB_TONE dtmf_tone = { 0, 0, 0, -8.725, -5.7, 0, 75, 75, NULL };

// Tone specific data to overlay the generic dtmf_tone prototype with.
static struct DTMF_DIGIT {
	uint16_t    low;
	uint16_t    high;
	char	    c;
} dtmf_digits[] = {
	{697, 1209, '1'},
	{697, 1336, '2'},
	{697, 1477, '3'},
	{697, 1633, 'A'},
	{770, 1209, '4'},
	{770, 1336, '5'},
	{770, 1477, '6'},
	{770, 1633, 'B'},
	{852, 1209, '7'},
	{852, 1336, '8'},
	{852, 1477, '9'},
	{852, 1633, 'C'},
	{941, 1209, '*'},
	{941, 1336, '0'},
	{941, 1477, '#'},
	{941, 1633, 'D'}
};


void vpbdial_change_dtmf_length(int ontime_ms, int offtime_ms)
{ //{{{
	dtmf_tone.ton  = ontime_ms;
	dtmf_tone.toff = offtime_ms;
} //}}}

void vpbdial_open()
{ //{{{
	const int num_dtmf = sizeof(dtmf_digits)/sizeof(*dtmf_digits);
	tones.resize( num_dtmf );
	for(int i = 0; i < num_dtmf; ++i)
	{
		memcpy(&tones[i].tone, &dtmf_tone, sizeof(dtmf_tone));
		tones[i].tone.freq1 = dtmf_digits[i].low;
		tones[i].tone.freq2 = dtmf_digits[i].high;
		tones[i].c          = dtmf_digits[i].c;
	}
} //}}}

void vpbdial_close()
{ //{{{
	tones.clear();
} //}}}


void vpbdial_validate(const string &dialstring)
{ //{{{
	for( string::const_iterator i = dialstring.begin(),
				    e = dialstring.end(); i != e; ++i ) {
		switch( *i )
		{
		    case '&': break;
		    case ',': break;

		    default:    // search for digit in table
			char c = toupper(*i);
			for(TONE::List::iterator t = tones.begin(),
						 n = tones.end(); t != n; ++t)
				if( t->c == c ) goto next_digit;

			throw Wobbly(VPBAPI_DIAL_INVALID_DIGIT);
		}
		next_digit:;
	}
} //}}}

static void set_tonegen_config( ToneGen::Config &config, const VPB_TONE *tone )
{ //{{{
	config.ClearTones();

	for(unsigned int tnum = 0; tone; ++tnum ) {
		const VPB_TONE &t = *tone;

		if(t.freq1) config.SetFreq(t.freq1, t.level1, 0, tnum);
		if(t.freq2) config.SetFreq(t.freq2, t.level2, 1, tnum);
		if(t.freq3) config.SetFreq(t.freq3, t.level3, 2, tnum);
		config.SetCadence(t.ton, t.toff, tnum);

		if( t.next == tone ) {
			config.SetRepeat();
			return;
		}
		tone = t.next;
	}
} //}}}

static void dial_next_digit(int handle, ToneGen &tonegen, ToneGen::Config *tone = NULL)
{ //{{{
	unsigned short	b, ch;

	maphndletodev(handle, &b, &ch);

	ToneGen::Config &config  = tone ? *tone : tonegen.GetConfig();
	string          &dialstr = config.GetUserData<string>(2);
	bool             sync    = config.GetUserData<bool>(3);
	char             c       = dialstr[0];

	dialstr.erase(0,1);

	mprintf("[%2d] dial_next_digit: Dialing digit [%c]\n", handle, c);

	switch(c)
	{
	    case '&':
	    {
		// NOTE: the V4PCI tone special cases a single freq of
		//       0Hz, with an ontime of 0ms as a hookflash 'tone'.
		//       The other implementations don't really care, but
		//       we do need to call BeginSequence for the sync
		//       functions to work correctly, even though this
		//       isn't really a tone...
		config.ClearTones();
		config.SetFreq(0);

		// pause a moment to give existing queued audio time to clear.
		usleep( 160 * 1000 );

		// do not use objtrack_handle_to_id for hook
		// flash, because it uses channel instead of
		// id (see comments in declarations of data
		// structures) - mmickan 25 Aug 2006
		uint16_t hook_mess[PC_LCODEC_BREAK] = { PC_LCODEC_BREAK,
							PC_CODEC_BREAK,
							ch,
							get_country(b,ch)->flash_time
						      };
		if(vpb_c->vpbreg(b)->model == VPB_V4PCI) {
			// OpenLine flash time is measured in lsf superframes
			hook_mess[3] *= (FS/1000);
			hook_mess[3] /= vpb_c->vpbreg(b)->lsf;
		}
		vpb_c->PutMessageVPB(b, hook_mess);

		break;
	    }
	    case ',':
		config.ClearTones();
		config.SetFreq(0);
		config.SetCadence(1000);
		break;

	    default:
		// search for digit in table
		c = toupper(c);
		for(TONE::List::iterator i = tones.begin(),
					 e = tones.end(); i != e; ++i)
		{
			if(i->c == c) {
				set_tonegen_config( config, &i->tone );
				break;
			}
		}
	}

	if(tone) tonegen.BeginSequence( tone, sync );
	else     tonegen.ContinueSequence();
} //}}}

static void vpbdial_complete_callback( ToneGen &tonegen )
{ //{{{
	const ToneGen::Config &config = tonegen.GetConfig();
	int handle = objtrack_id_to_handle(TONEOBJ,
					   config.GetUserData<int>(0),
					   config.GetUserData<unsigned short>(1));
	string &dialstr = config.GetUserData<string>(2);

	//mprintf("[%2d] vpbdial_complete_callback: remaining digits: '%s'\n",
	//	handle, dialstr.c_str());

	if( ! dialstr.empty() ) {
		dial_next_digit(handle, tonegen);
		return;
	}
	if( ! config.GetUserData<bool>(3) ) {
		VPB_EVENT e = { VPB_DIALEND, handle, 0, 0, 0, NULL };
		putevt(&e);
	}
	tonegen.EndSequence();
} //}}}

static void dial_string(int handle, const string &dialstring, bool sync)
{ //{{{
	unsigned short  b, ch;

	maphndletodev(handle, &b, &ch);

	if(dialstring.empty()) {
		if( ! sync ) {
			VPB_EVENT  e;
			e.type   = VPB_DIALEND;
			e.handle = handle;
			e.data   = 0;
			putevt(&e);
		}
		return;
	}

      #ifdef _OPENPRI
	VPBREG  *pvpbreg = vpb_c->vpbreg(b);
	if(pvpbreg->model == VPB_PRI) { //{{{
		//mprintf("dial_string: Got Pri card, checking channel state\n");
		OpenPri *PriDsp  = (OpenPri*)pvpbreg->hostdsp;
		int	ch_state = PriDsp->chan_state(ch);
		//mprintf("dial_string: Channel [%d] is in state [%d]\n",ch,ch_state);
		switch( ch_state )
		{
		    case 10: break;

		    case 0:
		    {
			// Channel is Idle, we need to dial out 
			char cid[21] = { 0 };	//XXX ?
			//XXX Work around for the appalling lack of const
			//    correctness that libpri is riddled with.
			char number[128];
			strncpy(number, dialstring.c_str(), sizeof(number));
			number[sizeof(number)-1] = '\0';
			vpb_isdn_call(handle, number, cid, 0, 0);
		    }
		    // fall through to:
		    default:
			// Channel is not Connected
			if( ! sync ) {
				VPB_EVENT  e;
				e.type   = VPB_DIALEND;
				e.handle = handle;
				e.data   = 0;
				putevt(&e);
			}
			return;
		}
	} //}}}
      #endif

	vpbdial_validate(dialstring);

	//XXX We can leak a Config object here if this is interrupted before
	//    it is passed to Start().  Right now, that's not likely to happen
	//    during normal operation, but bear it in mind if that changes...

	ToneGen         &tonegen = *vpb_c->vpbreg(b)->toneg[ch];
	ToneGen::Config *tone    = new ToneGen::Config;

	tone->SetCompletionCallback(vpbdial_complete_callback);
	tone->SetUserData(0, objtrack_handle_to_id(TONEOBJ,handle));
	tone->SetUserData(1, b);
	tone->SetUserData(2, dialstring);
	tone->SetUserData(3, sync);

	//mprintf("[%2d] dial_string: begin\n", handle);

	dial_next_digit(handle, tonegen, tone);
} //}}}

int WINAPI vpb_dial_async( int handle, const string &dialstring )
{ //{{{
	try {
		ValidHandleCheck(handle);
		dial_string(handle, dialstring, false);
	}
	catch(const Wobbly &w) {
		return RunTimeError(w,"vpb_dial_async");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_dial_sync( int handle, const string &dialstring )
{ //{{{
	try {
		ValidHandleCheck(handle);
		dial_string(handle, dialstring, true);
	}
	catch(const Wobbly &w) {
		return RunTimeError(w,"vpb_dial_sync");
	}
	return VPB_OK;
} //}}}


static inline void check_freq(unsigned short f)
{ //{{{
	if(f > 4000) throw Wobbly(VPBAPI_DIAL_INVALID_FREQUENCY);
} //}}}

static inline void check_level(float l)
{ //{{{
	if(l > 0.0) throw Wobbly(VPBAPI_DIAL_INVALID_LEVEL);
} //}}}


int WINAPI vpb_settone( char ident, const VPB_TONE *t )
{ //{{{
	try {
		// validate tone parameters
		check_freq(t->freq1);
		check_freq(t->freq2);
		check_freq(t->freq3);
		check_level(t->level1);
		check_level(t->level2);
		check_level(t->level3);

		TONE::List::iterator i = tones.begin(),
				     e = tones.end();
		ident = toupper(ident);

		// determine if a tone with this identifier already exists
		for(; i != e; ++i) if(i->c == ident) break;

		// if ident doesn't exist already, make new tone
		if( i == e ) {
			i    = tones.insert(e,TONE());
			i->c = ident;
		}
		memcpy(&i->tone, t, sizeof(VPB_TONE));
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_settone");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_gettone( char ident, VPB_TONE *t )
{ //{{{
	try {
		TONE::List::iterator i = tones.begin(),
				     e = tones.end();
		ident = toupper(ident);

		// determine if a tone with this identifier exists
		for(; i != e; ++i) if(i->c == ident) break;

		if( i == e ) throw Wobbly(VPBAPI_DIAL_NO_TONE);

		memcpy(t, &i->tone, sizeof(VPB_TONE));
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_gettone");
	}
	return VPB_OK;
} //}}}


void vpbdial_playtone(int handle, const VPB_TONE &t,
		      ToneGen::CompletionCallback callback)
{ //{{{
	unsigned short  b,ch;
	maphndletodev(handle, &b, &ch);

	//XXX We can leak a Config object here if this is interrupted before
	//    it is passed to Start().  Right now, that's not likely to happen
	//    during normal operation, but bear it in mind if that changes...

	ToneGen         &tonegen = *vpb_c->vpbreg(b)->toneg[ch];
	ToneGen::Config *tone    = new ToneGen::Config;

	set_tonegen_config( *tone, &t );
	tone->SetCompletionCallback(callback);
	tone->SetUserData(0, objtrack_handle_to_id(TONEOBJ,handle));
	tone->SetUserData(1, b);

	//mprintf("[%d/%d] vpbdial_playtone: begin\n", b,ch);

	// The callback is only defined in async mode.
	tonegen.Start( tone, callback == NULL );
} //}}}


int WINAPI vpb_playtone_state(int handle)
{ //{{{
	try {
		ValidHandleCheck(handle);

		unsigned short  b,ch;

		maphndletodev(handle, &b, &ch);
		return vpb_c->vpbreg(b)->toneg[ch]->GetState() == ToneGen::IDLE
			? 0 : 1;
	}
	catch(const Wobbly &w){
		throw VpbException("vpb_playtone_state: bad handle");
	}
} //}}}


static void tone_complete_callback( ToneGen &tonegen )
{ //{{{
	const ToneGen::Config &config = tonegen.GetConfig();
	int handle = objtrack_id_to_handle(TONEOBJ,
					   config.GetUserData<int>(0),
					   config.GetUserData<unsigned short>(1));
	VPB_EVENT e = { VPB_DIALEND, handle, 0, 0, 0, NULL };
	putevt(&e);
} //}}}

int WINAPI vpb_playtone_async(int handle, const VPB_TONE &vpb_tone)
{ //{{{
	try {
		ValidHandleCheck(handle);
		vpbdial_playtone(handle, vpb_tone, tone_complete_callback);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_async");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_playtone_async(int handle, const VPB_TONE *vpb_tone)
{ //{{{
	try {
		ValidHandleCheck(handle);
		vpbdial_playtone(handle, *vpb_tone, tone_complete_callback);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_async");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_playtone_async(int handle, VPB_TONE_ID tone_id)
{ //{{{
	try {
		const Country *country_data = vpb_get_port_country(handle);
		vpbdial_playtone(handle,
				 country_data->tone_gen[tone_id],
				 tone_complete_callback);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_async(id)");
	}
	return VPB_OK;
} //}}}


int WINAPI vpb_playtone_sync(int handle, const VPB_TONE &vpb_tone)
{ //{{{
	try {
		ValidHandleCheck(handle);
		vpbdial_playtone(handle, vpb_tone);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_sync");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_playtone_sync(int handle, const VPB_TONE *vpb_tone)
{ //{{{
	try {
		ValidHandleCheck(handle);
		vpbdial_playtone(handle, *vpb_tone);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_sync");
	}
	return VPB_OK;
} //}}}

int WINAPI vpb_playtone_sync(int handle, VPB_TONE_ID tone_id)
{ //{{{
	try {
		const Country *country_data = vpb_get_port_country(handle);
		vpbdial_playtone(handle, country_data->tone_gen[tone_id]);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_playtone_sync(id)");
	}
	return VPB_OK;
} //}}}


int WINAPI vpb_tone_terminate(int handle)
{ //{{{
	try {
		ValidHandleCheck(handle);

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

		ToneGen &tonegen = *vpb_c->vpbreg(b)->toneg[ch];
		tonegen.Stop();
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_tone_terminate");
	}
	return VPB_OK;
} //}}}

