/*
Copyright (c) 2001		     RIPE NCC


All Rights Reserved

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of the author not be
used in advertising or publicity pertaining to distribution of the
software without specific, written prior permission.

THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/*
-------------------------------------------------------------------------------
Module Header
Filename	  : TTMOptions.C
Author	    	  : Rene Wilhelm
Date	      	  : 31-OCT-2001
Description       : TTMOptions class implemenation
Language Version  : C++
OSs Tested        : Solaris 2.6, Solaris 8, Debian Linux 2.2
TODO		  : Implement destructor (nice to clean up)
		    Implement dynamic help function 

$Id: TTMOptions.C,v 1.4 2003/05/16 13:57:41 ttraffic Exp $
-------------------------------------------------------------------------------
*/

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include "TTMOptions.h"
#include "utils.h"

static char const rcsid[] = "$Id: TTMOptions.C,v 1.4 2003/05/16 13:57:41 ttraffic Exp $";
static char const *rcsid_p = rcsid;   // Prevent g++ from optimizing out rcsid


/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Initializing constructor
Input 		  :   argument vector, option string (getopt(3) syntax)
Comments	  :   This constructor assigns default values to all
		      parameters, and then parses the argument vector,
		      setting optional parameters to the provided values
-------------------------------------------------------------------------------
*/

TTMOptions::TTMOptions(int argc, char** argv, char* optstring) {

	extern char *optarg;
	char buffer[50];
	char ch;

	sourceId   = 0;
	targetId   = 0;
	endTime    = 0;
	timePeriod = 0;
	ipVersion  = 4;
	inputPath  = "/ncc/ttpro/data/root";
	outputPath = "/ncc/ttpro/data/plots";
	minDelay   = 0;
	maxDelay   = 250;
	allPackets = 0;
	logScale   = 0;
	storageType = 'H';	// hierarchical
	sumPeriod = day;
	optionString = optstring;
	
	plotFormat = gif;
	verbose = 0;

	progName = argv[0];

	putenv("TZ=GMT+0");	// ensure GMT/UTC for time operations

        short fflag=0;
        short cflag=0;
        short mflag=0;
        short oflag=0;
        short pflag=0;
        short sflag=0;
        short tflag=0;
        short zflag=0;

	char *endpointer;


	while ((ch = getopt(argc, argv, optstring)) != -1)

	switch(ch) {
	case '6':
		ipVersion = 6;
		break;
	case 'a':
		allPackets = 1;
		break;
	case 'f':
		if (fflag) {
	                usage(); /* only one -f allowed */
		}

		if (strcmp(optarg, "csv") == 0) {
			plotFormat = csv;
		}
		else if (strcmp(optarg, "gif") == 0) {
			plotFormat = gif;
		}
		else if (strcmp(optarg, "ps") == 0) {
			plotFormat = ps;
		}
		else if (strcmp(optarg, "eps") == 0) {
			plotFormat = eps;
		}
		else if (strcmp(optarg, "pdf") == 0) {
			plotFormat = pdf;
		}
		else {
			usage();
		}
		fflag = 1;
		break;
	case 's':
		if (!sflag) {
			sflag = 1;    
			if (sscanf(optarg, "%[0-9]", buffer) != 1) {
				usage();
			}
			sourceId = atoi(buffer);

		}
		else {
			/* only one -s allowed */
			usage();
		}
		break;
	case 't':
		if (!tflag) {
			tflag = 1;    
			if (sscanf(optarg, "%[0-9]", buffer) != 1) {
				usage();
			}
			targetId = atoi(buffer);
		}
		else {
			/* only one -t allowed */
			usage();
		}
		break;
	case 'p':
		if (!pflag) {
			pflag = 1;    
			decodePeriod(optarg, endTime, timePeriod);
		}
		else {
			/* only one -p allowed */
			usage();
		}
		break;
	case 'o':
		if (!oflag) {
			outputPath = new char[strlen(optarg)+1];
			strcpy(outputPath, optarg);
		}
		else {
			/* only one -o allowed */
			usage();
		}
		break;
	case 'v':
		verbose = 1;
		break;
        case 'm':
		if (!mflag) {
	                mflag = 1;    
			maxDelay = strtod(optarg, &endpointer);
	                if (*endpointer != '\0') {
				usage();
			}
			if (maxDelay <0) {
				fprintf (stderr, "error: %s: -m positive value expected\n", progName);
				exit(1);
			}
			else if ((maxDelay == HUGE) || (maxDelay == HUGE_VAL)) {
				fprintf (stderr, "error: %s: -m value out of range\n", progName);
				exit(1);
			}
		}
		else {
			/* only one -m allowed */
			usage();
		}
		break;
	case 'w':
		sumPeriod = week;
		break;
        case 'z':
		if (!zflag) {
	                zflag = 1;    
			minDelay = strtod(optarg, &endpointer);
	                if (*endpointer != '\0') {
				usage();
			}
			if (minDelay <0) {
				fprintf (stderr, "error: %s: -z positive value expected\n", progName);
				exit(1);
			}
			else if ((minDelay == HUGE) || (minDelay == HUGE_VAL)) {
				fprintf (stderr, "error: %s: -z value out of range\n", progName);
				exit(1);
			}
		}
		else {
			/* only one -z allowed */
			usage();
		}
		break;
	default:
		usage();
	}

	if ( !pflag || (tflag && !sflag)) {
		/* time period is only required argument */
		/* specific target requires source to be specified as well */
		usage();
	}

	if ( !cflag ) {
		// construct default config command:
		//  "TTCONFIG_COMMAND -d <enddate> TTCONFIG_COMMAND_ARGS"

		int len = strlen(TTCONFIG_COMMAND) +
			  strlen(TTCONFIG_COMMAND_ARGS) + 33;
			  
		configCmd  = new char[len];
		strcpy(configCmd, TTCONFIG_COMMAND);

		char *cp = strchr(configCmd, '\0');

	        strcpy(cp, " -d ");
       		cp = cp + 4;

        	if (strftime(cp,26,"'%Y-%m-%d %H:%M:%S'",localtime(&endTime)) == 0) {
                	fprintf(stderr, "%s: strftime(): ERROR converting time %d\n",
                        progName, (int) endTime);
        	}
        	strcat(configCmd, " ");

		if (ipVersion == 6) {
        		strcat(configCmd, TTCONFIG_COMMAND_ARGS6);
		}
		else {
        		strcat(configCmd, TTCONFIG_COMMAND_ARGS);
		}
	}
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	:   TTMOptions destructor
Comments	:
-------------------------------------------------------------------------------
*/

/*
-------------------------------------------------------------------------------
Subroutine header
Purpose         :   Error handler
Input           :   none
Output          :   error message
Side effects    :   terminates program
-------------------------------------------------------------------------------
 */

void TTMOptions::invaliddate() {
        fprintf (stderr, "%s: invalid date/time specification\n", progName);
        exit(1);
}

/*
-------------------------------------------------------------------------------
Subroutine header

Purpose          :   decode ascii ISO8601 time period string into an
		     ending time in time_t format (seconds since 1970)
	  	     and a duration
Input            :   char  *datestring  string to be decoded
Output           :   time_t& endtime     system timestamp of end time
                     time_t& duration    duration in seconds

-------------------------------------------------------------------------------
*/

void TTMOptions::decodePeriod (char* datestring, time_t& endtime, time_t& duration) {

	char *part1, *part2;
	char flag1, flag2;
	time_t time1, time2;

	/* datestring =  ISO8601 time period string */
	/* two parts seperated by /  */


	part1 = datestring;
	if ((part2 = strchr(part1, '/')) == NULL) {
		invaliddate();
        }

	*part2 = '\0';
	part2++;

	parseISO8601 (part1, time1, flag1);
	parseISO8601 (part2, time2, flag2);

        if ((flag1 == 'T') && (flag2 == 'D')) {
		/* format: starttime/duration */
		duration = time2;
		endtime = time1 + duration;
	}
	else if ((flag1 == 'D') && (flag2 == 'T')) {
		/* format: duration/endtime */
		duration = time1;
		endtime = time2;
	}
	else if ((flag1 == 'T') && (flag2 == 'T')) {
		/* format: starttime/endtime */
		if (time1 >= time2) {
			/* negative or zero time interval */
			invaliddate();
		}
		else {
			endtime = time2;
			duration = time2 - time1;
		}
	}
	else {
		invaliddate();
        }
}

/*
-------------------------------------------------------------------------------
Subroutine header

Purpose	  	:  decode ascii time string into duration of time or
		   fixed point in time; the string should be in the
		   formats described by ISO8601 standard

NOTE		:  to not complicate the routine too much, points in time
		   MUST be complete representations of either a date (CCYYMMDD)
		   or a date+time (CCYYMMDDTHHMMSS).
	           - and : are allowed as separators.

		   similarly, durations are only supported when specified
		   in days, hours, minutes and seconds. Length of months
		   and years are content dependant.

Input		:  char*   text  string to be decoded
Output		:  time_t& time  system timestamp or duration  (seconds)
                   char&   flag  indicates if returned time is timestamp
		   or duration

-------------------------------------------------------------------------------
*/

#define DAYFLAG    0x001000
#define DHMSFLAG   0x001111
#define HOURFLAG   0x000100
#define HMSFLAG    0x000111
#define MINFLAG    0x000010
#define MSFLAG     0x000011
#define SECFLAG    0x000001

void TTMOptions::parseISO8601 (char *text, time_t& isotime, char& flag) {
	char *c;
	int num;

	struct tm  tmstruct;
 
	int year     = 0;
	int month    = 0;
	int seconds  = 0;
	int minutes  = 0;
	int hours    = 0;
	int days     = 0;

	int dateflags = 0;   /* flag which date component we've seen */

	c = text;
	isotime = 0;

	if (*c++ == 'P') {
		/* duration */
		flag = 'D';
		while (*c != '\0') {
			num = 0;
			while (*c >= '0' && *c <= '9') {
				/* assumes ASCII sequence! */
				num = 10 * num + *c++ - '0';
			}
		
			switch (*c++) {
			case 'D':
				if (dateflags & DHMSFLAG) {
					/* day, hour, min or sec already set */
					invaliddate();
				}
				else {	
					dateflags |= DAYFLAG;
					days = num;
				}
				break;
			case 'H':
				if (dateflags & HMSFLAG) {
					/* hour, min or sec already set */
					invaliddate();
				}
				else {	
					dateflags |= DAYFLAG;
					hours = num;
				}
				break;
			case 'M':
			  	if (dateflags & MSFLAG) {
					/* min or sec already set */
					invaliddate();
				}
				else {
					dateflags |= MINFLAG;
					minutes = num;
			  	}
				break;
			case 'S':
				if (dateflags & SECFLAG) {
					/* sec already set */
					invaliddate();
				}
				else {	
					dateflags |= SECFLAG;
					seconds = num;
				}
				break;
			default:
				invaliddate();
			}
		}
		isotime = seconds + 60 * minutes + 3600 * hours +
			   SECONDSPERDAY * days;
	}
	else {
		/* point in time, must be one of 
			CCYYMMDD
			CCYY-MM-DD
			CCYYMMDDTHHMM
			CCYY-MM-DDTHH:MM
			CCYYMMDDTHHMMSS
			CCYY-MM-DDTHH:MM:SS
		*/
		c = text;
		flag = 'T';

		/* NOTE: we have to check for the extended format first, 
		   because otherwise the separting '-' will be interpreted
		   by sscanf as signs of a 1 digit integer .... :-(  */

		if (sscanf(text, "%4u-%2u-%2u", &year, &month, &days) == 3) {
			c += 10;
		}
		else if (sscanf(text, "%4u%2u%2u", &year, &month, &days) == 3) {
			c += 8;
		}
		else {
			invaliddate();
		}

		tmstruct.tm_year = year - 1900;
		tmstruct.tm_mon  = month - 1;
		tmstruct.tm_mday = days;

		if (*c == '\0') {
			tmstruct.tm_hour = 0;
			tmstruct.tm_sec = 0;
			tmstruct.tm_min = 0;
			isotime = mktime (&tmstruct);
		}
		else if (*c == 'T') {
			/* time of day part */
			c++;
			if (sscanf(c, "%2d%2d", &hours, &minutes)  == 2) {
				c += 4;
			}
			else if (sscanf(c, "%2d:%2d", &hours, &minutes) == 2) {
				c += 5;
			}
			else {
				invaliddate();
			}

			if (*c == ':') {
				c++;
			}

			if (*c != '\0') {
			   	if (sscanf(c, "%2d", &seconds)  == 1) {
					c += 2;
			   	}
			   	else {
					invaliddate();
			   	}
				if (*c != '\0') {      /* something left? */
					invaliddate();
				}
			}
			tmstruct.tm_hour = hours;
			tmstruct.tm_min = minutes;
			tmstruct.tm_sec = seconds;
			isotime = mktime (&tmstruct);
		}

		else {
			invaliddate();
		}
	}

}

TTMOptions::~TTMOptions() {

}

void TTMOptions::usage() {

	char *cp = optionString;
	fprintf (stderr, "usage: %s", progName);

	while (*cp != '\0') {
		switch (*cp) {
		case 'v':
			// verbose, optional
			fprintf(stderr, " [-v]");
			break;
		case 'w':
			// week summaries, optional
			fprintf(stderr, " [-w]");
			break;
		case 'f':
			// plot format, optional
			cp++;
			fprintf(stderr, " [-f fmt]");
			break;
		case 's':
			// source id, optional
			cp++;
			fprintf(stderr, " [-s src]");
			break;
		case 't':
			// target id, optional
			cp++;
			fprintf(stderr, " [-t tgt]");
			break;
		case 'm':
			// max delay, optional
			cp++;
			fprintf(stderr, " [-m max]");
			break;
		case 'z':
			// target id, optional
			cp++;
			fprintf(stderr, " [-z min]");
			break;
		case '6':
			// IPv6 instead of IPv4 data, optional
			cp++;
			fprintf(stderr, " [-6]");
			break;
		case 'p':
			// time period, required
			cp++;
			fprintf(stderr, " -p period");
			break;
		case 'o':
			// output dir, optional
			cp++;
			fprintf(stderr, " [-o outdir]");
			break;
		default:
			fprintf(stderr, " %c", *cp);
		}
		cp++;
	}
		
	fprintf(stderr, "\n");
	exit(1);
}

