/*
 * Copyright (c) 2002-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: dagsort.c 13166 2010-09-07 10:33:47Z karthik.sharma $
 *
 * Takes network traffic traces of various formats, allows filtering and
 * packet manipulations to be performed and writes modified traces out in
 * various formats.
 */

/* dagsort headers. */
#include "adt/adt_list.h"
#include "dagsort.h"

/* Endace headers. */
#include "dagapi.h"
#include "dagutil.h"
#include "dag_platform.h"
#include "sys/stat.h"
/* CVS Header */
static const char* const kCvsHeader __attribute__ ((unused))  = "$Id: dagsort.c 13166 2010-09-07 10:33:47Z karthik.sharma $";
static const char* const kRevisionString = "$Revision: 13166 $";

#define MAX_RECORD_LEN 1024*64

/* Array to hold the input file names. */
char gInpArray[100];

/* Pointer to hold the output file names. */
char* out_file_name = NULL;

/* Array to hold the input files' pointers. */
FILE *gInputFile;

/* Pointer to hold list of dag records (usually first record) from each file. */
void *gInpRecord;

/*stores the current position*/
IteratorPtr position_p;

/* Counter variable to hold number of input files. */
int gFileCount = 0;

int64_t gMaxNegativeDiff = 0;

/* The arguments passed to the DAG card. */
static char * uExtraArgs;

/* Set by signal handler to indicate processing should be stopped. */
static int uStopProcessing = 0;

/* Counter variables used to hold statistics by the report function. */
static uint64_t uInCount;
static uint64_t uOutCount;
static uint64_t uByteCount;

/* Internal routines. */
static void print_version(void);
static void print_usage(void);
static void close_devices(void);

static void sortfile(void);

static void anysig(int sig);
static void report(ListPtr readlist_p);
static void alarmsig(int sig);	

static int doreport = 1;

/* Implementation of internal routines. */
static void
print_version(void)
{
	printf("dagsort (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}


static void
print_usage(void)
{
	print_version();

	/* FIXME: not yet implemented.
	   dagclarg_display_usage(clarg, stdout);
	*/

	printf("dagsort - Endace DAG file sorting utility\n");
	printf("Usage: dagsort [options] [extra-args]\n");
	printf("    -h,--help,--usage      display help (this page)\n");
	printf("    -v,--verbose           increase verbosity\n");
	printf("    --version              display version information\n");
	printf("    -i <filename>          input erf file  \n");
	printf("    -o <filename>          output erf file \n");
	
}


/* Opens (dag)input and output files if the input and output types are
 * appropriate.  If in_file_name is null then stdin is used.  If out_file_name
 * is null the stdout is used.
 */
static void
open_devices(char *in_file_name, int dagstream, char *out_file_name)
{
	if (in_file_name && strcmp(in_file_name, "-") == 0)
	{
		in_file_name = NULL;
	}

	if (out_file_name && strcmp(out_file_name, "-") == 0)
	{
		out_file_name = NULL;
	}

	set_erf_output(out_file_name);

	dagutil_verbose("Set output file to %s.\n", out_file_name == NULL ? "stdout" : out_file_name);
}
/*
 * Closes the input and output devices by calling appropriate device specific
 * functions.
 */
static void
close_devices(void)
{
	
	close_erf_output();
	close_erf_input();	
	dagutil_verbose("Closed input and output files.\n");
}
/* The heart of dagsort.  Reads ERF headers and payloads from an input
 * reader, applies any filtering and sends (possibly modified) ERF headers and
 * payloads to output writer.
 */
static void
process(void)
{
	dag_record_t * header;
	uint64_t ts_now = 0;
	uint64_t max_so_far = 0;
	uint64_t max_neg_diff = 0;
	char command[512];
	uint64_t abs_max = 0;
	uint64_t abs_min = 0;
	
	uInCount = 0;
	uOutCount = 0;
	uByteCount = 0;

	dagutil_verbose("Scanning the input file for largest negative difference in timestamps.....\n");
	header = get_next_erf_header();
	ts_now = header->ts;
	abs_max = ts_now;
	abs_min = ts_now;
	while (!uStopProcessing)
	{
		if(header != NULL)
		{
			uInCount++;
			ts_now = header->ts;
			if(ts_now > abs_max)
				abs_max = ts_now;

			if(ts_now < abs_min)
				abs_min = ts_now;

			if(ts_now > max_so_far)
				max_so_far = ts_now;
			if((ts_now < max_so_far) && ((max_so_far - ts_now) > max_neg_diff))
			{
				max_neg_diff = max_so_far - ts_now;
			}
		}else
		{
			break;
		}
		header = get_next_erf_header();
	}
	gMaxNegativeDiff = max_neg_diff;
	dagutil_verbose_level(0,"Processed %"PRIu64" records: \n",uInCount);
	dagutil_verbose_level(0,"Total time span of the file is %0.9f secs. \n",(double)(abs_max - abs_min)/0x100000000ll);
	dagutil_verbose_level(0,"Largest negative difference in timestamps is %0.9f secs\n",(double)gMaxNegativeDiff/0x100000000ll);
	
	fclose(gInputFile);
	if (gMaxNegativeDiff >  0)
	{
		dagutil_verbose("The input file is unsorted. \n");
		sortfile();
		dagutil_verbose("%"PRIu64"%% of the file has been sorted.\n ",(uint64_t)100);	
	}
	else
	{
		/*Copying input file to output file.*/
		dagutil_verbose("The input file is sorted.\n");	
		dagutil_verbose("Copying %s to %s ...\n",gInpArray,out_file_name);
		sprintf(command,"cp  %s %s",gInpArray,out_file_name);
		system(command);
	}
}
static void
sortfile(void)
{
	int fd1 = 0;
	read_rec_t *start;
	read_rec_t *last;
	read_rec_t *temp;
	IteratorPtr  iterator_p;
	char candidate_store[MAX_RECORD_LEN];
	uint32_t candidate_size = 0;
	ListPtr readlist_p = NULL;
	
	fd1 = open(gInpArray, O_RDONLY|O_LARGEFILE);
	if (fd1 == -1)
		dagutil_panic("Could not open %s for reading.\n", gInpArray);

	gInputFile =  fdopen(fd1, "r");
	if (gInputFile == NULL)
	{
		fclose(gInputFile);
		dagutil_panic("Could not open %s for reading.\n", gInpArray);
	}

	readlist_p = adt_list_init(kListInterfaceIterator, kListRepresentationDynamic, adt_null_disposer);
	if ( readlist_p == NULL )
	{
		dagutil_error ("adt_list_init : %s\n", strerror(errno));
		return;
	}
	iterator_p = adt_list_get_iterator(readlist_p);
	if(NULL == iterator_p)
	{
		dagutil_error("adt_list_get_iterator: %s \n",strerror(errno));
		return ;
	}
	adt_iterator_last(iterator_p);
	position_p = adt_iterator_duplicate(iterator_p);
	if(NULL == position_p)
	{
		dagutil_error("adt_list_get_iterator: %s \n",strerror(errno));
		return ;
	}
	
	dagutil_set_signal_handler(anysig);
        dagutil_set_timer_handler(alarmsig, 1);	
	
	/* Register a couple of signals */
	signal(SIGINT, anysig);
	signal(SIGTERM, anysig);

	/*Read the first record.*/
	start = read_next_erf_header(readlist_p);
	while(1)
	{
		adt_iterator_last(iterator_p);
		last = (read_rec_t*)adt_iterator_retrieve(iterator_p);
		if(last == NULL)
			goto done;
		while(1)
		{
			if(doreport)
				report(readlist_p);
			
			if((last->header->ts > (start->header->ts + gMaxNegativeDiff)))
			{
				break;
			}
			last = read_next_erf_header(readlist_p);
			if(last == NULL)
				break;
			
		}
		/*If start is same as candidate update start.*/
		adt_iterator_first(iterator_p);
		if(adt_iterator_is_in_list(iterator_p))
		{
			start = (read_rec_t*)adt_iterator_retrieve(iterator_p);
			memcpy(candidate_store,start->header,dag_record_size);
			memcpy((candidate_store + dag_record_size),start->payload,(ntohs(start->header->rlen) - dag_record_size));
			candidate_size = ntohs(start->header->rlen);

			write_erf_record(candidate_store,candidate_size);		

			/*delete the candidate from the list.how do you do that???*/
			temp = adt_iterator_retrieve(iterator_p);
			free(temp->header);
                        free(temp->payload);
                        free(temp);
			adt_iterator_remove(iterator_p);
		
			adt_iterator_first(iterator_p);	
			start = adt_iterator_retrieve(iterator_p);
		}
		else
		{
			goto done;
		}
	}
done:
	adt_list_dispose(readlist_p);
	adt_iterator_dispose(iterator_p);
	adt_iterator_dispose(position_p);
	fclose(gInputFile);
}
/*
 * Parses command line arguments setting appropriate parameters in input and
 * output devices and the initiates the processing.
 */
int
dagsort_main(int argc, char **argv)
{
	int opt;
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || defined(__APPLE__)
	int fd1;	
#endif

	char in_file_name[DAGNAME_BUFSIZE];
	
	dagutil_set_progname("dagsort");

	while ((opt = getopt(argc, argv, "A:a:b:c:d:e:f:FG:hi:o:p:u:r:s:t:T:UVv-:y:")) != EOF)
	{
		switch (opt)
		{
			case '?':
			case 'h':
				print_usage();
				return EXIT_SUCCESS;
				break;

			case 'i':
				strncpy(in_file_name, optarg, DAGNAME_BUFSIZE);
				if (gFileCount < 2)
				{
					strcpy(gInpArray, in_file_name);
					gFileCount++;
				}
				break;

			case 'o':
				out_file_name = optarg;
				break;

			case 'v':
				dagutil_inc_verbosity();
				dagutil_verbose("Verbose level set to %d\n", dagutil_get_verbosity());
				break;
			case '-':
				if (strcmp(optarg, "help") == 0 || strcmp(optarg, "usage") == 0)
				{
					print_usage();
					return EXIT_SUCCESS;
				}
				else if (strcmp(optarg, "verbose") == 0)
				{
					dagutil_inc_verbosity();
					dagutil_verbose("Verbose level set to %d\n", dagutil_get_verbosity());
				}
				else if (strcmp(optarg, "version") == 0)
				{
					print_version();
					return EXIT_SUCCESS;
				}
				else
				{
					dagutil_panic("unknown option '%s', see -h for help on usage\n", optarg);
				}
				break;

			default:
				dagutil_panic("unknown option, see -h for help on usage\n");
				break;
		}
	}

	gInpRecord = dagutil_malloc(sizeof(dag_record_t));

	if(0 == gFileCount)
	{
		dagutil_panic("Input file name not specified \n");
	}

	if(NULL == out_file_name)
	{
		dagutil_panic("Output file not specified \n");
	}


	open_devices(NULL, 0, out_file_name);
	fd1 = open(gInpArray, O_RDONLY|O_LARGEFILE);
	if (fd1 == -1)
		dagutil_panic("Could not open %s for reading.\n", gInpArray);

	gInputFile =  fdopen(fd1, "r");
	if (gInputFile == NULL)
	{
		fclose(gInputFile);
		dagutil_panic("Could not open %s for reading.\n", gInpArray);
	}

	/* Do the processing. */
	process();

	/* Close input and output files. */
	close_devices();

	/* Finished with the extra args at this point. */
	dagutil_free(uExtraArgs);
	dagutil_free(gInpRecord);

	sleep(1);

	return EXIT_SUCCESS;
}




static void
anysig(int sig)
{
	/* Restore the default signal handlers, so the next interrupt will have the default effect (e.g. exit) */
	dagutil_set_signal_handler(SIG_DFL);
	_exit(EXIT_SUCCESS);
}


static void
alarmsig(int sig)
{
	doreport++;
}


static void
report(ListPtr readlist_p)
{
	uint64_t rate;

	struct stat st_in,st_out;
	uint32_t in_size,out_size = 0;

	stat(gInpArray, &st_in);
	in_size = st_in.st_size;

	stat(out_file_name, &st_out);
	out_size = st_out.st_size;

	/*
	 * rate = size of the output file * 100 / size of the input file.
	 */
	rate = ((((off_t)out_size) *100) /(off_t)in_size);

	dagutil_verbose("%"PRIu64"%% of the file has been sorted. %d records cached.  \r",rate,adt_list_items(readlist_p));
	doreport = 0;
}


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