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

    This program may be used to generate and record tones using the
    programmable tone generator.


         Voicetronix Voice Processing Board (VPB) Software

         Copyright (C) 2007-2008, Ron Lee, 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 version 2.1 as published by the Free Software Foundation.

         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 "vpbapi.h"
#include "kbhit.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>

using std::string;

static pthread_attr_t   detached_thread;

static VPB_TONE tone = {0, 0, 0, 0.0, 0.0, 0.0, 0, 0, NULL};
static string   dialstr;
static int      toneid   = -1;
static bool     havetone = false;
static bool     havesync = false;
static bool     loop     = false;
static bool     contest  = false;
static bool     finished = false;


static int arg_exists(int argc, char *argv[], const char *arg)
{ //{{{
	for(int i = 0; i < argc; ++i)
		if(strcmp(argv[i],arg) == 0) return i;

	return 0;
} //}}}

static void usage(const char *argv0)
{ //{{{
	printf("usage: %s [options]\n\n", argv0);
	printf("  --card num         Play tone to card num.  Default is 0\n");
	printf("  --port num         Play tone to port num.  Default is 0\n");
	printf("  --toneid num       Play a predefined (locale specific) tone\n");
	printf("  --tone n freq mag  Specify tone component n.\n");
	printf("  --ontime ms        Specify the tone 'on' time.\n");
	printf("  --offtime ms       Specify the tone 'off' time.\n");
	printf("  --dial digits      Dial the specified dtmf digits.\n");
	printf("  --sync             Play tones synchronously.\n");
	printf("  --loop             Loop playing tones continuously.\n");
	printf("  --recgain db       Set the record gain to db.\n");
	printf("  --rec outfile      Record to outfile while playing.\n");
	printf("  --contest          Do not wait for completion between tones.\n");
	printf("  --help, -?         Show this message.\n\n");
} //}}}

static void *contest_thread(void *p)
{ //{{{
	int h = (long)p;

	fprintf(stdout,"(a)sync dialling ...\n");
	vpb_dial_sync(h, dialstr);

	return NULL;
} //}}}

static void *worker_thread(void *p)
{//{{{
	int h = (long)p;

	fprintf(stdout, "Started worker thread %#lx\n", pthread_self());
	do {
		if( havesync ) {
			if( toneid >= 0 ) {
				fprintf(stdout, "sync tone %d ...\n", toneid);
				vpb_playtone_sync(h, (VPB_TONE_ID)toneid);
			}
			if( ! dialstr.empty() ) {
				if( contest ) {
					pthread_t   contest_id;
					if( pthread_create(&contest_id,
							   &detached_thread,
							   contest_thread,
							   (void*)(intptr_t)h) )
					{
						fprintf(stderr,
							"Failed to create contest thread\n");
					}

				} else {
					fprintf(stdout,"sync dialling ...\n");
					vpb_dial_sync(h, dialstr);
				}
			}
			if( havetone ) {
				fprintf(stdout, "sync playing ...\n");
				vpb_playtone_sync(h, &tone);
			}
		} else {
			if( toneid >= 0 ) {
				fprintf(stdout, "async tone %d ...\n", toneid);
				vpb_playtone_async(h, (VPB_TONE_ID)toneid);

				if( ! contest ) {
					VPB_EVENT   e;

					fprintf(stdout, "Waiting for completion...\n");
					while(vpb_get_event_ch_sync(h,&e,0) == VPB_OK)
					{
						char    s[VPB_MAX_STR];

						vpb_translate_event(&e, s);
						fprintf(stdout, "%s", s);

						if(e.type == VPB_DIALEND) break;
					}
				}
			}
			if( ! dialstr.empty() ) {
				fprintf(stdout, "async dialling ...\n");
				vpb_dial_async(h, dialstr);

				if( ! contest ) {
					VPB_EVENT   e;

					fprintf(stdout, "Waiting for completion...\n");
					while(vpb_get_event_ch_sync(h,&e,0) == VPB_OK)
					{
						char    s[VPB_MAX_STR];

						vpb_translate_event(&e, s);
						fprintf(stdout, "%s", s);

						if(e.type == VPB_DIALEND) break;
					}
				}
			}
			if( havetone ) {
				fprintf(stdout, "async playing ...\n");
				vpb_playtone_async(h, &tone);

				if( ! contest ) {
					VPB_EVENT   e;

					fprintf(stdout, "Waiting for completion...\n");
					while(vpb_get_event_ch_sync(h,&e,0) == VPB_OK)
					{
						char    s[VPB_MAX_STR];

						vpb_translate_event(&e, s);
						fprintf(stdout, "%s", s);

						if(e.type == VPB_DIALEND) break;
					}
				}
			}
		}
	} while( loop );

	finished = true;

	return NULL;
} //}}}

int main(int argc, char *argv[])
{ //{{{
	string  recfile;
	int     cardnum  = 0;
	int     portnum  = 0;
	int     arg;

	if(argc < 2) {
		usage(argv[0]);
		exit(EXIT_FAILURE);
	}
	if( arg_exists(argc,argv,"--help") || arg_exists(argc, argv,"-?") ) {
		usage(argv[0]);
		exit(EXIT_SUCCESS);
	}
	if((arg = arg_exists(argc,argv,"--card")) != 0) cardnum = atoi(argv[arg+1]);
	if((arg = arg_exists(argc,argv,"--port")) != 0) portnum = atoi(argv[arg+1]);

	if((arg = arg_exists(argc,argv,"--toneid")) != 0) {
		toneid = atoi(argv[arg+1]);
		if( toneid < 0 || toneid >= VPB_TONE_ID_MAX ) {
			fprintf(stderr, "Invalid tone id %d\n", toneid);
			exit(EXIT_FAILURE);
		}
	}

	for(int p = 0;;) { //{{{
		if((arg = arg_exists(argc - p, argv + p, "--tone")) != 0)
		{
			havetone = true;
			++p += arg;

			fprintf(stdout, "Using tone %s: %sHz, %sdB\n",
			       argv[p], argv[p+1],argv[p+2]);

			int n = atoi(argv[p]);
			if( n > 2 ) {
				fprintf(stderr, "Invalid tone number %d\n", n);
				exit(EXIT_FAILURE);
			}

			int freq = atoi(argv[p+1]);
			if( freq < 1 || freq > 4000 ) {
				fprintf(stderr, "Invalid tone frequency %d\n", freq);
				exit(EXIT_FAILURE);
			}

			float mag = atof(argv[p+2]);
			if( mag < -20 || mag > 0 ) {
				fprintf(stderr, "Invalid tone magnitude %f\n", mag);
				exit(EXIT_FAILURE);
			}

			switch(n)
			{
			    case 0:
				tone.freq1 = freq;
				tone.level1 = mag;
				break;

			    case 1:
				tone.freq2 = freq;
				tone.level2 = mag;
				break;

			    case 2:
				tone.freq3 = freq;
				tone.level3 = mag;
				break;
			}
		}
		else	break;
	} //}}}
	if((arg = arg_exists(argc,argv,"--ontime")) != 0)  tone.ton  = atoi(argv[arg+1]);
	if((arg = arg_exists(argc,argv,"--offtime")) != 0) tone.toff = atoi(argv[arg+1]);
	if((arg = arg_exists(argc, argv, "--dial")) != 0)  dialstr   = argv[arg+1];
	if((arg = arg_exists(argc, argv, "--rec")) != 0)   recfile   = argv[arg+1];
	if(arg_exists(argc,argv,"--sync"))		   havesync  = true;
	if(arg_exists(argc,argv,"--loop"))		   loop      = true;
	if(arg_exists(argc,argv,"--contest"))		   contest   = true;


	pthread_t   worker_id;
	int         h         = vpb_open(cardnum, portnum);
	int         port_type = vpb_get_port_type(h);

	// Wait a moment for the hardware init events to complete.
	sleep(1);

	if((arg = arg_exists(argc, argv, "--recgain")) != 0)
		vpb_record_set_gain(h, atof(argv[arg+1]));

	if(port_type == VPB_FXO)
		vpb_sethook_sync(h, VPB_OFFHOOK);

	if( ! recfile.empty() ) {
		vpb_record_file_async(h, recfile, VPB_LINEAR);
		fprintf(stdout, "recording to %s\n",  recfile.c_str());
	}


	pthread_attr_init(&detached_thread);
	pthread_attr_setdetachstate(&detached_thread, PTHREAD_CREATE_DETACHED);

	if( pthread_create(&worker_id, NULL, worker_thread, (void*)(intptr_t)h) )
	{
		fprintf(stderr, "Failed to create worker thread.  Aborting.\n");
		return EXIT_FAILURE;
	}


	// now just wait for a termination event
	if(loop) getkey();
	else while( ! finished && ! kbhit() ) usleep(500);
	fprintf(stdout, "shutting down...\n");


	pthread_cancel( worker_id );
	pthread_join( worker_id, NULL );
	pthread_attr_destroy( &detached_thread );

	vpb_tone_terminate(h);
	usleep( 200 * 1000 );

	if(port_type == VPB_FXO)
		vpb_sethook_sync(h, VPB_ONHOOK);

	//fprintf(stdout, "stopping recording...\n");
	//if( ! recfile.empty() )
	//	vpb_record_terminate_sync(h);

	fprintf(stdout, "done.\n\n");
	vpb_close(h);

	return EXIT_SUCCESS;
} //}}}

