/*
Copyright (c) 2000,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	  : TTMDelayData.C
Author	    	  : Rene Wilhelm
Date	      	  : 13-OCT-2000
Revised		  : 20-JUN-2001, better object model, new methods
Revised		  : 13-AUG-2001, TestBox has become a class
Description       : Implementation of TTMDelayData class (create chain of
		    ROOT TTree files for specific measurement period)
Language Version  : C++
OSs Tested	  : Solaris 2.6
$Id: TTMDelayData.C,v 1.6 2003/05/16 13:57:41 ttraffic Exp $
-------------------------------------------------------------------------------
*/

#include "TTMDelayData.h"
#include "TTMStatistics.h"
#include "TTMHistograms.h"
#include "CSVdump.h"
#include "utils.h"

#include "Delay.h"
#include "TFile.h"
#include "TTree.h"
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

#include <iostream.h>



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


/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	:     TTMDelayData constructor 
Side Effects    :     initialize private data members
Comments	:
-------------------------------------------------------------------------------
*/

TTMDelayData::TTMDelayData() {

	chain = NULL;
	dataPath = NULL;
	sumTree = NULL;

	id2index = NULL;
	delayStats = NULL;
	delayHistos = NULL;
	csv = NULL;

	dataProcessed = 0;
	maxBoxId = 0;
	numIntervals = 0;
	endTime = 0;
	timePeriod = 0;
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	:   TTMDelayData destructor
Side Effects    :   deletes ROOT TChain + dynamically created datapath string
Comments	:
-------------------------------------------------------------------------------
*/

TTMDelayData::~TTMDelayData() {

	if (chain != NULL)	 delete chain;

	if (csv != NULL)	 delete[] csv;
	if (id2index != NULL)	 delete[] id2index;
	if (delayStats != NULL)  delete[] delayStats;
	if (delayHistos != NULL) delete[] delayHistos;

}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose           :   Set TTM Configuration for this data chain
Input parameters  :   config - TestBoxConfig object, a list of all
                      TestBoxes which were actively participating in the 
                      measurement network at the end time of the data chain
Comments          :   also initializes internal bookkeeping parameters
-------------------------------------------------------------------------------
*/

void TTMDelayData::SetConfig(TTMOptions& opt, TestBoxConfig& conf, int numint) {
        
   
        tbConfig = &conf;
        progOptions = &opt;

	int oldNumBoxes = numBoxes;

	int oldNumIntervals = numIntervals;
	numIntervals = numint;


        // initialize data members from program options
        
        dataPath    = progOptions->InputPath();
        endTime     = progOptions->EndTime();
        outputDir   = progOptions->OutputPath();
        plotFormat  = progOptions->PlotFormat();
        storageType = progOptions->StorageType();
        timePeriod  = progOptions->TimePeriod();
	ipv6Chain   = (progOptions->IPversion() == 6);

        outputFile   = progOptions->OutputPath();

	struct stat statbuf;
        if (stat(outputFile, &statbuf) == 0) {
		// it exists

                if (S_ISDIR(statbuf.st_mode)) {
			// it is a directory, append default output file name
        		outputFile  = new char[strlen(outputDir) + 24];
			sprintf(outputFile, "%s/summary.root", outputDir);
                }
        }
        else if (progOptions->Verbose()) {
                perror(outputFile);
        }

        // Determine the corresponding Id to TestBox translation table
        // since array indices run 0..arraySize-1, we have to allocate one
        // more element to allow direct indexing with testbox ids

	maxBoxId = tbConfig->GetMaxBoxId();
        id2index = new int[maxBoxId+1]; 

        // initialize to -1  ( = box not in active list)
        for (uint i=0; i <= tbConfig->GetMaxBoxId(); i++) {
                id2index[i] = -1;
        }

        nSources = tbConfig->GetNumEntries();

        // fill the slots which correspond to active boxes and their targets
        // (essential when only one source box present in running config)
	
	// This whole mapping could probably be moved to the TestBox class
	// where a GetTargetIndex(targetId) method would return the 
	// position of targetId in targetList array.
	// It would mean that delayStats and delayHistos arrays
	// in TTMDelayData class would be created and dimensioned
	// anew for each source box. For now we stick with this 
	// less ideal "global" id2index mapping.  [RW 20030113]

        sourceList = tbConfig->GetTBList();
        int index = 0;
        for (int k=0; k < nSources; k++) {
                uint srcid = sourceList[k]->GetId();
                if (srcid > tbConfig->GetMaxBoxId()) {
                        cerr << progOptions->ProgramName()
                             <<"ERROR: source id "
                             << srcid << "out of range; expected 1-"
                             << tbConfig->GetMaxBoxId() << endl;

                }
                else {
                        if (id2index[srcid] < 0) {
                                // not yet defined
                                id2index[srcid] = index++;
                        }
                }

                // now check this box' targets

                int ntargets;
                TestBox **target = sourceList[k]->GetTargets(ntargets);
                for (int j=0; j < ntargets; j++) {
                        uint tgtid = target[j]->GetId();
                        if (tgtid > tbConfig->GetMaxBoxId()) {
                                cerr << progOptions->ProgramName()
                                     <<"ERROR: target id "
                                     << tgtid << "out of range; expected 1-"
                                     << tbConfig->GetMaxBoxId() << endl;
                        }

                        else {
                                if (id2index[tgtid] < 0) {
                                        // not yet defined
                                        id2index[tgtid] = index++;
                                }
                        }
                }
                
        }

        numBoxes = index;

        if (progOptions->Verbose()) {
                cout << "Found " << numBoxes
                     << " distinct boxes in src/tgt config" << endl;
        }


	if (((oldNumBoxes != numBoxes) && (oldNumBoxes != 0))  ||
	    ((oldNumIntervals != numIntervals) && (oldNumIntervals != 0))) { 
		// have to redo all analysis with different interval settings
		// delete current array with TTMStatistics

		if (delayStats != NULL) {
			delete[] delayStats;
			delayStats = NULL;
		}
	}


}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   create (new) ROOT TTree chain 
Comments	  :   one chain is created of ALL specified sources
        	  :   existing TChain of this object will be deleted
Future		  :   add code to Reset/Clear histograms for Delay Plots
-------------------------------------------------------------------------------
*/

void TTMDelayData::Chain(TestBox* source) {
	

#define BUFLENGTH 15

	char charbuf[BUFLENGTH];  // small buffer for temporary storage
	char *fullpath;
	struct tm *timestruct;
	time_t starttime, timestamp;	

	int    pathlen;		  // length of the *path string argument
	int    nfiles;		  // number of files to chain


// for all targets ...

	// initalize data members

	// dispose of old ROOT chain if it still exists
	if (chain != NULL) {
		delete chain;
	}

	// create ROOT chain
	chain = new TChain("tree");

	// copy path
	pathlen = strlen(dataPath);
	fullpath = new char[pathlen+100];  // leaves 80 char for date + boxname 
	strcpy(fullpath, dataPath);

	starttime = endTime - timePeriod;

	if (endTime % SECONDSPERDAY == 0) {
		// endtime on day boundary, push it forward a little
		// to get the correct data file
		timestamp = starttime + 5;  
	}
	else {
		timestamp = starttime;
	}

	// since endtime is in time_t (seconds since 1/1/1970) format
	// we can determine day numbers (since 1/1/1970) by simple integer
	// division; number of files to chain is difference between
	// end and start day.  Subtract and add 5 seconds respectively
	// to avoid rounding errors

	int endday = (endTime-5) / SECONDSPERDAY;
	int startday = (endTime-timePeriod+5) / SECONDSPERDAY;
	nfiles = (endday - startday) + 1;

	for (int i = nfiles; i>0; i--)
	{

		fullpath[pathlen] = '\0';  // truncate previous path

		timestruct = gmtime(&timestamp);
		if (storageType == 'H') {
			// append appropriate <year>/<month>/<day>
			strftime(charbuf, BUFLENGTH, "/%Y/%m/%d", timestruct);
			strcat(fullpath, charbuf);
		}
		strcat(fullpath, "/");

		if (ipv6Chain) {
			strcat(fullpath, "IPv6.");
		}
			
		// construct full filename from source name and timestruct
		strftime(charbuf, BUFLENGTH, ".%Y%m%d.root", timestruct);
		strcat(fullpath, source->GetName());
		strcat(fullpath, charbuf);
			
        	if (progOptions->Verbose()) {
			cout << "fullpath: " << fullpath << endl;
		}

		// check if file exists before adding

		struct stat statbuf;
		if (stat(fullpath, &statbuf) < 0) {
			perror(fullpath);
		}
		else {
			chain->Add(fullpath);
		}
		timestamp += SECONDSPERDAY; // increase timestamp by a day
	}
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Process packets in TTM Data chain
		      Update TTM Statistics object(s)
Side Effects      :   
Comments	  :   Target list has been set by SetTargets() method
TODO		  :   Also fill histograms for later plotting

-------------------------------------------------------------------------------
*/
int TTMDelayData::ProcessPackets(TestBox* source, ttmdata type) {
	
	int index;
	time_t starttime = endTime - timePeriod;

	if (chain == NULL) {
		cerr << "TTMDelayData::ProcessPackets : " << 
			"No data chain defined, please call " <<
			"Chain() method first" << endl;
		return(0);
	}

	InitStorage(source, type);
		
	// loop over all data

	Delay *delay = new Delay();
	chain->SetBranchAddress("delay", &delay);
	Int_t nMeasurements = (Int_t) chain->GetEntries();

	nProcessed = 0;
	for (Int_t eventNo=0; eventNo < nMeasurements; eventNo++) {
		if (chain->GetEvent(eventNo) < 0) {
			// problem reading this "event"?
			continue;
		}

		Int_t targetId = delay->GetTargetId();
		if ( (targetId <= (int)maxBoxId) && (targetId > 0)) {
                	// valid targetId
        		if ((index = id2index[targetId]) < 0) {
				continue;  // out of range, no target, skip
			}
	


               		// part of requested targets list
			if (delay->GetSourceId() == (Int_t) source->GetId()) {
				// packet from requested source

				if (type == delaystats) {
					delayStats[index].Update(delay, starttime);
				}
				else if (type == delayhist) {
					delayHistos[index].Fill(delay);
				}
				else if (type == csvdump) {
					csv[index].Dump(delay);
				}

				nProcessed++;
			}
		}
	}

       	if (progOptions->Verbose()) {
		cout << "Source Box " << source->GetId() << ": " << nProcessed
		     << " measurements processed" << endl;
	}

	dataProcessed = 1;

	return(nProcessed);

}


/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Initialize storage for data processing
		      Create Arrays, Histograms etc.
Side Effects      :   

-------------------------------------------------------------------------------
*/
void TTMDelayData::InitStorage(TestBox* source, ttmdata type) {


	int i;


 	// Create Statistics, Histogram or CSV objects for all boxes
 	// in TB config  (the superset of all selected sources
 	// and their targets);

	if (type == delaystats) {

		if (delayStats == NULL) {
			// create new objects

			delayStats = new TTMStatistics[numBoxes];
			for (i=0; i<numBoxes; i++) {
				delayStats[i].SetNumIntervals(numIntervals);
			}
		}
		else {
			// current allocation is fine,
			// just reset statistics counters

			for (i=0; i<numBoxes; i++) {
				delayStats[i].Reset();
			}
		}
	}
	
	// Histograms

	else if (type == delayhist) {
	
		int ntargets;
		TestBox** target = source->GetTargets(ntargets);

		if (delayHistos != NULL) {
			// delete old Histograms
			delete[] delayHistos;
		}

		delayHistos = new TTMHistograms[numBoxes];

		for (i=0; i<ntargets; i++) {
			int k = id2index[target[i]->GetId()];
			delayHistos[k].SetConfig(source, i, progOptions);
		}
	}

	else if (type == csvdump) {

		// CSV Output

		int ntargets;
		TestBox** target = source->GetTargets(ntargets);

		if (csv != NULL) {
			// delete old Histograms
			delete[] csv;
		}

		csv = new CSVdump[numBoxes];

		for (i=0; i<ntargets; i++) {
			int k = id2index[target[i]->GetId()];

			char *prefix = "";
			if (ipv6Chain) {
				prefix = "IPv6.";
			}
			csv[k].Open(outputDir, source->GetShortName(),
				target[i]->GetShortName(), prefix, delays);
		}
	}

}


/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	 :   Create summary ROOT file for this DelayData set
Descpription	 :   For each source box chain the data files for 
		     configured dates and output the statistics to
		     the summary TTree file

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

void TTMDelayData::Summarize() {
	

	// Create ROOT TTree

	Int_t split = 1;
	Int_t bsize = 64000;

	TFile *sumfile = new TFile (outputFile, "RECREATE");
	sumTree  = new TTree ("TTMSummary", "TTM DelaySummary");

       	if (progOptions->Verbose()) {
		cout << "Opened " << outputFile << " for Summary tree" << endl;
	}
		
	summary = new DelaySummary();
   	sumTree->Branch ("summary", "DelaySummary", &summary, bsize, split);


	for (int i=0; i<nSources; i++) {

		Chain(sourceList[i]);
		if (ProcessPackets(sourceList[i], delaystats) != 0) {
			// some delay data found
			FillSummaryTree(sourceList[i], sumfile);
		}
	}

	sumfile->Close();
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	 :   Create delay Plots for this DelayData set
Descpription	 :   For each source box chain the data files for 
		     configured dates and create the plots in
		     the output directory

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

void TTMDelayData::Plot() {
	

	for (int i=0; i<nSources; i++) {

		Chain(sourceList[i]);
		if (ProcessPackets(sourceList[i], delayhist) != 0) {
			// some delay data found
			PlotHistos(sourceList[i]);
		}
	}
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	 :   Output data in CVS format
Descpription	 :   For each source box chain the data files for 
		     configured dates and dump data to a file

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

void TTMDelayData::OutputCSV() {
	

	for (int i=0; i<nSources; i++) {

		Chain(sourceList[i]);
		ProcessPackets(sourceList[i], csvdump);
	}
}

		
/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	 :   Fill summary Tree with collected statistics
Input		 :   source - Source Testbox
		     hfile  - Pointer to ROOT TTree file

For each target box output the percentile levels for five intervals:

	HRS   0 -  6
	HRS   6 - 12
	HRS  12 - 18
	HRS  18 - 24
	HRS   0 - 24

If the chain has multiple days worth of data, the intervals accumulate
data for the whole period. I.e. the median in the 1st interval represents
the median of all measurements that took place between 0 and 6 UTC in *any*
of the days of the chained period!

Also output the loss rates for the specific intervals 

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

void TTMDelayData::FillSummaryTree(TestBox* source, TFile* hfile) {
		

	int ntargets;

        TestBox** target = source->GetTargets(ntargets);

	int nfills = 0;

	for (int i=0; i<ntargets; i++) {

		uint targetId = target[i]->GetId();
		int k = id2index[targetId];

		for (int j=0; j<=numIntervals;j++) {

			summary = new DelaySummary();

			summary->SetSourceId (source->GetId());
			summary->SetTargetId (targetId);
			summary->SetBinNo(j);

			summary->SetNumDays  ((timePeriod+5)/SECONDSPERDAY);

			Double_t binsize, timelow;

			if (j==0) {
				// interval 0 summarizes whole period
				binsize = timePeriod;
				timelow = endTime-timePeriod;
			}
			else {
				// other intervals cover specific part of day
				// It's hard to find proper value of binsize
				// because each bin <j> is in fact a collection
				// of <numdays> smaller bins.
				// <timelow> for such a bin is even harder
				// to define. For now we stick with these.

				binsize = SECONDSPERDAY/numIntervals;
				timelow = endTime - timePeriod + (j - 1) * binsize;
			}

			summary->SetBinSize (binsize);
			summary->SetTimeLow (timelow);
			summary->SetNumEntries(delayStats[k].GetNumPacketsValid(j)); 
			summary->SetLevel025(delayStats[k].GetDelayPerc(j, 2.5));
			summary->SetMedian  (delayStats[k].GetDelayPerc(j, 50));
			summary->SetLevel975(delayStats[k].GetDelayPerc(j, 97.5));
			summary->SetLoss    (delayStats[k].GetLossRate(j));

			sumTree->Fill(); // put the above object in the tree
			nfills++;

			delete summary;  // can't be reused by ROOT
		}
	}
		

	hfile->Write ();

/*
	cout << "Source Box : " << source->GetId() <<endl<<endl;

	for (int i=0; i<nTargets; i++) {
		uint targetId = target[id]->GetId();
		int k = id2index[targetId];
		cout <<"Target: "  << target[k].GetId() <<endl;
		cout <<"Valid Packets: " << delayStats[k].GetNumPacketsValid(0); 
		for (int j=1; j<=numIntervals;j++) {
			cout << " " << delayStats[k].GetNumPacketsValid(j);
		}
		cout <<endl;
		cout <<"Loss Rate: " << delayStats[k].GetLossRate(0); 
		for (int j=1; j<=numIntervals;j++) {
			cout << " " << delayStats[k].GetLossRate(j);
		}
		cout <<endl;
		cout <<"Median: " << delayStats[k].GetDelayPerc(0, 50); 
		for (int j=1; j<=numIntervals;j++) {
			cout << " " << delayStats[k].GetDelayPerc(j, 50);
		}
		cout <<endl;
	}
	
*/
	return;
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	  	 :   Plot the delay histograms objects
Input		 :   source - Source Testbox


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

void TTMDelayData::PlotHistos(TestBox* source) {

	int ntargets;

        TestBox** target = source->GetTargets(ntargets);

	for (int i=0; i<ntargets; i++) {

		int k = id2index[target[i]->GetId()];
		delayHistos[k].Plot();
	}
}
