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

	FILE....: DIGITS.CPP
	TYPE....: C++ Module
	AUTHOR..: David Rowe
	DATE....: 28/7/98
	AUTHOR..: Ron Lee
	DATE....: 23/12/06

	Digit collection module for VPB API.


         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 "apifunc.h"
#include "timer.h"
#include "mess.h"
#include "scopedmutex.h"

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

using std::string;
using std::list;
using std::vector;


typedef list<char>  DigBuf;

struct DigitCollect
{ //{{{
	typedef vector<DigitCollect>    List;

	enum State
	{
	    IDLE,
	    SYNC,
	    ASYNC
	};

	DigBuf		dbuf;			// Buffer of incoming digits
	std::string	term_digits;            // list of digits that terminate collection
	unsigned short	max_digits;		// max number of digits to collect
	unsigned long	digit_time_out;		// max total time for digit collection (ms)
	unsigned long	inter_digit_time_out;	// max time between digits (ms)
	unsigned short	count;			// number of digits so far
	State		state;			// Digit collection state
	char		*buf;			// ptr to desination buffer;
	Timer		inter_digit_timer;	// inter digit timer
	Timer		digit_timer;		// total timer

	pthread_mutex_t mutex;

	DigitCollect()
	    : max_digits( 1 )
	    , digit_time_out( 60000 )
	    , inter_digit_time_out( 5000 )
	    , count( 0 )
	    , state( IDLE )
	    , buf( NULL )
	{
		pthread_mutex_init(&mutex, NULL);
	}

	~DigitCollect()
	{
		pthread_mutex_destroy(&mutex);
	}

}; //}}}

static DigitCollect::List   digcollect;


static const char *term_str[] = {
	"Terminating Digit",
	"Maximum Digits",
	"Time Out",
	"Inter-Digit Time Out",
	"Destination Buffer Full",
	"Invalid VPB_DIGIT event"
};


void digits_open(unsigned short numch)
{ //{{{
	digcollect.resize(numch);
} //}}}

void digits_close()
{ //{{{
	digcollect.clear();
} //}}}

void validate_digits(const char *digits)
{ //{{{
	static const char valid_dig[] = "0123456789*#ABCD";

	for(size_t j = 0, len = strlen(digits); j < len; ++j)
	{
		char c = toupper(digits[j]);
		for(size_t i = 0; i < sizeof(valid_dig) - 1; ++i)
			if (valid_dig[i] == c) goto next;

		throw VpbException("validate_digits: invalid digit '%c' in string '%s'",
				   c, digits);
	    next:;
	}
} //}}}

// Copy collected digits into the user buffer and determine if the termination
// conditions have been met.  Must be called with the DigitCollect mutex locked.
static void check_buffer(int handle)
{ //{{{
	DigitCollect    &d    = digcollect[handle];

	while( ! d.dbuf.empty() )
	{
		char digit = d.dbuf.front();

		d.dbuf.pop_front();
		d.buf[d.count++] = digit;

		bool maxdigits = (d.count == d.max_digits);

		if( maxdigits || d.term_digits.find(digit) != string::npos )
		{
			VPB_EVENT   e;

			d.buf[d.count] = '\0';

			e.type   = VPB_DIGIT;
			e.handle = handle;
			e.data   = maxdigits ? VPB_DIGIT_MAX : VPB_DIGIT_TERM;
			e.data1  = d.count;
			putevt(&e, VPB_MDIGIT);

			d.state  = DigitCollect::IDLE;
			break;
		}
		d.inter_digit_timer.start();
	}
} //}}}


int WINAPI vpb_get_digits_async(int handle, VPB_DIGITS *newdig, char *buf)
{ //{{{
	try {
		ValidHandleCheck(handle);
		validate_digits(newdig->term_digits);

		DigitCollect    &d = digcollect[handle];

		ScopedMutex lock( &d.mutex );

		if( d.state != DigitCollect::IDLE ) {
			mprintf("[%d] vpb_get_digits_async: WARNING already collecting (%d)",
				handle, d.state);
		}

		d.term_digits          = newdig->term_digits;
		d.max_digits           = newdig->max_digits;
		d.digit_time_out       = newdig->digit_time_out;
		d.inter_digit_time_out = newdig->inter_digit_time_out;
		d.count                = 0;
		d.state                = DigitCollect::ASYNC;
		d.buf                  = buf;
		d.inter_digit_timer.stop();
		d.digit_timer.start();
 		check_buffer(handle);
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_get_digits_async");
	}
	return VPB_OK;
} //}}}

CollectEndReason WINAPI vpb_get_digits_sync(int handle, VPB_DIGITS *newdig, char *buf)
{ //{{{
	try {
		ValidHandleCheck(handle);
		validate_digits(newdig->term_digits);
	}
	catch(const Wobbly&) {
		throw VpbException("vpb_get_digits_sync: invalid argument");
	}

	DigitCollect    &d = digcollect[handle];

	pthread_mutex_lock( &d.mutex );

	switch( d.state )
	{
	    case DigitCollect::IDLE:
		break;

	    case DigitCollect::SYNC:
		pthread_mutex_unlock( &d.mutex );
		throw VpbException("vpb_get_digits_sync: sync collection already active");

	    case DigitCollect::ASYNC:
		mprintf("[%d] vpb_get_digits_sync: WARNING async collection aborted",
			handle);
	}

	d.term_digits          = newdig->term_digits;
	d.max_digits           = newdig->max_digits;
	d.digit_time_out       = newdig->digit_time_out;
	d.inter_digit_time_out = newdig->inter_digit_time_out;
	d.count                = 0;
	d.state                = DigitCollect::SYNC;
	d.buf                  = buf;
	d.inter_digit_timer.stop();
	d.digit_timer.start();

	for(;;) {
		if( d.state != DigitCollect::SYNC ) {
			pthread_mutex_unlock( &d.mutex );
			throw VpbException("vpb_get_digits_sync: sync collection interrupted");
		}

		if( ! d.dbuf.empty() ) {
			char digit = d.dbuf.front();

			d.dbuf.pop_front();
			d.inter_digit_timer.start();
			d.buf[d.count++] = digit;

			if( d.count == d.max_digits ) {
				d.buf[d.count] = '\0';
				d.state        = DigitCollect::IDLE;
				pthread_mutex_unlock( &d.mutex );
				return VPB_DIGIT_MAX;
			}
			if( d.term_digits.find(digit) != string::npos ) {
				d.buf[d.count] = '\0';
				d.state        = DigitCollect::IDLE;
				pthread_mutex_unlock( &d.mutex );
				return VPB_DIGIT_TERM;
			}
		}
		else {
			if( d.digit_timer.check_timeout_ms(d.digit_time_out) ) {
				d.buf[d.count] = '\0';
				d.state        = DigitCollect::IDLE;
				pthread_mutex_unlock( &d.mutex );
				return VPB_DIGIT_TIME_OUT;
			}
			if( d.inter_digit_timer.check_timeout_ms(d.inter_digit_time_out) ) {
				d.buf[d.count] = '\0';
				d.state        = DigitCollect::IDLE;
				pthread_mutex_unlock( &d.mutex );
				return VPB_DIGIT_INTER_DIGIT_TIME_OUT;
			}
			pthread_mutex_unlock( &d.mutex );
			usleep( 20 * 1000 );
			pthread_mutex_lock( &d.mutex );
		}
	}
} //}}}

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

		DigitCollect   &d = digcollect[handle];
		ScopedMutex     lock( &d.mutex );

		if( d.state != DigitCollect::IDLE )
			mprintf("[%d] WARNING: vpb_flush_digits while collector not IDLE (%d)",
				handle, d.state);

		d.dbuf.clear();
	}
	catch(const Wobbly &w){
		return RunTimeError(w,"vpb_flush_digits");
	}
	return VPB_OK;
} //}}}


void digits_new_digit(int handle, unsigned short digit)
{ //{{{
	DigitCollect    &d = digcollect[handle];

	ScopedMutex lock( &d.mutex );

	d.dbuf.push_back(digit);

	switch( d.state )
	{
	    case DigitCollect::IDLE:
		// If the user does not care to collect the digits, they could
		// in theory accumulate in dbuf indefinitely.  That's clearly
		// not what we want, so only keep the last 200 or so buffered.
		// If this really happens, we should probably add a way to
		// disable automatic buffering completely, or even just ignore
		// all digits unless collection is active, but the latter may
		// break some existing code.  OTOH it might fix a common issue
		// with people not knowing they need to flush before collection.
		if( d.dbuf.size() > 255 ) {
			mprintf("[%d] WARNING: digits_new_digit buffer overflow",
				handle);
			for(int i = 0; i < 55; ++i) d.dbuf.pop_front();
		}
		break;

	    case DigitCollect::SYNC:
		break;

	    case DigitCollect::ASYNC:
		check_buffer(handle);
	}
} //}}}

void digits_check_timers()
{ //{{{
	VPB_EVENT	e;

	for(int handle = 0, end = digcollect.size(); handle < end; ++handle)
	{
		DigitCollect    &d = digcollect[handle];

		ScopedMutex lock( &d.mutex );

		if(d.state != DigitCollect::ASYNC) continue;

		if( d.digit_timer.check_timeout_ms(d.digit_time_out) )
		{
			d.buf[d.count] = '\0';

			e.data   = VPB_DIGIT_TIME_OUT;
			e.type   = VPB_DIGIT;
			e.data1  = d.count;
			e.handle = handle;
			putevt(&e, VPB_MDIGIT);

			d.state  = DigitCollect::IDLE;
			continue;
		}
		if( d.inter_digit_timer.check_timeout_ms(d.inter_digit_time_out) )
		{
			d.buf[d.count] = '\0';

			e.data   = VPB_DIGIT_INTER_DIGIT_TIME_OUT;
			e.type   = VPB_DIGIT;
			e.data1  = d.count;
			e.handle = handle;
			putevt(&e, VPB_MDIGIT);

			d.state  = DigitCollect::IDLE;
		}
	}
} //}}}

const char *digits_term(int data)
{
	return (data > 4 || data < 0) ? term_str[5] : term_str[data];
}

