/*
 *
 * Copyright (c) 2004-2006 Endace Technology Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Technology Limited and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagchan.c 11777 2009-08-12 23:29:58Z dlim $
 *
 */

/**********************************************************************
* DESCRIPTION:	Creates shell script for configuring channels based on
*               a configuration file
*
*
* HISTORY:
*	28-05-04 DNH 1.0  Initial version
*
**********************************************************************/

/* Endace headers. */
#include "dagapi.h"
#include "dagclarg.h"
#include "dag37t_api.h"

/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused))  = "$Id: dagchan.c 11777 2009-08-12 23:29:58Z dlim $";
static const char* const kRevisionString = "$Revision: 11777 $";


#define MAX_CHARS 256           /* Max chars for input file lines */
#define DEFAULT_CFG_FILE_NAME "chan.cfg"

static char dagname_buf[DAGNAME_BUFSIZE] = "dag0";
static char dagname[DAGNAME_BUFSIZE];
static char fname_buf[DAGNAME_BUFSIZE];
static int iDeletedCount = 0;
uint32_t unsuccessful = 0;

/* Internal routines. */
static void print_version();
static void print_usage(ClArgPtr);
static uint32_t ReadChannelConfig(char *pcFile, int dagfd);


/* Commandline argument codes. */
enum
{
	CLA_DEVICE,
	CLA_HELP,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_FNAME,
	CLA_QUAL,
	CLA_RETAIN
};


int
dagchan_main(int argc, char **argv)
{
	uint32_t wChannelCount;
	int dagfd;
	int argindex;
	int code;
	int result;
	int dagstream;
	ClArgPtr clarg = NULL;
	FILE* errorfile = NULL;

	char *cfgname = DEFAULT_CFG_FILE_NAME;
	bool bRemoveChannels = true;
	bool bQualityCheck = false;

	dagutil_set_progname("dagchan");

	/* Set up default DAG device. */
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}

	/* Set up the command line options. */
	clarg = dagclarg_init(argc, (const char* const *)argv);

	dagclarg_add(clarg, "this page.", "--help", 'h', CLA_HELP);
	dagclarg_add_long_option(clarg, CLA_HELP, "--usage");
	dagclarg_add_short_option(clarg, CLA_HELP, '?');
	dagclarg_add(clarg, "increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add_string(clarg, "channel configuration file (chan.cfg).", "--fname", 'c', "filename", fname_buf, DAGNAME_BUFSIZE, CLA_FNAME);
	dagclarg_add_string(clarg, "DAG device to use.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add(clarg, "quality, check command was received by framer.", "--qual", 'q', CLA_QUAL);
	dagclarg_add(clarg, "retain, do not delete channels.", "--retain", 'r', CLA_RETAIN);

	/* Parse the command line options. */
	result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	while (1 == result)
	{
		switch (code)
		{
			case CLA_HELP:
				print_usage(clarg);
				return EXIT_SUCCESS;
				break;

			case CLA_VERSION:
				print_version();
				return EXIT_SUCCESS;
				break;

			case CLA_VERBOSE:
				dagutil_inc_verbosity();
				break;

			case CLA_FNAME:
				cfgname = fname_buf;
				break;

			case CLA_DEVICE:
				if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &dagstream))
				{
					dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
				}
				break;

			case CLA_QUAL:
				bQualityCheck = true;
				break;

			case CLA_RETAIN:
				bRemoveChannels = false;
				break;

			default:
					dagutil_error("unknown option %s\n", argv[argindex]); 
					print_usage(clarg);
					return EXIT_FAILURE;
				break;
		}
		result = dagclarg_parse(clarg, errorfile, &argindex, &code);
	}
	
	if (-1 == result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	
	
	if((dagfd = dag_open(dagname)) < 0)
	{
		fprintf(stderr, "dag_open failed\n");
		return -1;
	}

	

	if(bRemoveChannels)
	{
		dagutil_verbose_level(1, "deleting all channels\n");
		dag_delete_board_channels(dagfd);
	}
	
	wChannelCount = ReadChannelConfig(cfgname, dagfd);
	
	dagutil_verbose_level(1, "%d channels configured\n", wChannelCount);
	dagutil_verbose_level(1,"%d channels deleted\n", iDeletedCount);
	
	
	if(unsuccessful == 0)
		dagutil_verbose_level(1,"Everything is happy.\n");
	else
	{
		dagutil_warning("A channel configuration has not completed successfully.\n");
		return -1;
	}
	dagutil_verbose_level(4,"        Finished...cleaning up\n");
	
	return EXIT_SUCCESS;
}

static void
print_version(void)
{
	//printf("dagchan (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}

static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagchan - Endace DAG 3.7T and DAG 3.7T4 card channel configuration utility.\n");
	printf("Usage: dagchan [options]\n");
	dagclarg_display_usage(clarg, stdout);
	printf("    Creates a shell script for configuring channels on the DAG 3.7T and DAG 3.7T4\n" "\n");
}

/**********************************************************************
* FUNCTION:	ReadChannelConfig
* DESCRIPTION:	Read channel configuration file
* INPUTS:	
*       pcFile  - name of configuration file
*		channel - array of possible channels
* OUTPUTS:	array of channels desired
* RETURNS:	number of channels configured
**********************************************************************/
static uint32_t
ReadChannelConfig(char *pcFile, int dagfd)
{
	int lines = 0;
	int timeslots = 31;
	FILE *cfile;
	char *res;
	char rc;
	int lineno = 0;
	int ts, line;
	char mask[16];
	uint32_t tsmap;
    	uint32_t format;		/* channel format; 0=default, 1=HDLC, 2=ATM */
	int iSuccess;
	int iChanNum;
	uint32_t iIfc;
	
	uint32_t chancount = 0;
	
	char in[MAX_CHARS];         /* input line */
	int strind, tmpind;         /* Input string index */
	int saw_one = 0;
	dag_channel_t channel_type;
	int direction = DAG_DIR_RECEIVE;
	daginf_t*       daginf;

	daginf = dag_info(dagfd);
	if(daginf->device_code == PCI_DEVICE_ID_DAG3_7T4)
            lines = 4;
        else if(daginf->device_code == PCI_DEVICE_ID_DAG3_70T) 
            lines = 16;

	if ((cfile = fopen(pcFile, "r")) == NULL)
	{
		perror("Failed to open channel config file");
		return -1;
	}
	
	/* read each channel configuration and add it to the channel list */
	dagutil_verbose_level(3, "    Reading channel configuration file\n");
	while ((res = fgets(in, MAX_CHARS, cfile)) != NULL)
	{
		lineno++;
	
		/* For the channel kind, hdlc aliases to receive and amt aliases to transmit */
		if (strcmp(in, "transmit\n") == 0)
		{
			direction = DAG_DIR_TRANSMIT;
			continue;
		}
		else if (strcmp(in, "receive\n") == 0)
		{
			direction = DAG_DIR_RECEIVE;
			continue;
		}
	
		switch (in[0])
		{
			case '#':          /* Comment line */
			case '\n':         /* empty line */
				break;
	
			case 'c':          /* 8-bit channel: format line timeslot */
				if (in[1] == 'r')
				{
					rc = sscanf(in, "cr %d %d %d ", &format, &line, &ts);
					channel_type = CT_CHAN_RAW; }
				else
				{
					rc = sscanf(in, "c %d %d %d ", &format, &line, &ts);
					channel_type = CT_CHAN;
				}
				if (rc != 3)
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d)\n", pcFile, lineno, rc);
					exit(EXIT_FAILURE);
				}
	
				if ((line < 0) || (line >= lines))
				{
					fprintf(stderr, "%s:%d - invalid line id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, line, lines);
					exit(EXIT_FAILURE);
				}
	
				if ((ts < 1) || (ts > timeslots))
				{
					fprintf(stderr, "%s:%d - invalid timeslot %d. Must be in the range 1 to %d.\n",
							pcFile, lineno, ts, timeslots);
					exit(EXIT_FAILURE);
				}
	
				if ((format < 0) || (format > 7))
				{
					fprintf(stderr, "%s:%d - invalid format %d. Must be in the range 0 to 7.\n",
							pcFile, lineno, format);
					exit(EXIT_FAILURE);
				}
	
				dagutil_verbose_level(1,"c-channel %-2d: line %d ts %d\n", chancount, line, ts);
				iSuccess = dag_add_channel(dagfd, direction, (format << 16) | channel_type, line, ts, 0);
				if (iSuccess >= 0)
				{
					dagutil_verbose_level(1, "Channel created with ID %d\n", iSuccess) ;
					chancount++;
				}
				else
					unsuccessful++;

				break;
	
			case 's':          /* Subchannel: format line timeslot bitmask */
				if (in[1] == 'r')
				{
					rc = sscanf(in, "sr %d %d %d %8s", &format, &line, &ts, mask);
					channel_type = CT_SUB_RAW;
				}
				else
				{
					rc = sscanf(in, "s %d %d %d %8s", &format, &line, &ts, mask);
					channel_type = CT_SUB;
				}
				if (rc != 4)
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d)\n", pcFile, lineno, rc);
					exit(EXIT_FAILURE);
				}
	
				if ((line < 0) || (line >= lines))
				{
					fprintf(stderr, "%s:%d - invalid line id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, line, lines);
					exit(EXIT_FAILURE);
				}
	
				if ((ts < 1) || (ts > timeslots))
				{
					fprintf(stderr, "%s:%d - invalid timeslot %d. Must be in the range 1 to %d.\n",
							pcFile, lineno, ts, timeslots);
					exit(EXIT_FAILURE);
				}
	
				if ((format < 0) || (format > 7))
				{
					fprintf(stderr, "%s:%d - invalid format %d. Must be in the range 0 to 7.\n",
							pcFile, lineno, format);
					exit(EXIT_FAILURE);
				}
	
				/*
				 * convert the input bit string to a binary mask
				 */
				tsmap = 0;
				saw_one = 0;
				for (strind = 0; strind < 8; strind++)
				{
					tsmap <<= 1;
					if ((mask[strind] != '0') && (mask[strind] != '1'))
					{
						fprintf(stderr, "%s:%d - invalid subchannel mask %s\n", pcFile, lineno,
								mask);
						exit(EXIT_FAILURE);
					}
					// tsmap |= (mask[strind] == '1');
					if (mask[strind] == '1')
					{
						tsmap |= 1;
	
						if (saw_one == 0)
						{
							/* Record offset of first bit (last bit for this loop) in channel for data mapping later */
	
							saw_one = 1;        /* first one */
						}
						else if (saw_one == 2)
						{
							/* Error - non-contiguous subchannel mask */
							fprintf(stderr,
									"%s:%d - Error channel %d line %d ts %d.  Non-contiguous subchannel %s\n",
									pcFile, lineno, chancount + 1, line, ts, mask);
							return -1;
						}
					}
					else
					{
						if (saw_one == 1)
							saw_one = 2;        /* Saw one followed by a zero */
					}
				}

				if (chancount)
				{
					dagutil_verbose_level(1,"s-channel %-2d: line %d ts %d mask 0x%02x \n",
									chancount, line, ts, tsmap);
				}
				iSuccess =
					dag_add_channel(dagfd, direction, (format << 16) | channel_type, line,
									tsmap | (ts << 16), 0);
				//dagutil_verbose_level(0,"dag_add_channel(%d, %d , %d , %d, %d, 0) --> %d;\n", 
				//	dagfd, direction, (format << 16)| channel_type, line, tsmap | (ts << 16), iSuccess);
				fflush(stdout);
				if (iSuccess >= 0)
				{
					chancount++;
				}
				else
					unsuccessful++;
				break;
	
			case 'h':          /* Hyperchannel: line timeslot_list */
				if (in[1] == 'r')
				{
					rc = sscanf(in, "hr %d %d %n", &format, &line, &strind);
					channel_type = CT_HYPER_RAW;
				}
				else
				{
					rc = sscanf(in, "h %d %d %n", &format, &line, &strind);
					channel_type = CT_HYPER;
				}
				if ((rc < 2) || (strind == 0))
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n", pcFile,
							lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
	
				if ((line < 0) || (line >= lines))
				{
					fprintf(stderr, "%s:%d - invalid line id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, line, lines);
					exit(EXIT_FAILURE);
				}
	
				if ((format < 0) || (format > 7))
				{
					fprintf(stderr, "%s:%d - invalid format %d. Must be in the range 0 to 7.\n",
							pcFile, lineno, format);
					exit(EXIT_FAILURE);
				}
	
				/*
				 * Read the timeslot list and convert it to a bit mask
				 */
				tsmap = 0;
				tmpind = 0;
				while (sscanf(&in[strind], " %d %n", &ts, &tmpind) > 0)
				{
					if ((ts < 1) || (ts > timeslots))
					{
						fprintf(stderr,
								"%s:%d - invalid timeslot %d. Must be in the range 1 to %d.\n",
								pcFile, lineno, ts, timeslots);
						exit(EXIT_FAILURE);
					}
					tsmap |= 1 << (ts);
					dagutil_verbose_level(2,"    ts: %d  map: 0x%08x (strind=%d)\n", ts, tsmap, strind);
					strind += tmpind;
				}
	
				/* Add it to the channel table */
				dagutil_verbose_level(1,"h-channel %-2d: line %d ts-map 0x%08x\n", chancount, line, tsmap);
				iSuccess =
					dag_add_channel(dagfd, direction, (format << 16) | channel_type, line, tsmap, 0);
				if (iSuccess >= 0)
				{
					chancount++;
				}
				else
					unsuccessful++;
	
				break;
	
			case 'l':          /* Line: line */
				if (in[1] == 'r')
				{
					rc = sscanf(in, "lr %d %d", &format, &line);
					channel_type = CT_HYPER_RAW;
				}
				else
				{
					rc = sscanf(in, "l %d %d", &format, &line);
					channel_type = CT_HYPER;
				}
				if (rc != 2)
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n",
							pcFile, lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
	
				if ((line < 0) || (line >= lines))
				{
					fprintf(stderr, "%s:%d - invalid line id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, line, lines);
					exit(EXIT_FAILURE);
				}
	
				if ((format < 0) || (format > 7))
				{
					fprintf(stderr, "%s:%d - invalid format %d. Must be in the range 0 to 7.\n",
							pcFile, lineno, format);
					exit(EXIT_FAILURE);
				}
	
				/* Add it to the channel table */
				/* Use all timeslots for the channel */
				dagutil_verbose_level(1,"l-channel %-2d: line %d ts-map 0x%08x\n", chancount, line,
								0xFFFFFFFF);
				iSuccess =
					dag_add_channel(dagfd, direction, (format << 16) | channel_type, line, 0xFFFFFFFF, 0);
				if (iSuccess >= 0)
				{
					chancount++;
				}
				else
					unsuccessful++;
	
				break;
	
			case 'd':          /* delete a connection */
				rc = sscanf(in, "d %d", &iChanNum);
				if (rc != 1)
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n",
							pcFile, lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
	
				if ((iChanNum < 0) || (iChanNum >= MAX_CHANNEL))
				{
					fprintf(stderr,
							"%s:%d - invalid channel id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, iChanNum, MAX_CHANNEL);
					exit(EXIT_FAILURE);
				}
	
				iSuccess = dag_delete_channel(dagfd, (uint32_t) iChanNum);
				if (iSuccess >= 0)
				{
					iDeletedCount++;
				}
				else
					unsuccessful++;
				break;
	
			case 'r':
				/* create raw connection */

				
				rc = sscanf(in, "r %d", &iChanNum);
				channel_type = CT_RAW;
	
				if (rc != 1)
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n",
							pcFile, lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
	
				if ((iChanNum < 0) || (iChanNum >= MAX_CHANNEL))
				{
					fprintf(stderr,
							"%s:%d - invalid channel id %d. Must be in the range 0 to %d.\n",
							pcFile, lineno, iChanNum, MAX_INTERFACES);
					exit(EXIT_FAILURE);
				}
	
				iSuccess = dag_add_channel(dagfd, 0, channel_type, iChanNum, 0, 0);
				if (iSuccess >= 0)
				{
					chancount++;
				}
				else
					unsuccessful++;
				break;
	
			case 'f':          /* fix HEC correctable ATM cells */
				rc = sscanf(in, "f %d", &iIfc);
				if ((1 == rc) && ((0 <= iIfc) && (MAX_CHANNEL > iIfc)))
				{
					dag_ifc_hec(dagfd, iIfc, true);
				}
				else
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n",
							pcFile, lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
				break;
	
			case 't':          /* turn on cell scrambling */
				rc = sscanf(in, "t %d", &iIfc);
				if ((1 == rc) && ((0 <= iIfc) && (MAX_CHANNEL > iIfc)))
				{
					dag_ifc_cell_scrambling(dagfd, iIfc, true);
				}
				else
				{
					fprintf(stderr, "%s:%d - error parsing line (rc=%d, strind=%d)\n",
							pcFile, lineno, rc, strind);
					exit(EXIT_FAILURE);
				}
				break;
	
			default:
				fprintf(stderr, "%s:%d - unrecognized line\n", pcFile, lineno);
				exit(EXIT_FAILURE);
				break;
		}
	}
	dagutil_verbose_level(3,"    Done reading file\n");
	
	fclose(cfile);
	
	return (chancount);
} /* ReadChannelConfig */



/***************
* Examples: adding different types of channels
*
* add a simple receive channel, interface 3, timeslot 8
*   rslt = dag_add_channel(dagfd, DAG_DIR_RECEIVE, DAG_CT_CHAN, 3, 8);
*
* add a receive subchannel, interface 1, timeslot 7, bits 2-5
*   rslt = dag_add_channel(dagfd, DAG_DIR_RECEIVE, DAG_CT_SUB, 1, 0x0007003C);
* the 7 is the timeslot number and the 3C is the subchannel mask
*
* add a receive hyperchannel, interface 14, timeslots 1,2,3,7,8,12,15,27
*   rslt = dag_add_channel(dagfd, DAG_DIR_RECEIVE, DAG_CT_HYPER, 14, 0x040048c7);
* 0x040048c7 is the timeslot bitmap. the way that is made is by putting a
* 1 at the bit you want part of the hyperchannel, the first bit is for
* timeslot 1.
****************/


#ifndef ENDACE_UNIT_TEST
int
main(int argc, const char* const * argv)
{
	return dagchan_main(argc, (char**) argv);
}
#endif /* ENDACE_UNIT_TEST */
