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

	Test program to measure the Echo Return Loss (ERL) of a VPB port.
	The idea is to minimise the ERL (maximum -ve number).

	Instructions:
	1. ./measerl --card N --port P
	2. Dial the line connected to the port being tested for FXO ports.
        4. For an FXS port, also use the --nohook option.  Perform the test
           with the handet already off hook and the microphone muted.
	3. A series of tones will be heard in the phone dialling the port,
	   avoid speaking into the phone to prevent corrupting the results.
	5. The program will print the results.


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

#include <cassert>
#include <cstdlib>
#include <cstring>

#define OFF_HK_DELAY    1000    // number of ms to pause after off-hook


static struct {
	int bal_min,
	    bal_max,
	    reg;
} scan_lut[] = { { 192, 200, 0x32 },
		 {   0, 255, 0x3a },
		 {   0, 255, 0x42 } };

static short sil_buf[N * MNSAM];


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;
} //}}}

int main(int argc, char *argv[])
{
	int	bd = 0, ch = 0, dport,
		h, hdial = -1,
		bal  = -1,
		bal2 = -1,
		bal3 = -1,
		bal_min = 0,
		bal_max = 255,
		bal_step = 1,
		best_bal1 = 0,
		best_bal3 = 0,
		on_hook, arg,
		scan = 0;
	float	av_erl, max_erl, min_erl;

	if (arg_exists(argc, argv, "-h") || arg_exists(argc, argv, "--help")) {
		printf("--bal1 [Hex]        Set codec hybrid balance 1 reg\n");
		printf("--bal2 [Hex]        Set codec hybrid balance 2 reg\n");
		printf("--bal3 [Hex]        Set codec hybrid balance 3 reg\n");
		printf("--card [0...]       VPB card\n");
		printf("--port [0...]       VPB port\n");
		printf("--pg [-12.0..12.0]  Set codec play gain\n");
		printf("--rg [-12.0..12.0]  Set codec record gain\n");
		printf("--dial dport number Dial out another port\n");
		printf("--offhook           Take off hook immediately\n");
		printf("--scan [1..3] TextFile.txt start[0..ff] end[0..ff] step[e.g. 1]\n");
		printf("--autoscan          Auto scan codec balance values\n");
		printf("--handset           proto handset mode\n");
	      //printf("--loop              Enable software loopback\n");
		printf("--ec                Enable echo canceller\n");
	      //printf("--hostecho          Enable Host Echo Canceller\n");
		printf("--nohook            Use for station ports\n");

		exit(0);
	}
	if ((arg = arg_exists(argc, argv, "--card")) != 0) {
		bd = atoi(argv[arg+1]);
		assert(bd >= 0);
	}
	if ((arg = arg_exists(argc, argv, "--port")) != 0) {
		ch = atoi(argv[arg+1]);
		assert(ch >= 0);
	}

	try {
		//verbose(1);	
		printf("bd = %d ch = %d\n",bd,ch);
		h = vpb_open(bd,ch);

		// option to turn on loop back
		//if((arg = arg_exists(argc, argv, "--loop")) != 0) vpb_loopback_on(h);

		// option to turn on host echo
		//if((arg = arg_exists(argc, argv, "--hostecho")) != 0) vpb_hostecho_on(h);

		// option to set codec balance reg 1
		if ((arg = arg_exists(argc, argv, "--bal1")) != 0) {
			bal = strtol(argv[arg+1], NULL, 16);
			assert((bal >= 0) && (bal <= 255));
			vpb_set_codec_reg(h, 0x32, bal);
			printf("bal1 = 0x%02x (%d)\n", bal, bal);
		}

		// option to set codec balance reg 2
		if ((arg = arg_exists(argc, argv, "--bal2")) != 0) {
			bal2 = strtol(argv[arg+1], NULL, 16);
			assert((bal2 >= 0) && (bal2 <= 255));
			vpb_set_codec_reg(h, 0x3a, bal2);
			printf("bal2 = 0x%02x (%d)\n", bal2, bal2);
		}

		// option to set codec balance reg 3
		if ((arg = arg_exists(argc, argv, "--bal3")) != 0) {
			bal3 = strtol(argv[arg+1], NULL, 16);
			assert((bal3 >= 0) && (bal3 <= 255));

			if (!(bal & 32)) {
				printf("bit 5 of bal1 needs to be set to use bal3....\n");
				bal |= 32;
				vpb_set_codec_reg(h, 0x32, bal);
				printf("bal1 now set to 0x%02x\n", bal);
			}
			vpb_set_codec_reg(h, 0x42, bal3);
			printf("bal3 = 0x%02x (%d)\n", bal3, bal3);
		}

		// option to use OpenLine4 handset proto mod
		if ((arg = arg_exists(argc, argv, "--handset")) != 0) {
			// special mod for Simple Simon OpenLine4 hardware
			// which were specially modifed V16 OpenLine4's 
			// (V4PCIs)

			char    s[VPB_MAX_STR];

			vpb_get_model(h,s);
			if (strcmp(s,"VPB4") == 0) {
				printf("manope\n");
				// audio sw but not loop
				vpb_set_codec_reg(h, 0x12, 0x7c);
				vpb_set_codec_reg(h, 0x0a, 0x20);
			}
		}

		// option to set codec record gain
		if ((arg = arg_exists(argc, argv, "--rg")) != 0) {
			float recg = atof(argv[arg+1]);
			assert((recg >= -12.0) && (recg <= 12.0));
			vpb_record_set_hw_gain(h, recg);
			printf("record hw gain set to %3.2f dB\n", recg);
		}

		// option to set codec play gain
		if ((arg = arg_exists(argc, argv, "--pg")) != 0) {
			float playg = atof(argv[arg+1]);
			assert((playg >= -12.0) && (playg <= 12.0));
			vpb_play_set_hw_gain(h, playg);
			printf("play hw gain set to %3.2f dB\n", playg);
		}

		// option to use echo canceller (off by default)
		if (!arg_exists(argc, argv, "--ec")) {
			vpb_echo_canc_disable();
		}

		// option to dial out of another port
		if ((arg = arg_exists(argc, argv, "--dial")) != 0) {
			dport = atoi(argv[arg+1]);
			assert((dport >= 1) && (dport <= 4));
			//assert(dport != port);
			hdial = vpb_open(0, dport);
			vpb_sethook_sync(hdial, VPB_OFFHOOK);
			printf("OK, dialing %s down port %d\n", argv[arg+2],
			       dport);
			vpb_dial_sync(hdial, argv[arg+2]);
		}

		// option to enter scan mode 
		if( (arg = arg_exists(argc, argv, "--scan")) != 0 ) {
			scan = atoi(argv[arg+1]);
			assert((scan == 1) || (scan == 2) || (scan == 3));
			fscan = fopen(argv[arg+2],"wt");
			assert(fscan != NULL);

			bal_min = strtol(argv[arg+3], NULL, 16);
			assert((bal_min >= 0) && (bal_min <= 255));
			bal_max = strtol(argv[arg+4], NULL, 16);
			assert((bal_max >= 0) && (bal_max <= 255));
			bal_step = strtol(argv[arg+5], NULL, 16);
			assert((bal_step >= 0) && (bal_step <= 255));
		}

		if ((arg = arg_exists(argc, argv, "--dial")) != 0) {
			printf("Waiting for rings on port %d\n",ch);
		}
		else {
			if (!arg_exists(argc, argv, "--offhook")) {
				printf("Please dial line "
				       "attached to port %d\n",ch);
			}
		}

		// see if we should take off hook immediately
		if (arg_exists(argc, argv, "--offhook")) {
			on_hook = 0;
			vpb_sethook_sync(h, VPB_OFFHOOK);
			vpb_sleep(300);
		}
		else {
			// see if we should proceed without changing hook switch
			if(arg_exists(argc, argv, "--nohook")) on_hook = 0;
			else                                   on_hook = 1;
		}

		if(on_hook) printf("Waiting for a ring...\n");
		VPB_EVENT   e;
		while(on_hook && vpb_get_event_sync(&e,0) == VPB_OK)
		{
			char    s[VPB_MAX_STR];

			vpb_translate_event(&e, s);
			printf("%s",s);

			// take channels off-hook on ring
			if (e.type == VPB_RING_OFF) {
				vpb_sethook_sync(e.handle,VPB_OFFHOOK);
				vpb_sleep(OFF_HK_DELAY);
				on_hook = 0;
			}
		}

//		vpb_dial_sync(e.handle,"1232123,1232121");

		// OK, this is where we actually start to do things
		vpb_play_buf_sync(h, (char*)sil_buf, sizeof(sil_buf));

		if (scan) fprintf(fscan, "scan = %d bal = 0x%x bal2 = 0x%x"
				  "bal3 = 0x%x\n", scan, bal, bal2, bal3);
		printf(" bal\t300\t500\t1000\t2000\t3000"
		       "\t\tavg\tmax\tmin\tdiff\tbest_b\tbest_max");
		if (scan) fprintf(fscan, " bal\t300\t500\t1000\t2000\t3000"
				  "\t\tavg\tmax\tmin\tdiff\tbest_b\tbest_max");
		int best_bal, b, reg;
		float best_max_erl;

		if (scan || arg_exists(argc, argv, "--autoscan")) {

			if (arg_exists(argc, argv, "--autoscan")) {
				fscan = fopen("autoscan.txt","wt");
				assert(fscan != NULL);

				// first scan bal1
				scan = 1;
				bal_min = 192;
				bal_max = 216;
				bal_step = 1;

			}

			if ((scan==3) && !(bal & 32)) {
				printf("\nbit 5 of bal1 needs to be set to use bal3....\n");
				bal |= 32;
				vpb_set_codec_reg(h, 0x32, bal);
				printf("bal1 now set to 0x%02x\n", bal);
			}
			reg = scan_lut[scan-1].reg;
			best_max_erl = 100.0;
			best_bal = 0;

			for(b=bal_min; (b<=bal_max) && !kbhit(); b+=bal_step) {
				printf("\n0x%02x ",b);
				if (scan) fprintf(fscan, "\n0x%0x ",b);
				vpb_set_codec_reg(h, reg, b);
				scan_erl(h, &av_erl, &max_erl, &min_erl);
				if (max_erl < best_max_erl) {
					best_bal = b;
					best_max_erl = max_erl;
				}
				printf("   0x%02x %9.2f ",best_bal,best_max_erl);
				if (scan) fprintf(fscan, ")   x%02x %9.2f ",
						  best_bal, best_max_erl);

			}

			if (arg_exists(argc, argv, "--autoscan")) {
				// set bal12 to best value & enable bal3
				best_bal1 = best_bal |= 32;
				vpb_set_codec_reg(h, 0x32, best_bal1);
				printf("\n\nbal1 = 0x%02x (%d)\n", best_bal1, best_bal1);

				// now scan bal3
				scan = 3;
				bal_min = 0;
				bal_max = 255;
				bal_step = 1;

				reg = scan_lut[scan-1].reg;
				best_max_erl = 100.0;
				best_bal = 0;

				printf("Scanning balance register 3...........\n");

				for(b=bal_min; (b<=bal_max) && !kbhit(); b+=bal_step) {
					printf("\n0x%02x ",b);
					if (scan) fprintf(fscan, "\n0x%02x ",b);
					vpb_set_codec_reg(h, reg, b);
					scan_erl(h, &av_erl, &max_erl, &min_erl);
					if (max_erl < best_max_erl) {
						best_bal = b;
						best_max_erl = max_erl;
					}
					printf("\t0x%02x %9.2f ",best_bal,best_max_erl);
					if (scan) fprintf(fscan, "\tx%02x %9.2f ",
							  best_bal, best_max_erl);

				}
				best_bal3 = best_bal;
			}

		}
		else {
			// single measurement
			printf("\n ---");
			scan_erl(h, &av_erl, &max_erl, &min_erl);
		}

		if (scan) fclose(fscan);
		printf("\n");

		//if((arg = arg_exists(argc, argv, "--loop")) != 0) vpb_loopback_off(h);

		//if((arg = arg_exists(argc, argv, "--hostecho")) != 0) vpb_hostecho_off(h);

		if(vpb_get_port_type(h) == VPB_FXO) vpb_sethook_sync(h, VPB_ONHOOK);
		vpb_close(h);

		if( hdial >= 0 ) {
			vpb_sethook_sync(hdial, VPB_ONHOOK);
			vpb_close(hdial);
		}

		if (arg_exists(argc, argv, "--autoscan")) {
			printf("Autoscan run finished!\n\n");
			printf("set bal1 = 0x%02x\n", best_bal1);
			printf("set bal3 = 0x%02x\n", best_bal3);
			printf("\n");
			printf("e.g. using environment variables:\n");
			printf("export VPB_BAL1 = 0x%02x\n",best_bal1);
			printf("export VPB_BAL3 = 0x%02x\n",best_bal3);
			printf("\n");
			printf("Or using C code in your program:\n");
			printf("vpb_set_codec_reg(h, 0x32, 0x%02x);\n", best_bal1);
			printf("vpb_set_codec_reg(h, 0x42, 0x%02x);\n", best_bal3);
			printf("\n");
		}
	}
	catch (const std::exception &e) {
		fprintf(stderr, "%s: uncaught exception: %s\n", argv[0],
								e.what());
	}

	return 0;
}


