/******************************************************************************
*                                                                             *
*   Copyright 2005 University of Cambridge Computer Laboratory.               *
*                                                                             *
*   This file is part of Nprobe.                                              *
*                                                                             *
*   Nprobe is free software; you can redistribute it and/or modify            *
*   it under the terms of the GNU General Public License as published by      *
*   the Free Software Foundation; either version 2 of the License, or         *
*   (at your option) any later version.                                       *
*                                                                             *
*   Nprobe 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 General Public License for more details.                              *
*                                                                             *
*   You should have received a copy of the GNU General Public License         *
*   along with Nprobe; if not, write to the Free Software                     *
*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
*                                                                             *
******************************************************************************/


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

#include <time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

#include "sk98_clk.h"
#include "basic_defs.h"
#include <netinet/ip.h>
#include "list.h"
#include "pkt.h"
#include "flows.h"
#include "service.h"
#include "http.h"
#include "tcp.h"
#include "udp.h"
#include "udp_ns.h"
#include "output.h"
#include "probe_config.h"

static sk98_clk_t sk98_clk;

static int called = 0;


int getline(char s[], int lim, FILE *stream)
{
    int c,i;

    for(i=0;i<lim-1 && (c=getc(stream))!=EOF && c!='\n';++i)
        s[i]=c;

    s[i]='\0';

    if(c != EOF) {
        return i;
    }else{
        return c;
    }
}

int sk98_start_clock(int chan)
{
    FILE *in;
    char line[256], filename[256];

    memset(&sk98_clk, 0, sizeof(sk98_clk_t));
    sk98_clk.chan = chan;
    sk98_clk.nictstamp_freq = 31250000;
    sk98_clk.drift_period = 500000;

    sprintf(filename, "%ssk98_clk.conf", log_dir);
    if((in = fopen(filename, "r")))
    {
	if(getline(line, 255, in) == -1)
	    fprintf(stderr, "sk98_clk: Failed to read from conf file (%s)\n", line);
	else
	{
	    int rd = sscanf(line, "%lu %lu", &sk98_clk.nictstamp_freq, &sk98_clk.drift_period);

	    switch(rd)
	    {
	    case 0:
		fprintf(stderr, "sk98_clk: Failed to decode nictstamp frequency\n");
		break;
	    case 1:
		fprintf(stderr, "sk98_clk: Got frequency %lu\n", sk98_clk.nictstamp_freq);
		break;
	    case 2:
		fprintf(stderr, "sk98_clk: Got frequency %lu, period %lu\n", sk98_clk.nictstamp_freq, sk98_clk.drift_period);
		break;
	    }
	}

	fclose(in);
    }
    else
    {
	fprintf(stderr, "sk98_clk: Failed to open %s: ", filename);
	perror("");
	fprintf(stderr, "sk98_clk: Using default settings, freq=%lu, period=%lu\n", sk98_clk.nictstamp_freq, sk98_clk.drift_period);
    }

    return 0;
}

void sk98_getTime(unsigned long nictstamp, struct timeval *tv, struct timespec *ts)
{
    unsigned long long tdiff;

    // handle nictstamp wrap
    if(nictstamp < sk98_clk.tic_last)
	tdiff = ((unsigned long long) nictstamp + UINT_MAX) - (unsigned long long) sk98_clk.tic_last;
    else
	tdiff = (unsigned long long) nictstamp - (unsigned long long) sk98_clk.tic_last;
    
    sk98_clk.tic_cur += tdiff;

    if(tv)
    {
	tv->tv_sec = (int) ((sk98_clk.tic_cur - sk98_clk.tic_base) / (unsigned long long) sk98_clk.nictstamp_freq);
	tv->tv_usec = (((unsigned long long) 1000000 *
			(unsigned long long) ((sk98_clk.tic_cur - sk98_clk.tic_base) % sk98_clk.nictstamp_freq)) +
		       (unsigned long long) (sk98_clk.nictstamp_freq / 2)) /
	    (unsigned long long) sk98_clk.nictstamp_freq;

	tv->tv_sec += sk98_clk.time_base.tv_sec;
	tv->tv_usec += sk98_clk.time_base.tv_usec;
	
	if(tv->tv_usec > 1000000)
	{
	    tv->tv_sec++;
	    tv->tv_usec -= 1000000;
	}
    }
    if(ts)
    {
	ts->tv_sec = (int) ((sk98_clk.tic_cur - sk98_clk.tic_base) / (unsigned long long) sk98_clk.nictstamp_freq);
	ts->tv_nsec = ((unsigned long long) 1000000000 *
		       (unsigned long long) ((sk98_clk.tic_cur - sk98_clk.tic_base) % sk98_clk.nictstamp_freq)) /
	    (unsigned long long) sk98_clk.nictstamp_freq;
	
	ts->tv_sec += sk98_clk.time_base.tv_sec;
	ts->tv_nsec += sk98_clk.time_base.tv_usec * 1000;
	
	if(ts->tv_nsec > 1000000000)
	{
	    ts->tv_sec++;
	    ts->tv_nsec -= 1000000000;
	}
    }
    
    sk98_clk.tic_last = nictstamp;
    return;
}

int sk98_doTimer(unsigned long nictstamp)
{
    int ret = 0;
    FILE *drift_file = NULL;

    switch(sk98_clk.mode)
    {
    case 0:
	sk98_clk.nextdrift = nictstamp + (unsigned long) (((drand48() * 8) + 1) * sk98_clk.drift_period);
	//fprintf(stderr, "Started drift at %lu\n", sk98_clk.nextdrift);
	sk98_clk.mode = 1;
	break;

    case 1:
    case 2:
	//fprintf(stderr, ".");
	called++;
	if((nictstamp >= sk98_clk.nextdrift && nictstamp - sk98_clk.nextdrift < 1<<30) ||
	   (nictstamp < sk98_clk.nextdrift && sk98_clk.nextdrift - nictstamp > 1<<30))
	{
	    unsigned long data[6];
	    struct timeval now;

	    gettimeofday(&now, (struct timezone *)0);
			
	    if(drift_file)
	    {
		data[0] = nictstamp;
		data[1] = (unsigned long) now.tv_sec;
		data[2] = (unsigned long) now.tv_usec;
		data[3] = 0; // (unsigned long) st->total_pkts;
		data[4] = 0; // st->dev;
		data[5] = sk98_clk.chan; // st->dev_num;

		//fprintf(stderr, "%d: Wrote drift: %lu %lu %lu %d.%06d\n", sk98_clk.chan, data[0], data[1], data[2], sk98_clk.time_base.tv_sec, sk98_clk.time_base.tv_usec);
		
		fwrite(data, sizeof(unsigned long), 6, drift_file);
		fflush(drift_file);
	    }

	    sk98_clk.nextdrift += (unsigned long) (((drand48() * 8) + 1) * sk98_clk.drift_period);
	    //fprintf(stderr, "%d: %lu  %d.%06d    next: %lu\n", sk98_clk.chan, nictstamp, now.tv_sec, now.tv_usec, sk98_clk.nextdrift);

	    if(nictstamp > sk98_clk.nextdrift && nictstamp - sk98_clk.nextdrift < 1<<30)
	    {

		if(nictstamp - sk98_clk.nextdrift > 5*sk98_clk.nictstamp_freq) // minimum of 1 pkt per 5 seconds
		{
		    fprintf(stderr, "sk98_clk: Skipping %d old packets nic: %lu   nd: %lu\n", called, nictstamp, sk98_clk.nextdrift);
		    sk98_clk.nextdrift = nictstamp + (unsigned long) (((drand48() * 8) + 1) * sk98_clk.drift_period);
		    // skip past these old packets
		    sk98_clk.tic_base = 0;
		    called = 0;
		    break;
		}
		else
		    sk98_clk.nextdrift = nictstamp + (unsigned long) (((drand48() * 8) + 1) * sk98_clk.drift_period);

	    }
	    
	    if(sk98_clk.mode == 2)
		break;
	    
	    if(sk98_clk.tic_base == 0)
	    {
		// reset everything, in case triggered by skipping past stale packets
		sk98_clk.tic_base = sk98_clk.tic_cur = sk98_clk.tic_last = nictstamp;
		sk98_clk.nextdrift = nictstamp + (unsigned long) (((drand48() * 8) + 1) * sk98_clk.drift_period);
		sk98_clk.samples = 0;
		sk98_clk.sum = 0;
		sk98_clk.sum2 = 0;
		sk98_clk.tsum = 0;
		sk98_clk.tsum2 = 0;
		sk98_clk.psum = 0;
		sk98_clk.min = 0;
		sk98_clk.max = 0;
		sk98_clk.time_base = now;
	    }
	    else
	    {
		struct timeval est_time;
		int sec, usec;
		double time, tdiff;

		sk98_getTime(nictstamp, &est_time, NULL);

		if(est_time.tv_sec > now.tv_sec || (est_time.tv_sec == now.tv_sec && est_time.tv_usec > now.tv_usec))
		{
		    sec = est_time.tv_sec - now.tv_sec;
		    usec = est_time.tv_usec - now.tv_usec;

		    if(usec < 0)
		    {
			sec--;
			usec += 1000000;
		    }
		    time = sec + (double) usec / 1000000.0;
		}
		else
		{
		    sec = now.tv_sec - est_time.tv_sec;
		    usec = now.tv_usec - est_time.tv_usec;

		    if(usec < 0)
		    {
			sec--;
			usec += 1000000;
		    }
		    time = - (sec + (double) usec / 1000000.0);
		}		

		// time going backwards, so assume stale packets
		if(time > sk98_clk.max + 0.0005)
		{
		    // bin all samples and start again
		    fprintf(stderr, "%d: Losing %d samples - stale packets?\n", sk98_clk.chan, sk98_clk.samples);
		    
		    sk98_clk.tic_base = 0;
		    break;
		}
		
		// time added through a deschedule or other delay between nictstamp and gettimeofday
		if(time < sk98_clk.min - 0.0005)
		{
		    // bin this sample
		    sk98_clk.binned++;
		    if((sk98_clk.binned % 20) == 19)
		    {
		        fprintf(stderr, "%d: Consistently binning samples.  May never calibrate.\n", sk98_clk.chan);
		    	sk98_clk.tic_base = 0;
		    }
		    break;
		}

		sk98_clk.binned = 0;

		sk98_clk.samples++;
		sk98_clk.sum += time;
		sk98_clk.sum2 += (time * time);

		tdiff = (double) (now.tv_sec - sk98_clk.time_base.tv_sec) + ((double) (now.tv_usec - sk98_clk.time_base.tv_usec) / 1000000.0);

		sk98_clk.tsum += tdiff;
		sk98_clk.tsum2 += (tdiff * tdiff);
		sk98_clk.psum += tdiff * time;

		if(sk98_clk.samples == 1 || sk98_clk.min > time)
		    sk98_clk.min = time;
		if(sk98_clk.samples == 1 || sk98_clk.max < time)
		    sk98_clk.max = time;

		//fprintf(stderr, "%d: %d.%06d Timer %g range %g, ave %g (n%d)\n",
		//	sk98_clk.chan, now.tv_sec, now.tv_usec, time, sk98_clk.max - sk98_clk.min,
		//	sk98_clk.sum / (double) sk98_clk.samples, sk98_clk.samples);


		if(sk98_clk.samples >= 80)
		{
		    double s_xx = sk98_clk.tsum2 - (sk98_clk.tsum * sk98_clk.tsum / (double) sk98_clk.samples);
		    double s_xy = sk98_clk.psum - (sk98_clk.tsum * sk98_clk.sum / (double) sk98_clk.samples);
		    double grad = s_xy / s_xx;

		    double m_x = sk98_clk.tsum / (double) sk98_clk.samples;
		    double m_y = sk98_clk.sum / (double) sk98_clk.samples;
		    double inter =  m_y - (grad * m_x);

		    int adj = (int) ((inter * 1e6) + 0.5); 

		    //fprintf(stderr, "x: %g %g   y: %g %g\n", sk98_clk.tsum, sk98_clk.tsum2, sk98_clk.sum, sk98_clk.sum2);

		    if(sk98_clk.max - sk98_clk.min > 0.001)
		    {
			fprintf(stderr, "\n\nXXXX: %d: setting timer based on shifting clock (%g ms)\n\n",
				sk98_clk.chan, 1000.0 * (sk98_clk.max - sk98_clk.min));
		    }
		    else
		    {
			fprintf(stderr, "%d: calibrating timer (span %g us), adj %d us.  Apparent tics = %lu (current %lu)\n",
				sk98_clk.chan, 1e6 * (sk98_clk.max - sk98_clk.min), adj,
				(unsigned long) (((1.0 + grad) * (double) sk98_clk.nictstamp_freq)+0.5), sk98_clk.nictstamp_freq);
		    }


		    if(adj > 0)
		    {
			while(adj > 1e6)
			{
			    sk98_clk.time_base.tv_sec--;
			    adj -= 1e6;
			}
			sk98_clk.time_base.tv_usec -= (int) adj;

			//fprintf(stderr, "+ave: %g  secinc: %d  usecinc: %d\n", ave, (int) ave, 
			//(int) ((ave - (double) ((int) ave)) * 1000000.0));

			if(sk98_clk.time_base.tv_usec < 0)
			{
			    sk98_clk.time_base.tv_sec--;
			    sk98_clk.time_base.tv_usec += 1000000;
			}
		    }
		    else
		    {
			int sadj;

			adj = -adj;

			sadj = (int) (adj / 1e6);

			//fprintf(stderr, "-ave: %g  secinc: %d  usecinc: %d\n", ave, (int) ave, 
			//(int) ((ave - (double) ((int) ave)) * 1000000.0));

			sk98_clk.time_base.tv_sec += sadj;
			sk98_clk.time_base.tv_usec += (int) (adj - (double) sadj * 1e6);
				
			if(sk98_clk.time_base.tv_usec > 1000000)
			{
			    sk98_clk.time_base.tv_sec++;
			    sk98_clk.time_base.tv_usec -= 1000000;
			}
		    }

		    if(drift_file)
		    {
			data[0] = 0;
			data[1] = (unsigned long) sk98_clk.time_base.tv_sec;
			data[2] = (unsigned long) sk98_clk.time_base.tv_usec;
			data[3] = 0;
			data[4] = 0;
			data[5] = sk98_clk.chan; //st->dev_num;
			
			//fprintf(stderr, "%d: Wrote drift: %lu %lu %lu %d.%06d\n", st->dev_num, data[0], data[1], data[2], sk98_clk.time_base.tv_sec, sk98_clk.time_base.tv_usec);
		
			fwrite(data, sizeof(unsigned long), 6, drift_file);
			fflush(drift_file);

			sk98_clk.mode = 2;
		    }
		    else
			sk98_clk.mode = 3;

		    ret = 1;
		}
	    }
	}
	break;

    case 3:
	break;
    }

    return ret;
}
    


// returns zero if in initial startup period
inline unsigned long long sk98_maptotimeofday( unsigned int nictstamp)
{
    //fprintf(stderr, "%lu\n", nictstamp);
    
    if(sk98_clk.mode < 2)
    {
	sk98_doTimer(nictstamp);
	return 0;
    }
    else
    {
	struct timeval tv;

	sk98_getTime(nictstamp, &tv, NULL);

	return (unsigned long long) tv.tv_sec * 1000000 + (unsigned long long) tv.tv_usec;
    }
}
