/*
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	  : TTMSummaryChain.C
Author	    	  : Rene Wilhelm
Date	      	  : 21-SEP-2001
Revised		  : 01-AUG-2002, added XML summary output 
Revised		  : 10-AGU-2002, added CSV output format for plots
Description       : TTMSummaryChain class definition (create chain of
		    ROOT files with TTM summary data for specific period)
Language Version  : C++
OSs Tested        : Solaris 2.6, Debian Linux 2.2
$Id: TTMSummaryChain.C,v 1.7 2003/06/02 10:42:18 ruben Exp $
-------------------------------------------------------------------------------
*/

#include "TTMSummaryChain.h"
#include "Percentiles.h"
#include "Delay.h"
#include "utils.h"

#include <TStyle.h>
#include <TFile.h>
#include <TTree.h>
#include <TCanvas.h>
#include <TLine.h>
#include <TPaveLabel.h>
#include <TPostScript.h>
#include <TText.h>

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>

#include <fstream.h>
#include <iostream.h>
#include <iomanip.h>


static char const rcsid[] = "$Id: TTMSummaryChain.C,v 1.7 2003/06/02 10:42:18 ruben Exp $";
static char const *rcsid_p = rcsid;   // Prevent g++ from optimizing out rcsid


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

TTMSummaryChain::TTMSummaryChain() {

	sumChain = NULL;
	dataPath = NULL;
	outputDir = NULL;
	sumFile = NULL;
	sumTree = NULL;

	id2index = NULL;

	fileProcessed = 0;
	chainProcessed = 0;
	plotHistoFilled = 0;
	endTime = 0;
	timePeriod = 0;
}

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

TTMSummaryChain::~TTMSummaryChain() {

	delete sumChain;
	delete[] id2index;

	if (sumFile != NULL) {
		// close ROOT file
		sumFile->Close();
	}
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   create (new) ROOT TTree chain from input parameters
Side Effects      :   initialize or reset private data members
Input parameters  :   end - endtime of selected data
		      per - timeperiod of selected data
		      path - pathname to TTM ROOT datastorage
		      stype - type of datastorage, 'H' = hierarchical yyyy/mm/dd
Comments	  :   existing TChain of this object will be deleted
		      Input parameters are taken from ProgramOptions
Future		  :   add code to Reset/Clear histograms for Delay Plots
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::Chain() {


#define BUFLENGTH 15

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


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

	if (sumFile != NULL) {
		// close last day summary ROOT file
		sumFile->Close();
	}

	// create ROOT chain
	sumChain = new TChain("TTMSummary");

	// copy path

	int pathlen = strlen(dataPath);
	fullpath = new char[pathlen+1+100]; 
	strcpy(fullpath, dataPath);

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

	// 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

	// SummaryPeriod determines which files to chain:
	// statistics for one day or for entire 7 day period
	// in the latter case we chain every 7th file

	int numdays = (progOptions->SummaryPeriod() == week) ? 7 : 1;

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

	for (int i = numChainFiles; 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);
		}

		if (ipVersion == 6) {
			strcat(fullpath, "/IPv6.");
		}
		else {
			strcat(fullpath, "/");
		}

		if (progOptions->SummaryPeriod() == week) {
			strcat(fullpath, "weeksummary");
		}
		else {
			strcat(fullpath, "summary");
		}

		strftime(charbuf, BUFLENGTH, ".%Y%m%d.root", timestruct);
		strcat(fullpath, charbuf);
			
		if (progOptions->Verbose()) {
			cout << "fullpath: " << fullpath << endl;
		}

		struct stat statbuf;
		if (stat(fullpath, &statbuf) < 0) {
			perror(fullpath);
		}
		else {
			sumChain->Add(fullpath);
			if (i == numChainFiles) {
				// last day in chain may require additional
				// processing; open separate TFile for that

				sumFile = new TFile(fullpath, "READ");
				sumTree = (TTree *) sumFile->Get("TTMSummary");
			}
		}
		timestamp -= SECONDSPERDAY * numdays; // decrease timestamp 

	}
}

/*
-------------------------------------------------------------------------------
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 TTMSummaryChain::SetConfig(TTMOptions& opt, TestBoxConfig& conf, int numint) {
	
	tbConfig = &conf;
	progOptions = &opt;
	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();
	ipVersion   = progOptions->IPversion();

	// 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

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

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

	int numsources = tbConfig->GetNumEntries();

	// fill the slots which correspond to active boxes and their targets
	// (essential when only one source box present in running config)

	TestBox **box = tbConfig->GetTBList();
	int index = 0;
	for (int k=0; k < numsources; k++) {
		uint srcid = box[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 = box[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;
	}

	// sizes known, set dimensions of the DynamicArray objects
	// Note: next to the numInt intervals we have interval 0 
	// which holds statistics for the entire day, thus have to
	// allocate numIntervals+1 slots for each source-target.

	shortTermMedian.SetSize(numBoxes, numBoxes, numIntervals+1);
	shortTermLevel025.SetSize(numBoxes, numBoxes, numIntervals+1);
	shortTermLevel975.SetSize(numBoxes, numBoxes, numIntervals+1);
	shortTermLossRate.SetSize(numBoxes, numBoxes, numIntervals+1);

	longTermMedian.SetSize(numBoxes, numBoxes, numIntervals+1);
	longTermLevel025.SetSize(numBoxes, numBoxes, numIntervals+1);
	longTermLevel975.SetSize(numBoxes, numBoxes, numIntervals+1);
	longTermLossRate.SetSize(numBoxes, numBoxes, numIntervals+1);

	// simple arrays are initialized to -1; 
	// Percentile objects methods return -1 when no data is available

	shortTermMedian.InitValue(-1);
	shortTermLevel025.InitValue(-1);
	shortTermLevel975.InitValue(-1);
	shortTermLossRate.InitValue(-1);

}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Intialize Histogram Objects (set size, boundaries, etc.)
Note		  :   This internal routine can only be called after the
		      chain's time parameters are known.
-------------------------------------------------------------------------------
*/

int TTMSummaryChain::InitHistograms() {

	if (endTime == 0) {
		cerr << "InitHistograms: Chain not yet initialized!" << endl;
		return(-1);
	}

	if (plotFormat == csv) {
		// csv doesn't need histograms
		return(0);
	}

	// create the arrays

	// since these arrays are only usefull if TTMSummary:Plots
	// method is called, this isn't done in SetConfig method
	// to avoid unnecessary memory consumption

	// NOTE: due to the size of each histogram object
	// we cannot allocate histograms for all NxN combinations
	// the Plot code will handle one source box a time
	
	medianHist1D.SetSize(numBoxes, numIntervals+1);;
	level025Hist1D.SetSize(numBoxes, numIntervals+1);;
	level975Hist1D.SetSize(numBoxes, numIntervals+1);;

	medianHist2D.SetSize(numBoxes, numIntervals+1);;
	level025Hist2D.SetSize(numBoxes, numIntervals+1);;
	level975Hist2D.SetSize(numBoxes, numIntervals+1);;

	// now create the histograms, six per interval per one-way connection
	// Note: no histo's made for interval 0 = whole day


	char buf[50], title[50];

	TH1F *temp1;
	TH2F *temp2;

	time_t starttime = endTime - timePeriod;

	time_t tlow = starttime - ROOT_TIMEZERO;
	time_t thigh = endTime - ROOT_TIMEZERO;

	int id=0;
	float mindelay = progOptions->MinDelay();	
	float maxdelay = progOptions->MaxDelay();	

	int binsX = 600;
	int binsY = 250;

	//  Note: in a non-full mesh we are still overdoing memory allocation
	//  a bit when booking nBoxes & numIntervals sets of histograms:
	//  only source -> numberoftargets would be needed.

	//  for now we ignore it, but it's definitely something to
	//  revisit when the ratio numTargets / numBoxes becomes small

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

                	sprintf(buf, "Level025_1D[%d]", id);
                	sprintf(title, "2.5%% Level");
			temp1 = new TH1F(buf, title, binsX, mindelay, maxdelay);	
			level025Hist1D.SetElement(j,k, temp1);

                	sprintf(buf, "Median_1D[%d]", id);
                	sprintf(title, "Median");
			temp1 = new TH1F(buf, title, binsX, mindelay, maxdelay);	
			medianHist1D.SetElement(j,k, temp1);

                	sprintf(buf, "Level975_1D[%d]", id);
                	sprintf(title, "97.5%% Level");
			temp1 = new TH1F(buf, title, binsX, mindelay, maxdelay);	
			level975Hist1D.SetElement(j,k, temp1);

                	sprintf(buf, "Level025_2D[%d]", id);
                	sprintf(title, "2.5%% Level vs. time");
			temp2 = new TH2F(buf, title, binsX, tlow,
				thigh, binsY, mindelay, maxdelay);	
			level025Hist2D.SetElement(j,k, temp2);

                	sprintf(buf, "Median_2D[%d]", id);
                	sprintf(title, "Median  vs. time");
			temp2 = new TH2F(buf, title, binsX, tlow,
				thigh, binsY, mindelay, maxdelay);	
			medianHist2D.SetElement(j,k, temp2);

                	sprintf(buf, "Level975_2D[%d]", id);
                	sprintf(title, "97.5%% Level  vs. time");
			temp2 = new TH2F(buf, title, binsX, tlow,
				thigh, binsY, mindelay, maxdelay);	
			level975Hist2D.SetElement(j,k, temp2);

			// etc.

			id++;
			if (progOptions->Verbose()) {
				cerr << "id " << id << " " << j << "," <<  k << "   6 histos booked" << endl;
			}
		}
	}
	

	return(0);
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Reset Histogram Objects (no data, not filled)
Note		  :   This routine should only be called after
		      histograms have been created
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::ResetHistograms() {
	
	if (plotFormat == csv) {
		// csv doesn't need histograms
		return;
	}

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

			level025Hist1D.GetElement(j,k)->Reset();
			medianHist1D.GetElement(j,k)->Reset();
			level975Hist1D.GetElement(j,k)->Reset();
			level025Hist2D.GetElement(j,k)->Reset();
			medianHist2D.GetElement(j,k)->Reset();
			level975Hist2D.GetElement(j,k)->Reset();

		}
	}
	
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Process data in the chain
		      Fill histograms for specified source
-------------------------------------------------------------------------------
*/

int TTMSummaryChain::FillHistos(uint boxId) {
                // process all data in chain, fill Percentiles etc.
                // for all data in Chain
                //      get target ID, sourceID
                //      when sourceID matches fill Histograms

	if (sumChain == NULL) {
		cerr << "ROOT Chain undefined; please initialize by calling Chain method\n";
		return(-1);
	}

	if (plotFormat == csv) {
		// csv doesn't need histograms
		return(0);
	}

	DelaySummary *summary = new DelaySummary();
	sumChain->SetBranchAddress("summary", &summary);

	Int_t nEntries = (Int_t) sumChain->GetEntries();

	int src = id2index[boxId];
	if (src < 0) {
		cerr << "FillHistos: invalid or inactive source ID: " << boxId
			<< endl;
		return (-1);
	}

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

		int src, dest;

		int bin = summary->GetBinNo();
		uint targetid = summary->GetTargetId();
		uint sourceid = summary->GetSourceId();

		// silently skip if not requested source or
		// IDs out of range Ids  (inactive or invalid boxes)

		if ((sourceid != boxId) || (targetid <= 0) ||
		    (targetid > tbConfig->GetMaxBoxId())   ||
		    ((dest = id2index[targetid]) < 0)      ||
		    ((src  = id2index[sourceid]) < 0)      ||
		    (bin > numIntervals) || (bin < 1)) {

				continue;
		}

		// Got here, so data are valid

		Float_t lev025 = summary->GetLevel025();
		Float_t median = summary->GetMedian();
		Float_t lev975 = summary->GetLevel975();
	        Float_t bintime = summary->GetTimeLow() +
				  summary->GetBinSize()/2 - ROOT_TIMEZERO;

		// values -1 indicate 'no data available'

		if ((lev025 > 0) && (median > 0) && (lev975 > 0)) {

			// Fill Histograms
			level025Hist1D.GetElement(dest,bin)->Fill(lev025);
			level025Hist2D.GetElement(dest,bin)->Fill(bintime, lev025);
			medianHist1D.GetElement(dest,bin)->Fill(median);
			medianHist2D.GetElement(dest,bin)->Fill(bintime, median);
			level975Hist1D.GetElement(dest,bin)->Fill(lev975);
			level975Hist2D.GetElement(dest,bin)->Fill(bintime, lev975);

			// ... and percentiles
 			longTermLevel025.GetElement(src,dest,bin).Add(lev025);
			longTermMedian.GetElement(src,dest,bin).Add(median);
 			longTermLevel975.GetElement(src,dest,bin).Add(lev975);

			nProcessed++;
		}
	}
	return(nProcessed);
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Process all data in the chain
		      Fill percentiles
-------------------------------------------------------------------------------
*/

int TTMSummaryChain::ProcessChain() {
                // process all data in chain, fill Percentiles etc.
                // for all data in Chain
                //      get target ID, sourceID
                //      translate to indexes i,j
                //      update Percentiles[i,j]

	if (sumChain == NULL) {
		cerr << "ROOT Chain undefined; please initialize by calling Chain method\n";
		return(-1);
	}

	DelaySummary *summary = new DelaySummary();
	sumChain->SetBranchAddress("summary", &summary);

	Int_t nEntries = (Int_t) sumChain->GetEntries();

	int src, dest;
	int nProblem = 0;
	int nProcessed = 0;
	for (Int_t eventNo=0; eventNo < nEntries; eventNo++) {
		if (sumChain->GetEvent(eventNo) < 0) {
			// problem reading this "event"?
			nProblem++;
			continue;
		}


		int bin = summary->GetBinNo();
		uint targetid = int(summary->GetTargetId());
		uint sourceid = int(summary->GetSourceId());

		// silently skip out of range Ids  (inactive or invalid boxes)

		if ((targetid <= tbConfig->GetMaxBoxId()) &&
		    (sourceid <= tbConfig->GetMaxBoxId()) &&
		    (targetid > 0) && (sourceid > 0) 	  &&
		    (bin <= numIntervals) && (bin >= 0)   &&
		    ((src  = id2index[sourceid]) >= 0)    &&
		    ((dest = id2index[targetid]) >= 0)) {
			
			longTermMedian.GetElement(src,dest,bin).Add(summary->GetMedian());
 			longTermLevel025.GetElement(src,dest,bin).Add(summary->GetLevel025());
 			longTermLevel975.GetElement(src,dest,bin).Add(summary->GetLevel975());
 			longTermLossRate.GetElement(src,dest,bin).Add(summary->GetLoss());
	
			if (progOptions->Verbose()) {
				cout << "Source " << sourceid << "= index " << src << endl;
				cout << "Target " << targetid << "= index " << dest << endl;
				cout << "median " << summary->GetMedian() << endl;
			}

			nProcessed++;

		}
		else if (progOptions->Verbose()) {
			cout << "Event " << eventNo << " skipped: " << endl;
			cout << "   sourceid:  " << sourceid << endl;
			cout << "   targetid:  " << targetid << endl;
			cout << "   bin no:    " << bin << endl;
			cout << "   src index: " << src << endl;
			cout << "   dst index: " << dest << endl << endl;
		}
	}
	if (progOptions->Verbose()) {
		cout << "TTMSummaryChain: " << nEntries << " entries" << endl;
		cout << "TTMSummaryChain: " << nProblem << " problematic" << endl;
	}

	chainProcessed = 1;
	return(nProcessed);
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Process the data in the lastday file
Description	  :   For purposes of quick reference, the relevant
		      summary data from the last file in the Chain
		      are stored in an 3D array.
-------------------------------------------------------------------------------
*/

int TTMSummaryChain::ProcessFile() {

	if (sumFile == NULL) {
		cerr << "ROOT Chain undefined; please initialize by calling Chain method\n";
		return(-1);
	}

	DelaySummary *summary = new DelaySummary();
	sumTree->SetBranchAddress("summary", &summary);

	Int_t nEntries = (Int_t) sumTree->GetEntries();

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

		int src, dest;

		int bin = summary->GetBinNo();
		uint targetid = summary->GetTargetId();
		uint sourceid = summary->GetSourceId();

		// silently skip out of range Ids  (inactive or invalid boxes)

		if ((targetid <= tbConfig->GetMaxBoxId()) &&
		    (sourceid <= tbConfig->GetMaxBoxId()) &&
		    (targetid > 0) && (sourceid > 0) 	  &&
		    (bin <= numIntervals) && (bin >= 0)   &&
		    ((src  = id2index[sourceid]) >= 0)    &&
		    ((dest = id2index[targetid]) >= 0)) {
			
			shortTermMedian.SetElement(src, dest, bin, summary->GetMedian());
 			shortTermLevel025.SetElement(src, dest, bin, summary->GetLevel025());
 			shortTermLevel975.SetElement(src, dest, bin, summary->GetLevel975());
 			shortTermLossRate.SetElement(src, dest, bin, summary->GetLoss());
		
			if (progOptions->Verbose()) {
				cout << "Source " << sourceid << "= index " << src << endl;
				cout << "Target " << targetid << "= index " << dest << endl;
				cout << "median " << summary->GetMedian() << endl;
			}

			nProcessed++;

		}
	}
	fileProcessed = 1;
	return(nProcessed);
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create XML output
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::OutputXML() {

	// make sure all data have been processed

	if (!chainProcessed) {

		int nProcessed = ProcessChain();
		if (nProcessed == 0) {
			cerr << "TTMSummaryChain: no data found in chain? \n";
			return;
		}
	}

	if (!fileProcessed) {
		int nProcessed = ProcessFile();
		if (nProcessed == 0) {
			cerr << "TTMSummaryChain: no data found in last day file? \n";
			return;
		}
	}

	// Further steps for this routine:
	//  output XML to individual files, color coding
	
	char *xmlfile = new char [strlen(outputDir)+40];   
	char timestring[50];

	time_t daytime = endTime  -5 ;  // push it few seconds before midnight

	strftime(timestring, sizeof(timestring), "%A %e %B %Y", localtime(&daytime));

	TestBox **box = tbConfig->GetTBList();
	for (int i=0; i < tbConfig->GetNumEntries(); i++) {

		// Loop over all boxes
		// One XML file per box

		char *prefix = "";

		if (ipVersion == 6) {
			prefix = "/IPv6.";
		}

		if (progOptions->SummaryPeriod() == week) {
			sprintf(xmlfile, "%s/%s/%sweeksummary.xml",
				outputDir, box[i]->GetShortName(), prefix);
		}
		else {
			sprintf(xmlfile, "%s/%s/%ssummary.xml",
				outputDir, box[i]->GetShortName(), prefix);
		}

		// open outputstream 
		ofstream xml(xmlfile);

		if (!xml) {
			cerr << "Couldn't open " << xmlfile 
			     << " for XML output" << endl;
			continue;
		}

		xml << "<?xml version='1.0' standalone='yes'?>" << endl;
		xml << "<delaysummary>" << endl;

		xml << "<source name=\"" << box[i]->GetName() << "\"/>" << endl;

		xml << "<param date=\"" << timestring << "\" " ;
		xml << "period=\"" ;

		if (progOptions->SummaryPeriod() == week) {
			xml << "week\" " ; 
		}
		else {
			xml << "day\" " ; 
		}
		xml << "shortterm=\"1\" " << "longterm=\"" << numChainFiles ;
		xml << "\"/>" << endl;

		xml.setf(ios::showpoint);

		int src = id2index[box[i]->GetId()];


		int numTargets;
		TestBox **target = box[i]->GetTargets(numTargets);
		for (int j=0; j < numTargets; j++) {

			double stl025, ltl025, stl500, ltl500, stl975, ltl975;
			float  stloss, ltloss;

			int dst = id2index[target[j]->GetId()];
			char* dstname = target[j]->GetShortName();
			xml << "<target name=\"" <<  dstname << "\">" << endl;

			// data coming in to 'src' box
			xml << "\t<incoming>" << endl;

			xml.precision(4);
			stl025 = shortTermLevel025.GetElement(dst,src,0);
			ltl025 = longTermLevel025.GetElement(dst,src,0).GetLevel(50) ;		
			xml << "\t\t<perc2.5 short=\"" << stl025 << "\" ";
			xml << "long=\"" << ltl025 << "\"/>" << endl;

			stl500 = shortTermMedian.GetElement(dst,src,0);
			ltl500 = longTermMedian.GetElement(dst,src,0).GetLevel(50) ;		
			xml << "\t\t<Median short=\"" << stl500 << "\" ";
			xml << "long=\"" << ltl500 << "\"/>" << endl;

			stl975 = shortTermLevel975.GetElement(dst,src,0);
			ltl975 = longTermLevel975.GetElement(dst,src,0).GetLevel(50) ;
			xml << "\t\t<perc97.5 short=\"" << stl975 << "\" ";
			xml << "long=\"" << ltl975 << "\"/>" << endl;

			xml.precision(3);
			stloss = shortTermLossRate.GetElement(dst,src,0);
			ltloss = longTermLossRate.GetElement(dst,src,0).GetLevel(50);
			xml << "\t\t<Loss short=\"" << stloss << "\" ";
			xml << "long=\"" << ltloss << "\"/>" << endl;

			xml << "\t</incoming>" << endl;

			// data going out from 'src' box
			xml << "\t<outgoing>" << endl;

			xml.precision(4);
			stl025 = shortTermLevel025.GetElement(src,dst,0);
			ltl025 = longTermLevel025.GetElement(src,dst,0).GetLevel(50) ;		
			xml << "\t\t<perc2.5 short=\"" << stl025 << "\" ";
			xml << "long=\"" << ltl025 << "\"/>" << endl;

			stl500 = shortTermMedian.GetElement(src,dst,0);
			ltl500 = longTermMedian.GetElement(src,dst,0).GetLevel(50) ;		
			xml << "\t\t<Median short=\"" << stl500 << "\" ";
			xml << "long=\"" << ltl500 << "\"/>" << endl;

			stl975 = shortTermLevel975.GetElement(src,dst,0);
			ltl975 = longTermLevel975.GetElement(src,dst,0).GetLevel(50) ;
			xml << "\t\t<perc97.5 short=\"" << stl975 << "\" ";
			xml << "long=\"" << ltl975 << "\"/>" << endl;

			xml.precision(3);
			stloss = shortTermLossRate.GetElement(src,dst,0);
			ltloss = longTermLossRate.GetElement(src,dst,0).GetLevel(50);
			xml << "\t\t<Loss short=\"" << stloss << "\" ";
			xml << "long=\"" << ltloss << "\"/>" << endl;

			xml << "\t</outgoing>" << endl;
			xml << "</target>" << endl;;
		}

		xml << "</delaysummary>" << endl;
		xml.close();
	}

}	

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create HTML output
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::OutputHTML(float triggerLevel) {

	// make sure all data have been processed

	if (!chainProcessed) {

		int nProcessed = ProcessChain();
		if (nProcessed == 0) {
			cerr << "TTMSummaryChain: no data found in chain? \n";
			return;
		}
	}

	if (!fileProcessed) {
		int nProcessed = ProcessFile();
		if (nProcessed == 0) {
			cerr << "TTMSummaryChain: no data found in last day file? \n";
			return;
		}
	}

	// Further steps for this routine:
	//  output HTML to individual files, color coding
	
	char *htmlfile = new char [strlen(outputDir)+40];   
	char timestring[50];

	time_t daytime = endTime  -5 ;  // push it few seconds before midnight

	strftime(timestring, sizeof(timestring), "%A %e %B %Y", localtime(&daytime));

	TestBox **box = tbConfig->GetTBList();
	for (int i=0; i < tbConfig->GetNumEntries(); i++) {

		// Loop over all boxes
		// One HTML file per box

		char *prefix = "";

		if (ipVersion == 6) {
			prefix = "/IPv6.";
		}

		if (progOptions->SummaryPeriod() == week) {
			sprintf(htmlfile, "%s/%s/%sweeksummary.html",
				outputDir, box[i]->GetShortName(), prefix);
		}
		else {
			sprintf(htmlfile, "%s/%s/%ssummary.html",
				outputDir, box[i]->GetShortName(), prefix);
		}

		// open outputstream 
		ofstream html(htmlfile);

		if (!html) {
			cerr << "Couldn't open " << htmlfile 
			     << " for HTML output" << endl;
			continue;
		}


		html << "<!--#set var=\"title\" value=\"" ;
		html << "TTM Summaries " << box[i]->GetName();
		html << "\"--><!--#include virtual=\"/ssi/ttmhead1.html\"-->";

		if (progOptions->SummaryPeriod() == week) {
			html << "<H1>TTM Week Summaries for " << box[i]->GetName(); 
		}
		else {
			html << "<H1>TTM Summaries for " << box[i]->GetName(); 
		}

		html << "</H1><H2>" << timestring << "</H2>" << endl;

		html << "<P>";
		html << "The table below displays the TTM delay &amp; loss ";
		html << "parameters for traffic received from and send to the ";
		html << "listed test-boxes by " <<  box[i]->GetName() << ". ";

		if (progOptions->SummaryPeriod() == week) {
			html << "Each cell lists both the result for the week ending at 24:00 on the above date ";
		}
		else {
			html << "Each cell lists both the result for the above date ";
		}
		html << "as well as (in parentheses) the median value ";
		html << "of this parameter for the last ";
		html << numChainFiles;

		if (progOptions->SummaryPeriod() == week) {
			html << " weeks. If the two differ by more than ";
		}
		else {
			html << " days. If the two differ by more than ";
		}
		html << triggerLevel * 100 ;
		html << "% the background of the cell will be colored: ";
		html << "<font color=#FF2222>red</font> when the parameter ";
		html << "increased, <font color=#22FF22>green</font> when it ";
		html << "decreased.";

		html << "<P>";
		html << "The hyperlinks in the table will take you to the ";
		html << "pages with the delay plots for the last 24 hours ";
		html << "7 days and 30 days, giving a more detailed look ";
		html << "at the data. For a graphical overview of ";
		html << "the last week results click ";
		html << "<A HREF=\"index.html\">here</A>";
		html << "<P><CENTER>";

		html.setf(ios::showpoint);

		html << "<TABLE BORDER=2 CELLSPACING=2 CELLPADDING=5>";
		html << "<TR><TH></TH>"; // empty heading
		html << "<TH Colspan=4>Incoming</TH>" ;
		html << "<TH></TH>"; // seperator
		html << "<TH Colspan=4>Outgoing</TH></TR>" << endl ;
		html << "<TR><TH></TH>"; // empty heading
		html << "<TH>2.5 perc.</TH><TH>median</TH><TH>97.5 perc.</TH>";
		html << "<TH>% Lost</TH>";
		html << "<TH>&nbsp;&nbsp;&nbsp;&nbsp;</TH>"; // seperator
		html << "<TH>2.5 perc.</TH><TH>median</TH><TH>97.5 perc.</TH>";
		html << "<TH>% Lost</TH></TR>";

		int src = id2index[box[i]->GetId()];
		char* srcname = box[i]->GetShortName();

		int numTargets;
		TestBox **target = box[i]->GetTargets(numTargets);
		for (int j=0; j < numTargets; j++) {

			double stl025, ltl025, stl500, ltl500, stl975, ltl975;
			float  stloss, ltloss;

			int dst = id2index[target[j]->GetId()];
			char* dstname = target[j]->GetShortName();

			html << "<TR ALIGN=CENTER><TD>" << dstname << "</TD>" ;


			// data coming in to 'src' box

			html.precision(4);
			stl025 = shortTermLevel025.GetElement(dst,src,0);
			ltl025 = longTermLevel025.GetElement(dst,src,0).GetLevel(50) ;		
			FillCell(dstname, srcname, stl025, ltl025, triggerLevel, html);
			stl500 = shortTermMedian.GetElement(dst,src,0);
			ltl500 = longTermMedian.GetElement(dst,src,0).GetLevel(50) ;		
			FillCell(dstname, srcname, stl500, ltl500, triggerLevel, html);
			stl975 = shortTermLevel975.GetElement(dst,src,0);
			ltl975 = longTermLevel975.GetElement(dst,src,0).GetLevel(50) ;
			FillCell(dstname, srcname, stl975, ltl975, triggerLevel, html);

			html.precision(3);
			stloss = shortTermLossRate.GetElement(dst,src,0);
			ltloss = longTermLossRate.GetElement(dst,src,0).GetLevel(50);
			FillCell(dstname, srcname, stloss, ltloss, triggerLevel, html, 100);


			html << "<TD></TD>" ; // seperator cell

			// data going out from 'src' box

			html.precision(4);
			stl025 = shortTermLevel025.GetElement(src,dst,0);
			ltl025 = longTermLevel025.GetElement(src,dst,0).GetLevel(50) ;		
			FillCell(srcname, dstname, stl025, ltl025, triggerLevel, html);
			stl500 = shortTermMedian.GetElement(src,dst,0);
			ltl500 = longTermMedian.GetElement(src,dst,0).GetLevel(50) ;		
			FillCell(srcname, dstname, stl500, ltl500, triggerLevel, html);
			stl975 = shortTermLevel975.GetElement(src,dst,0);
			ltl975 = longTermLevel975.GetElement(src,dst,0).GetLevel(50) ;
			FillCell(srcname, dstname, stl975, ltl975, triggerLevel, html);

			html.precision(3);
			stloss = shortTermLossRate.GetElement(src,dst,0);
			ltloss = longTermLossRate.GetElement(src,dst,0).GetLevel(50);
			FillCell(srcname, dstname, stloss, ltloss, triggerLevel, html, 100);


			html << "</TR>" << endl;

		}
		html << "</TABLE>";
		html << "</CENTER>";
		html << "<!--#include virtual=\"/ssi/ttmfoot1.html\"-->";

		html.close();
	}

}	

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create HTML output for one cell of the table
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::FillCell(char *src, char *dst, double shortterm,
	double longterm, float level, ofstream& html, int multiplier) {

	// values of -1 indicate no data found and are left blank
	// no colorcoding if either short or longterm value are undefined.

	// to avoid excessive number of colored cells in normal conditions
	// changes are only reported when the "bad" value is > 0.01
	// this only affects packet loss, which is passed to this routine
	// as a fractional number (0.01 = 1% loss, 
	// the "bad" loss rate must be > 0.01  (= 1%)

	if ((shortterm != -1) && (longterm != -1)) {
		if ((shortterm < (1-level)*longterm)  && (longterm > 0.01)) {
			// improved, green
			html << "<TD BGCOLOR=\"#22FF22\">";
		}
		else if ((shortterm > (1+level)*longterm) && (shortterm >0.01)) {
			// worsened, red
			html << "<TD BGCOLOR=\"#FF2222\">";
		}
		else {
			html << "<TD>";
		}
	}
	else {
		html << "<TD>";
	}

	if (shortterm != -1) {
		html << "<A TARGET=Plots HREF=\"" ;
//		html << src << "." << dst << ".html" ;   // static page
		html << "../plots.cgi?src=" << src << "&dst=" << dst 
		     << "&url=map_index.cgi";
		if (progOptions->SummaryPeriod() == week) {
			// point to 30 day plot
			html << "#month" ;
		}
		html << "\">";
		html << shortterm * multiplier << "</A><BR>";
	}
	else {
		html << "- <BR>" ;
	}

	if (longterm != -1) {
		html << "(" << longterm * multiplier << ") </TD>";
	}
	else {
		html << "(&nbsp;)</TD>";
	}

}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create plots for all boxes
Note		  :   format 'csv' outputs data in comma seperated text files 
-------------------------------------------------------------------------------
*/


void TTMSummaryChain::PlotAll () {

	if (InitHistograms() == -1) {
		cerr << "error";
	}

	int numsources = tbConfig->GetNumEntries();
	TestBox **box = tbConfig->GetTBList();
	for (int i=0; i<numsources; i++) {
		FillHistos(box[i]->GetId());

	        MakePlots(outputDir, box[i]);
		ResetHistograms();

	}
}


/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create plots for specified box from filled histos
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::MakePlots(char* plotdir, TestBox* source) {
	// Note: plotdir is usually a subdir of outputDir data member

        TPostScript *psout;
	int ntargets;


        // force thin lines in PS output to avoid cluttering histograms
        gStyle->SetLineScalePS(0.2);

	// turn off statistics boxes of histograms
	gStyle->SetOptStat(0);      

	char *prefix = "";
	if (ipVersion == 6) {
		prefix = "/IPv6.";
	}


        uint sourceid = source->GetId();
	TestBox** targets = source->GetTargets(ntargets);

	for (int i=0; i<ntargets; i++) {
                if (targets[i]->GetId() == sourceid) {
                        continue;       // nothing is send from box to itself :)
		}

		int len = strlen(plotdir) + strlen(source->GetShortName()) +
		      strlen(targets[i]->GetShortName()) + 20;

		char *pathname = new char [len];

                sprintf(pathname, "%s/%strends.%s.%s.", plotdir, prefix,
                	source->GetShortName(), targets[i]->GetShortName());

		if (plotFormat == csv) {
			strcat(pathname, "csv");
			OutputCSV(pathname, source, targets[i]);
		}
		else {

		        TCanvas *canvas = new TCanvas("canvas", "Plots", 0, 0, 1270, 990);
			if (plotFormat == eps) {

	                        // open eps file - type 113 in ROOT
				strcat(pathname, "eps");
       		                psout = new TPostScript(pathname, 113);
                	}
	                else {
	                        // open ps file  - type 112 = landscape
				strcat(pathname, "ps");
	                        psout = new TPostScript(pathname, 112);
				psout->Range(25.5, 18.8);   // centre on A4 paper
	                }
	                PlotHistos(source, targets[i]);
		        canvas->Update();
	                psout->Close();
       		        delete psout;

			if (plotFormat == gif) {
				// PS file complete, convert to GIF
				len = len + 64;
				char* command = (char *) malloc(len);
				sprintf(command, "ps2gif -b 41 28 574 750.75 -r -s 1.2 %s", pathname);
				if (system(command) != 0) {
       		                 	cerr << "ERROR converting to gif: " <<
						pathname << endl;
                		}
				else {
					// remove PS file, don't clutter disk space
					unlink(pathname);
				}
			}
			else if (plotFormat == pdf) {
				// now convert them to pdf; create ROOTps2pdf command line which
				// specificies all .ps files that need converting.
				//
				// BoundingBox parameters are taken from a manual run of the
				// bbfig tool; will be fine as long as ROOT PostScript prolog
				// does not change. 
				//
				// rotate to get proper orientation
				// scale up a bit to make text more legible
				//

				char *buffer   = new char[1024+strlen(PS2GIF)+1]; 
		 
				sprintf(buffer, "%s %s", PS2PDF, pathname);
		 
				// FIXME: need a verbose flag for this program
				//
				// if (verbose) {
				// 	cerr << "executing " << buffer << endl;
				// }
		 
				if (system(buffer) != 0) {
					cerr << "ERROR converting to pdf: " << pathname << endl;
				}
				else {
					// conversion OK -> cleanup ps (they can be real large)
					sprintf(buffer, "%s %s", "/bin/rm ", pathname);

					// FIXME: need a verbose flag for this program
					//
					// if (verbose) {
					// 	cerr << "executing " << buffer << endl;
					// }
		 
					if (system(buffer) != 0) {
						cerr << "ERROR removing ps file? " <<
						pathname <<endl;
					}
				}
				delete[] buffer;
			}
		delete[] pathname;
		}
	}
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Create CSV Output for specified source, target
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::OutputCSV(char *path, TestBox* source, TestBox* target) {


	if (sumChain == NULL) {
		cerr << "ROOT Chain undefined; please initialize by calling Chain method\n";
		return;
	}

	ofstream csv(path);			// open output file
	csv.precision(4);

        csv << "Src,Dst,Time,2.5perc,median,97.5perc" << endl;

	uint sourceid = source->GetId();
	uint targetid = target->GetId();

	char* date = new char[20];

	DelaySummary *summary = new DelaySummary();
	sumChain->SetBranchAddress("summary", &summary);

	Int_t nEntries = (Int_t) sumChain->GetEntries();

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

		int bin = summary->GetBinNo();
		uint src = summary->GetSourceId();
		uint dst = summary->GetTargetId();

		// silently skip if not requested source or
		// IDs out of range Ids  (inactive or invalid boxes)

		if ((sourceid != src) || (targetid != dst) ||
		    (bin > numIntervals) || (bin < 1)) {

				continue;
		}

		// Got here, so data are valid

		Float_t lev025 = summary->GetLevel025();
		Float_t median = summary->GetMedian();
		Float_t lev975 = summary->GetLevel975();
	        time_t bintime = summary->GetTimeLow() +
				  summary->GetBinSize()/2;

		// values -1 indicate 'no data available'

		if ((lev025 > 0) && (median > 0) && (lev975 > 0)) {

			// Write a line

                        strftime(date, 20, "%Y%m%d %H:%M", gmtime(&bintime));

			csv << sourceid << "," << targetid << ","
                            << date     << "," << lev025 << ","
			    << median   << "," << lev975 << endl;
			nProcessed++;
		}
	}
	csv.close();
	delete[] date;
}

/*
-------------------------------------------------------------------------------
Subroutine Header
Purpose	   	  :   Plot the Histograms for the specified target
-------------------------------------------------------------------------------
*/

void TTMSummaryChain::PlotHistos(TestBox* source, TestBox* target) {
        
        char buf[175];
        Int_t bin;
	int logscale = 0;  // should become global parameter

//        TCanvas *canvas = new TCanvas("canvas", "Plots", 0, 0, 1270, 990);

	// get indices for histogram arrays
	int src = id2index[source->GetId()];
	int dest = id2index[target->GetId()];

	if (src < 0 || dest < 0) {
		cerr << "%s: PlotHistos: negative index, problem with config?" 
		     << " src = " << src << " dest = " << dest << endl;
	}

        // create pads
//	TPad *pad1 = new TPad("pad1", "This is pad 1",0.01,0.49,0.43,0.95);
	TPad *pad1 = new TPad("pad1", "This is pad 1",0.01,0.64,0.86,0.95);
//	TPad *pad2 = new TPad("pad2", "This is pad 2",0.44,0.49,0.86,0.95);
	TPad *pad2 = new TPad("pad2", "This is pad 2",0.01,0.32,0.86,0.63);
//	TPad *pad3 = new TPad("pad3", "This is pad 3",0.01,0.02,0.43,0.48);
	TPad *pad3 = new TPad("pad3", "This is pad 3",0.01,0.01,0.86,0.31);
//	TPad *pad4 = new TPad("pad4", "This is pad 4",0.44,0.02,0.86,0.48);
	TPad *pad5 = new TPad("pad5", "This is pad 5",0.87,0.02,0.99,0.95);

        // force them to be visible on the canvas

        pad1->Draw();
        pad2->Draw();
        pad3->Draw();
//        pad4->Draw();
        pad5->Draw();

	// convert start and end time to character strings

	char cendtime[20];
	tm *timestruct = gmtime(&endTime);
        strftime(cendtime, 20, "%Y-%m-%d %H:%M", timestruct);

        time_t starttime = endTime-timePeriod;
        timestruct = gmtime(&starttime);
	char cstarttime[20];
        strftime(cstarttime, 20, "%Y-%m-%d %H:%M", timestruct);

        // Top Centre Label
        sprintf(buf, "               Trends from %s to %s.  Start: %s  End: %s UTC",
                source->GetShortName(), target->GetShortName(), cstarttime, cendtime);

        TPaveLabel *pl = new TPaveLabel(0.274,0.956,0.748,0.998,buf,"br");
        pl->SetTextSize(0.45);
//        pl->SetTextSize(0.37);
        pl->Draw();

	sprintf(buf, "IPv%d", ipVersion);
//
//	Only label as IPv4 if box is v6 enabled; requires more info 
//	from TestBoxConfig --> future extension
//
//      TPaveLabel *ip = new TPaveLabel(0.01,0.956,0.1,0.998,buf,"br");
	if (ipVersion == 6) {
      		TPaveLabel *ip = new TPaveLabel(0.01,0.956,0.1,0.998,buf,"br");
                ip->SetTextColor(9);
     		ip->SetTextSize(0.8);
	     	ip->Draw();
        }

//	else if (ipVersion == 4) {
//                ip->SetTextColor(8);
//	}
//     	ip->SetTextSize(0.8);
//     	ip->Draw();

        
        // Top left
        pad1->cd();
        pad1->SetGridx();
        pad1->SetGridy();
        if (logscale) {
		pad1->SetLogy();
        }

	for (bin=1; bin <= numIntervals; bin ++) {
       		level025Hist2D.GetElement(dest, bin)->GetXaxis()->SetTimeDisplay(1);
       		level025Hist2D.GetElement(dest, bin)->SetMarkerSize(0.3);
       		level025Hist2D.GetElement(dest, bin)->SetMarkerStyle(bin+1);
	        level025Hist2D.GetElement(dest, bin)->SetMarkerColor(bin);
		if (bin == 1) {
		        level025Hist2D.GetElement(dest, bin)->Draw();
		}
		else {
		        level025Hist2D.GetElement(dest, bin)->Draw("SAME");
		}
	}
        
        // Top Right
        pad2->cd();
        pad2->SetGridx();
        pad2->SetGridy();
        if (logscale) {
                pad2->SetLogx();
        }

	for (bin=1; bin <= numIntervals; bin ++) {
       		medianHist2D.GetElement(dest, bin)->GetXaxis()->SetTimeDisplay(1);
       		medianHist2D.GetElement(dest, bin)->SetMarkerSize(0.3);
       		medianHist2D.GetElement(dest, bin)->SetMarkerStyle(bin+1);
	        medianHist2D.GetElement(dest, bin)->SetMarkerColor(bin);
		if (bin == 1) {
		        medianHist2D.GetElement(dest, bin)->Draw();
		}
		else {
		        medianHist2D.GetElement(dest, bin)->Draw("SAME");
		}
	}
        
        // Bottom Left
        pad3->cd();
        pad3->SetGridx();
        pad3->SetGridy();
        if (logscale) {
                pad3->SetLogx();
        }


	for (bin=1; bin <= numIntervals; bin ++) {
       		level975Hist2D.GetElement(dest, bin)->GetXaxis()->SetTimeDisplay(1);
       		level975Hist2D.GetElement(dest, bin)->SetMarkerSize(0.3);
       		level975Hist2D.GetElement(dest, bin)->SetMarkerStyle(bin+1);
	        level975Hist2D.GetElement(dest, bin)->SetMarkerColor(bin);
		if (bin == 1) {
		        level975Hist2D.GetElement(dest, bin)->Draw();
		}
		else {
		        level975Hist2D.GetElement(dest, bin)->Draw("SAME");
		}
	}

/*
	*** COMMENTED - INACTIVE ***

        // Bottom Right: 1D medians
        pad4->cd();
        pad4->SetGridx();
        pad4->SetGridy();

  
	for (bin=1; bin <= numIntervals; bin ++) {
	        medianHist1D.GetElement(dest, bin)->SetFillColor(bin);
		if (bin == 1) {
		        medianHist1D.GetElement(dest, bin)->Draw();
		}
		else {
		        medianHist1D.GetElement(dest, bin)->Draw("SAME");
		}
	}

*/

        // Far Right: legend 
        pad5->cd();

	Short_t color = 1; 

        strcpy(buf, "STATISTICS:");
        Float_t x = 0.5;       // Starting point
        Float_t y = 0.95;
        Int_t align = 22;      // horizontally and vertically centered
        Font_t font = 20;     // Times-Roman-bold-r
        write_stats(pad5, buf, x, y, align, font, color);

        y -=0.075;
        y -=0.025;
        sprintf(buf,     "Time period %d days", (int) (timePeriod / SECONDSPERDAY));
        write_stats(pad5, buf, x, y, align, font, color);
        y -=0.02;
        sprintf(buf,     "%d intervals per day", numIntervals);
        write_stats(pad5, buf, x, y, align, font, color);

        y -=0.02;
	float delta = 24 / numIntervals;
	for (bin=1; bin <= numIntervals; bin++) {
		int startHour = (int) (0 + delta * (bin - 1));
		int endHour =   (int) (startHour + delta);

        	y -=0.05;
        	font = 20;     // Times-Roman-bold-r
	        sprintf(buf, "Time %d:00 - %d:00 h", startHour, endHour);
       		write_stats(pad5, buf, x, y, align, font, bin);

        	y -=0.035;
       		write_stats(pad5, "Median values:", x, y, align, font, bin);

		font = 10;      // Times-Romand-medium-i
        	y -=0.025;
		sprintf(buf, "2.5%% = %8.2f",
		    longTermLevel025.GetElement(src,dest,bin).GetLevel(50));
       		write_stats(pad5, buf, x, y, align, font, bin);
		
        	y -=0.025;
		sprintf(buf, "Median = %8.2f",
		    longTermMedian.GetElement(src,dest,bin).GetLevel(50));
       		write_stats(pad5, buf, x, y, align, font, bin);

        	y -=0.025;
		sprintf(buf, "97.5%% = %8.2f",
		    longTermLevel975.GetElement(src,dest,bin).GetLevel(50));
       		write_stats(pad5, buf, x, y, align, font, bin);
	}
        
        // now update canvas (needed to write to PS file?)
//        canvas->Update();

}
void TTMSummaryChain::write_stats(TPad* pad, const char* ptr_text, const Float_t x,
       const Float_t y, const Short_t align, Font_t font, const Short_t color)
{
  pad->cd();
  TText *xlabel = new TText(x, y, ptr_text);
  xlabel -> SetTextFont(font);
  xlabel -> SetTextColor(color);
  xlabel -> SetTextSize(0.11);
  xlabel -> SetTextAlign(align);
  xlabel->Draw();
  return;
}

