#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <sys/time.h>
#include <limits.h>


typedef struct {
    unsigned int tic;
    struct timeval time;
    unsigned int pkts;
    unsigned int dev;
} record_t;

typedef struct {
    unsigned long long tics;
    struct timeval time;
} end_record_t;

//typedef long long int64_t;

int tic_freq = 31250000;

typedef struct {
    int64_t tic_cur;
    int64_t tic_base;
    struct timeval time_base;
    int64_t tic_last;
} timer_st;

int verbose = 0;
int total = 0;
int minutes = 0;

end_record_t *ends = NULL;
int end_length = 0;
int end_mode = 0;
void do_end_ave(int samples);

void do_record(timer_st *st, record_t *rec);
void get_offset(unsigned long long tics, unsigned long tic_base, unsigned long tic_frequency, struct timeval time_base, struct timeval time, int *getsec, int *getusec, int *getneg);
unsigned long get_freq(end_record_t *start, end_record_t *end);

FILE *output_file;

int main(int argc, char **argv)
{
    char c;
    int last, i, rc = 1;
    unsigned int dev = 0;
    FILE *file = stdin;
    record_t *recs = NULL;
    int rlen = 0, rcap = 0;
    timer_st tst;
    char line1[256];
    char line2[256];
    
    output_file = stdout;
 
    while ((c = getopt(argc, argv, "hf:o:d:vt:me:")) != -1)
	switch (c) {

	case 'e':
	    if(sscanf(optarg, "%d", &end_mode) != 1)
	    {
		printf("Cannot read end_mode number from '%s'\n", optarg);
		return 1;
	    }
	    break;

	case 'm':
	    minutes++;
	    break;

	case 'v':
	    verbose++;
	    break;

	case 't':
	    if(sscanf(optarg, "%d", &tic_freq) != 1)
	    {
		printf("Cannot read assumed tic frequency from '%s'\n", optarg);
		return 1;
	    }
	    break;

	case 'd':
	    if(sscanf(optarg, "%u", &dev) != 1)
	    {
		printf("Cannot read dev number from '%s'\n", optarg);
		return 1;
	    }
	    break;

	case 'h' :
	    fprintf(stderr,"%s\n"
		    "\t-f <filename>     {get data from filename - else stdin}\n"
		    "\t-o <filename>     {put data into filename - else stdout}\n"
		    "\t-v                {verbose mode}\n"
		    "\t-e                {end to end tic frequency estimation}\n"
		    "\t-t <freq>         {assumed tic frequency}\n"
		    "\t-d <dev>          {device number, [0..)}\n"
		    "\t-h                {shows this help page}\n\n", argv[0]);
	    return 0;
	    break;

	case 'f' :
	    file = fopen(optarg, "r");
	    if(!file)
	    {
		printf("Cannot read file '%s'\n\n", optarg);
		return 1;
	    }
	    break;

	case 'o' :
	    output_file = fopen(optarg, "w");
	    if(!output_file)
	    {
		printf("Cannot open file '%s'\n\n", optarg);
		return 1;
	    }
	    break;

	default :
	    printf("Bad option %c\n", c);
	}

    i=0;
    while((line1[i++] = fgetc(file)) != '\n');
    line1[i-1] = 0;
    i=0;
    while((line2[i++] = fgetc(file)) != '\n');
    line2[i-1] = 0;

    fprintf(stderr, "Got '%s'\nand '%s'\n", line1, line2);

    while(rc)
    {
	if(rlen + 1024 >= rcap)
	    recs = (record_t *) realloc(recs, sizeof(record_t) * (rcap += 16384));

	rlen += rc = fread(recs + rlen, sizeof(record_t), 1024, file);
    }

    ends = (end_record_t *) malloc(sizeof(end_record_t) * rlen);

    fprintf(stderr, "%d records read\n", rlen);

    if(verbose > 1)
    {
	for(i=0; i<rlen; i++)
	    fprintf(output_file, "%10u %7d.%06d %10u %u\n",
		    recs[i].tic, (int) recs[i].time.tv_sec, (int) recs[i].time.tv_usec,
		    recs[i].pkts, recs[i].dev);
    }
    else
    {
	last = 0;
	while(last < rlen && recs[last].dev != dev)
	    last++;

	if(last == rlen)
	{
	    fprintf(stderr, "No records with dev=%d\n", dev);
	    return 0;
	}

	ends[end_length].time = tst.time_base = recs[last].time;
	ends[end_length++].tics = tst.tic_last = tst.tic_cur = tst.tic_base = recs[last].tic;

	for(i=last+1; i<rlen; i++)
	{
	    if(recs[i].dev == dev)
	    {
		// recalibrate base timestamp
		if(recs[i].tic == 0 && recs[i].pkts == 0)
		{
		    fprintf(stderr, "Recal base time\n");
		    tst.time_base = recs[i].time;
		}
		else
		    do_record(&tst, recs+i);
	    }
	}
    }
    fprintf(stderr, "Got %d end records\n", end_length);

    if(end_mode)
    {
	//for(i=0; i<end_length; i++)
	//printf("%15llu %d.%06d\n", ends[i].tics, (int) ends[i].time.tv_sec, (int) ends[i].time.tv_usec);

	do_end_ave(end_mode);
	for(i=0; i<end_length; i++)
	{
	    /*
	    int sec, usec, neg;
	    get_offset(ends[i].tics, ends[0].tics, tic_freq, ends[0].time, ends[i].time, &sec, &usec, &neg);
	    fprintf(output_file, "%9ld.%06d   %c%d.%06d\n", (int) ends[i].time.tv_sec - ends[0].time.tv_sec, (int) ends[i].time.tv_usec, neg ? '-' : ' ', sec, usec);
	    */

	    int j;

	    for(j=0; j<end_length; j++)
		if(j != i)
		    fprintf(output_file, "%d  %d  %ld\n", i, j, (long) get_freq(ends+i, ends+j)- (long) tic_freq);

	    fprintf(output_file, "\n");
		
		

	}
	fprintf(stderr, "End freq is %lu\n", get_freq(ends, ends+end_length-1));
    }
    
    return 0;
}

void do_end_ave(int samples)
{
    int i, n = 0, num = 0;
    unsigned long long tics = 0, secs = 0, usecs = 0;
    end_record_t *nend;

    nend = (end_record_t *) malloc(sizeof(end_record_t) * (end_length / samples));

    for(i=0; i<end_length; i++)
    {
	n++;
	tics += ends[i].tics;
	secs += ends[i].time.tv_sec;
	usecs += ends[i].time.tv_usec;

	//fprintf(stderr, "   - %d  add %d.%06d %llu\n", n, ends[i].time.tv_sec, ends[i].time.tv_usec, ends[i].tics);

	if(n == samples)
	{
	    nend[num].tics = (tics + (samples / 2)) / samples;
	    nend[num].time.tv_sec = secs / samples;

	    //fprintf(stderr, "%lld\n", secs - ((unsigned long long) nend[num].time.tv_sec * (unsigned long long) samples));

	    nend[num].time.tv_usec = (unsigned long long) 1000000 * (secs - ((unsigned long long) nend[num].time.tv_sec * (unsigned long long) samples)) / samples;
	    nend[num].time.tv_usec += (usecs + (samples / 2)) / samples;

	    if(nend[num].time.tv_usec > 1000000)
	    {
		nend[num].time.tv_usec -= 1000000;
		nend[num].time.tv_sec ++;
	    }

	    //fprintf(stderr, "%d.%06d  %llu\n", nend[num].time.tv_sec, nend[num].time.tv_usec, nend[num].tics);
	    num++;
	    n = 0;
	    tics = secs = usecs = 0;
	}
    }

    ends = nend;
    end_length = num;
    fprintf(stderr, "Down to %d end records\n", end_length);
}

unsigned long get_freq(end_record_t *e1, end_record_t *e2)
{
    unsigned long long tdiff, udiff;
    end_record_t *start, *end;

    if(e1->tics > e2->tics)
    {
	start = e2;
	end = e1;
    }
    else
    {
	start = e1;
	end = e2;
    }

    tdiff = end->tics - start->tics;
    udiff = (unsigned long long) 1000000 * (unsigned long long) (end->time.tv_sec - start->time.tv_sec);

    if(end->time.tv_usec > start->time.tv_usec)
	udiff += end->time.tv_usec - start->time.tv_usec;
    else
	udiff -= start->time.tv_usec - end->time.tv_usec;

    return (tdiff * 1000000) / udiff;
}

void get_offset(unsigned long long tics, unsigned long tic_base, unsigned long tic_frequency, struct timeval time_base, struct timeval time, int *getsec, int *getusec, int *getneg)
{
    int neg, sec, usec;

    sec = (int) ((tics - tic_base) / (unsigned long long) tic_frequency);
    usec = 1000000 * ((tics - tic_base) % tic_frequency) / tic_frequency;
	    
    if(verbose)
	fprintf(output_file, "%16llu %6d.%06d ", tics, sec, usec);

    sec += time_base.tv_sec;
    usec += time_base.tv_usec;

    if(usec > 1000000)
    {
	sec++;
	usec -= 1000000;
    }

    // sec.usec is now the projected time using the tic_freq as correct
    if(verbose)
	fprintf(output_file, "%d.%06d  <%d.%06d>", sec, usec, (int) time.tv_sec, (int) time.tv_usec);

    if(sec > time.tv_sec || (sec == time.tv_sec && usec > time.tv_usec))
    {
	neg = 0;
	sec -= time.tv_sec;
	usec -= time.tv_usec;

	if(usec < 0)
	{
	    sec--;
	    usec += 1000000;
	}
    }
    else
    {
	neg = 1;
	sec = time.tv_sec - sec;
	usec = time.tv_usec - usec;

	if(usec < 0)
	{
	    sec--;
	    usec += 1000000;
	}
    }

    *getsec = sec;
    *getusec = usec;
    *getneg = neg;
}

void do_record(timer_st *st, record_t *rec)
{
    struct timeval time = rec->time;
    unsigned int tic = rec->tic;
    int64_t tdiff = (int64_t) tic - (int64_t) st->tic_last;
    int neg, sec, usec;
    
    if(tdiff < 0)
	tdiff += UINT_MAX;
	    
    st->tic_cur += tdiff;

    ends[end_length].tics = st->tic_cur;
    ends[end_length++].time = time;

    if(verbose)
	fprintf(output_file, "%6d %12u ", total++, tic);

    get_offset(st->tic_cur, st->tic_base, tic_freq, st->time_base, time, &sec, &usec, &neg);

    if(!verbose && !end_mode)
    {
	int run_sec, run_usec, run_min;
	run_sec = time.tv_sec - st->time_base.tv_sec;
	run_usec = time.tv_usec - st->time_base.tv_usec;

	if(run_usec < 0)
	{
	    run_sec--;
	    run_usec += 1000000;
	}

	if(minutes)
	{
	    run_min = run_sec / 60;
	    run_sec = (1000000 * (run_sec - (run_min * 60))  / 60) + (run_usec / 60);

	    if(minutes > 1)
	    {
		int run_hours = run_min / 60;
		
		run_min = (1000000 * (run_min - (run_hours * 60)) / 60) + (run_sec / 60);

		fprintf(output_file, "%3d.%06d", run_hours, run_min);
	    }
	    else
		fprintf(output_file, "%9d.%06d", run_min, run_sec);
	}
	else
	    fprintf(output_file, "%9d.%06d", run_sec, run_usec);
    }

    if(!end_mode)
	fprintf(output_file, "  %c%d.%06d\n", neg ? '-' : ' ', sec, usec);

    st->tic_last = tic;
}


