/*******************************************************************************
 *	TmsTestData.cpp	TMS Test code for a Data Client
 *			T.Barnaby,	BEAM Ltd,	2007-10-02
 *******************************************************************************
 *
 *	This is a simpe test program to fetch data from the TMS system.
 *	This example fetches the Sigma/DeltaX/DeltaY levels from all
 *	pickups.
 */
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <BFile.h>
#include <TmsD.h>
#include <TmsC.h>

#define	DEBUG		0

#if DEBUG
#define	dprintf(fmt, a...)       printf(fmt, ##a);
#else
#define	dprintf(fmt, a...)
#endif

using namespace Tms;
using namespace std;

const char*	cycleType = "SimBeam3";
const char*	testSignalFileName = "beam3-437000-8.psd";
int		quiet = 0;

// Get current time in seconds
double getTime()
{
	struct timeval	tp;
	
	gettimeofday(&tp, NULL);
	return ((double) tp.tv_sec + (double) tp.tv_usec * 1e-6);
}

void dumpData(Data& data, int dump){
	BFile	file;
	int	n;
	
	file.open(BString("data") + dump + ".txt", "w");
	
	for(n = 0; n < data.numValues; n++){
		file.printf("%d %d %d\n", n, data.dataValues[n].sigma, data.dataValues[n].time);
	}
}


BError dataCheck(Data& data){
	BError	err;
	BUInt32	c;
	BUInt32	s;
	BUInt32	b;
	BUInt32	bunchValues[] = {8651, 7151, 6070, 4937};
	BUInt32 timeStart = 400;
	BUInt32	numSamples;
	BUInt32	numSamplesTest;
	BUInt32	o;
	BUInt32	t;
	int	numErrorSigma = 0;
	int	numErrorTime = 0;
	int	errorChannel = -1;
	int	errorSample = -1;
	static int	dump = 0;

	numSamples = data.numValues / data.numBunches / data.numChannels;
	
	// Don't look at the last ms of sample sas a harmonic change may have occurred
	numSamplesTest = numSamples - 437;

	dprintf("CheckData: NumValues: %d NumChannels: %d NumBunches: %d NumSamples: %d\n", data.numValues, data.numChannels, data.numBunches, numSamples);

	for(c = 0; c < data.numChannels; c++){
		for(s = 2; s < numSamplesTest; s++){		// Ignores the first two samples
			for(b = 0; b < data.numBunches; b++){
				o = (c * numSamples * data.numBunches) + (s * data.numBunches) + b;
				t = timeStart + (s / 437);
//				printf("%d,%d,%d: %d %d\n", c, b, s, data.dataValues[o].sigma, data.dataValues[o].time);
				if((data.dataValues[o].sigma > (bunchValues[b] * 1.1)) || data.dataValues[o].sigma < (bunchValues[b] * 0.9)){
					dprintf("Sigma out of range: (%d.%d.%d) %d should be: %d\n", c, s, b, data.dataValues[o].sigma, bunchValues[b]);
					numErrorSigma++;
					if(errorChannel == -1)
						errorChannel = c;
					if(errorSample == -1)
						errorSample = o;
				}
				if((data.dataValues[o].time > (t * 1.1)) || (data.dataValues[o].time < (t * 0.9))){
					dprintf("Time out of range: (%d.%d.%d) %u (%x) should be: %u (%x)\n", c, s, b, data.dataValues[o].time, data.dataValues[o].time, t, t);
					numErrorTime++;
					if(errorChannel == -1)
						errorChannel = c;
					if(errorSample == -1)
						errorSample = o;
				}
			}
		}
	}

	if(numErrorSigma || numErrorTime){
		printf("DataErrors: Dump: %d Sigma: %u Time: %u Channel: %u Sample: %d\n", dump, numErrorSigma, numErrorTime, errorChannel, errorSample);
		dumpData(data, dump++);
		err.set(1, "Data errors");
	}
	
	return err;
}

// Function to get information on Cycle
BError tmsInformation(TmsControl& tmsControl, TmsProcess& tmsProcess){
	BError			err;
	BUInt32			cn;
	BString			ct;
	CycleInformation	info;
	BIter			i;

	// Get current cycle info
	if(err = tmsProcess.getCycleInfo(cn, ct)){
		return err.set(1, BString("Error: Getting Cycle Number: ") + err.getString());
	}

	printf("CycleNumber: %u CycleType: %s\n", cn, ct.retStr());

	if(err = tmsProcess.getCycleInformation(cn, info)){
		return err.set(1, BString("Error: Getting Cycle Information: ") + err.getString());
	}

	printf("CycleNumber:	%u\n", info.cycleNumber);
	printf("CycleType:	%s\n", info.cycleType.retStr());
	for(info.periods.start(i); !info.periods.isEnd(i); info.periods.next(i)){
		printf("CyclePeriod: %u StartTime: %u EndTime: %u NumBunches: %u NumValues: %u\n",
			info.periods[i].cyclePeriod, info.periods[i].startTime, info.periods[i].endTime, info.periods[i].numBunches, info.periods[i].numValues);
	}
	return err;
}

// Function to set system to use simulated timing and data
BError tmsSimData(TmsControl& tmsControl, TmsProcess& tmsProcess, int simDataAll){
	BError		err;
#ifdef ZAP
	BUInt32		cn;
	BString		ct;
	ConfigInfo	configInfo;
	PupeConfig	pupeConfig;
	unsigned int	n;
	BArray<BUInt32>	data;
	BFile		file;
	int		nb;

	// Read the test signal file
	if(err = file.open(testSignalFileName, "r")){
		if(err = file.open(BString("/usr/tms/data/") + testSignalFileName, "r")){
			return err.set(1, BString("Error: Opening file: ") + testSignalFileName);
		}
	}
	data.resize(1024);

	if((nb = file.read(&data[0], 1024 * sizeof(BUInt32))) <= 0){
		return err.set(1, BString("Error: Reading 1024 32bit data items from file: ") + testSignalFileName);
	}
	data.resize(nb / sizeof(BUInt32));


	// Sets the CycleType to match the data
	// Get current cycle info
	dprintf("Get Cycle Info\n");
	if(err = tmsProcess.getCycleInfo(cn, ct)){
		return err.set(1, BString("Error: Getting Cycle Number: ") + err.getString());
	}

	// Set NextCycle type
	dprintf("Set Next Cycle Info\n");
	if(err = tmsControl.setNextCycle(cn + 1, cycleType)){
		return err.set(1, BString("Error: Setting Next Cycle: ") + err.getString());
	}	


	dprintf("Get Configuration\n");
	if(err = tmsControl.getConfiguration(configInfo)){
		return err.set(1, BString("Error: getting configuration TMS: ") + err.getString());
	}

	// Sets up the simulated timing system
	pupeConfig.adcSysclkSync = 0;
//	pupeConfig.internalTimingMask = 0x7D;			// Expects CYCLE_START signal
	pupeConfig.internalTimingMask = 0x7f;			// All internal event signals
	pupeConfig.disableBlr = 0;

	if(simDataAll){
		for(n = 0; n < configInfo.puReferences.size(); n++){
			pupeConfig.internalTimingMask = 0x7f;			// All internal event signals

			dprintf("Set PupeConfig: Channel: %d\n", n + 1);
			if(err = tmsControl.setPupeConfig(configInfo.puReferences[n], pupeConfig)){
				return err.set(1, err.getString());
			}
		}
	}
	else {
		for(n = 0; n < configInfo.puReferences.size(); n++){
			if(((n / 3) == 4) || ((n / 3) == 9) || ((n / 3) == 13)){
				pupeConfig.internalTimingMask = 0x7f;			// All internal event signals
			}
			else {
				pupeConfig.internalTimingMask = 0x01;			// Internal SYS_CLOCK signal
			}

			dprintf("Set PupeConfig: Channel: %d\n", n + 1);
			if(err = tmsControl.setPupeConfig(configInfo.puReferences[n], pupeConfig)){
				return err.set(1, err.getString());
			}
		}
	}
	
	for(n = 0; n < configInfo.puReferences.size(); n++){
		dprintf("Set Test Data Channel: %d\n", n + 1);
		if(err = tmsControl.setTestData(configInfo.puReferences[n], 1, data)){
			return err.set(1, err.getString());
		}
	}
#else
	err = tmsControl.setSimulation(Simulation(1, 0, 1, 1, cycleType));
#endif

	return err;
}

// Function to read some data
BError tmsTestSingle(TmsControl& tmsControl, TmsProcess& tmsProcess, int numSamples, int check, int channel){
	BError			err;
	DataInfo		dataInfo;
	BUInt32			cn = 0;
	BString			ct;
	Data			data;
	double			timeStart = 0;
	double			t1 = 0;
	double			t2 = 0;
	double			timeEnd = 0;
	int			i;
	int			repeat = 4;
	double			r;
	
	if(numSamples == 0)
		numSamples = 1024000;
		
	// Find out the current cycle number and type
	if(err = tmsProcess.getCycleInfo(cn, ct)){
		return err.set(1, BString("Error: Getting Cycle Number: ") + err.getString());
	}

	if(!quiet)	
		printf("Getting data from pick-up %d for cycle: %u\n", channel, cn);

	for(i = 0; i < repeat; i++){
		dataInfo.cycleNumber	= cn;
		dataInfo.channel	= channel;
		dataInfo.cyclePeriod	= CyclePeriodEvent0;
		dataInfo.startTime	= 0;
		dataInfo.orbitNumber	= 0;
		dataInfo.bunchNumber	= 0;
		dataInfo.function	= DataFunctionRaw;
		dataInfo.argument	= 0;
		dataInfo.numValues	= numSamples;
		dataInfo.beyondPeriod = 0;

		t1 = getTime();
		if(err = tmsProcess.getData(dataInfo, data)){
			return err.set(1, BString("Error: Getting Data: ") + err.getString());
		}
		t2 = getTime();
		if(timeStart == 0.0)
			timeStart = t2;

		if(!quiet)	
			printf("CycleNum: %d Channel: %d Read: %d Time: %f\n", cn, dataInfo.channel, data.numValues, t2 - t1);

		if(check){
			if(err = dataCheck(data))
				return err;
		}
	}
	
	numSamples = data.numValues;
	timeEnd = t2;
	r = (sizeof(DataValue) * numSamples * (repeat - 1)) / (timeEnd - timeStart);

	if(!quiet)	
		printf("Time to fetch data: %f DataRate: %fMBytes/sec\n", timeEnd - timeStart, r / (1024*1024));
	
	return err;
}

// Function to read some data
BError tmsTestAllManual(TmsControl& tmsControl, TmsProcess& tmsProcess, int check){
	BError			err;
	DataInfo		dataInfo;
	BUInt32			cn = 0;
	BString			ct;
	BUInt32			numChans = 40;
	BUInt32			n;
	Data			data[numChans];
	Data			dataAll;
	double			timeStart = 0;
	double			t1 = 0;
	double			t2 = 0;
	double			timeEnd = 0;
	int			i;
	int			repeat = 4;
	int			numSamples = 20000;
	double			r;
	ConfigInfo		configInfo;
	
	// Get configuration
	if(err = tmsControl.getConfiguration(configInfo)){
		return err.set(1, BString("Error: Getting Configuration: ") + err.getString());
	}
	numChans = configInfo.puReferences.size();

	// Find out the current cycle number and type
	if(err = tmsProcess.getCycleInfo(cn, ct)){
		return err.set(1, BString("Error: Getting Cycle Number: ") + err.getString());
	}
	
	if(!quiet)	
		printf("Getting data from all pick-ups for cycle: %u\n", cn);

	for(i = 0; i < repeat; i++){
		for(n = 0; n < numChans; n += 1){
			dataInfo.cycleNumber	= cn;
			dataInfo.channel	= 1 + n;
			dataInfo.cyclePeriod	= CyclePeriodEvent0;
			dataInfo.startTime	= 0;
			dataInfo.orbitNumber	= 0;
			dataInfo.bunchNumber	= 0;
			dataInfo.function	= DataFunctionRaw;
			dataInfo.argument	= 0;
			dataInfo.numValues	= numSamples;
			dataInfo.beyondPeriod= 0;

			t1 = getTime();
			if(err = tmsProcess.getData(dataInfo, data[n])){
				return err.set(1, BString("Error: Getting Data: ") + err.getString());
			}
			t2 = getTime();
			if(timeStart == 0.0)
				timeStart = t2;
			
			if(!quiet)	
				printf("CycleNum: %d Channel: %d Time: %f\n", cn, 1 + n, t2 - t1);

			if(check){
#ifndef ZAP
				if(err = dataCheck(data[n]))
					return err;
#else
				dataCheck(data[n]);
#endif
			}
//			usleep(100000);
		}
	}
	numSamples = data[0].numValues;
	timeEnd = getTime();
	r = (sizeof(DataValue) * numSamples * numChans * (repeat - 1)) / (timeEnd - timeStart);

	if(!quiet)	
		printf("Time to fetch data: %f DataRate: %fMBytes/sec\n", timeEnd - timeStart, r / (1024*1024));
	
	return err;
}

// Function to reads some data
BError tmsTestAll(TmsControl& tmsControl, TmsProcess& tmsProcess, int check, int type, int numSamples){
	BError			err;
	DataInfo		dataInfo;
	BUInt32			cn = 0;
	BString			ct;
	BUInt32			numChans = 40;
	BUInt32			n;
	Data			data;
	double			timeStart = 0;
	double			t1 = 0;
	double			t2 = 0;
	double			timeEnd = 0;
	int			i;
	int			repeat = 4;
	double			r;
	ConfigInfo		configInfo;
	
	if(numSamples == 0)
		if(type == 1)
			numSamples = 19960;
		else
			numSamples = 300000;
	
	// Get configuration
	if(err = tmsControl.getConfiguration(configInfo)){
		return err.set(1, BString("Error: Getting Configuration: ") + err.getString());
	}
	numChans = configInfo.puReferences.size();

	// Find out the current cycle number and type
	if(err = tmsProcess.getCycleInfo(cn, ct)){
		return err.set(1, BString("Error: Getting Cycle Number: ") + err.getString());
	}
	
	if(!quiet)	
		printf("Getting data from %d pick-ups for cycle: %u\n", configInfo.puReferences.size(), cn);

	for(i = 0; i < repeat; i++){
		dataInfo.cycleNumber	= cn;
		dataInfo.channel	= 0;
		dataInfo.cyclePeriod	= CyclePeriodEvent0;
		dataInfo.startTime	= 0;
		dataInfo.orbitNumber	= 0;
		dataInfo.bunchNumber	= 0;
		dataInfo.function	= DataFunctionRaw;
		dataInfo.argument	= 0;
		dataInfo.numValues	= numSamples;
		dataInfo.beyondPeriod = 0;
		
		if(type == 1){
			dataInfo.function	= DataFunctionMean;

//			dataInfo.startTime	= 199;
//			dataInfo.cyclePeriod	= CyclePeriodEvent1;
//			dataInfo.numValues	= 40;
		}

#ifdef ZAP		
		printf("Period: %d, Function: %d, Start: %d, Orbit: %d, Bunch: %d, NumValues: %d\n",
			dataInfo.cyclePeriod, dataInfo.function, dataInfo.startTime, dataInfo.orbitNumber,
			dataInfo.bunchNumber, dataInfo.numValues);
#endif
		t1 = getTime();
		if(err = tmsProcess.getData(dataInfo, data)){
			return err.set(1, BString("Error: Getting Data: ") + err.getString());
		}
		t2 = getTime();
		if(timeStart == 0.0)
			timeStart = t2;

		if(!quiet)	
			printf("CycleNum: %d Time: %fs NumValues: %d\n", cn, t2 - t1, data.numValues);

		if(check){
#ifdef ZAP
#ifndef ZAP
			if(err = dataCheck(data))
				return err;
#else
			dataCheck(data);
#endif
#else
			if(err = dataCheck(data)){
				printf("Error: Retry\n");
				if(err = tmsProcess.getData(dataInfo, data)){
					return err.set(1, BString("Error: Getting Data: ") + err.getString());
				}
				if(err = dataCheck(data)){
					printf("Double Error\n");
					return err;
				}
			}
#endif
		}
		
//		cn++;
//		usleep(100000);
	}
	numSamples = data.numValues;
	timeEnd = t2;
	r = (sizeof(DataValue) * numSamples * (repeat - 1)) / (timeEnd - timeStart);

	if(!quiet)	
		printf("Time to fetch data: %f DataRate: %fMBytes/sec\n", timeEnd - timeStart, r / (1024*1024));
	
	return err;
}

void usage(void) {
	cerr << "Usage:\ttmsTestData [options] hostname\n";
	cerr << " -help             - Help on command line parameters\n";
	cerr << " -simdata          - Set the system to use simulated timing signals and test data\n";
	cerr << " -simdataall       - Sets all PUPE boards in the system to use simulated timing signals and test data\n";
	cerr << " -info             - Prints information on the processing cycle\n";
	cerr << " -numSamples <n>   - The number of samples to read\n";
	cerr << " -test <testName>  - The Test to be performed: single - single channel all bunches, all - all bunches all channels\n";
	cerr << "                   - allMean - Mean from all bunches all channels\n";
	cerr << " -check            - Check the data for validity\n";
	cerr << " -cont             - Continuous\n";
	cerr << " -quiet            - Only print errors\n";
	cerr << " -channel <n>      - Channel number\n";
}

static struct option options[] = {
		{ "?",			0, NULL, 0 },
		{ "h",			0, NULL, 0 },
		{ "help",		0, NULL, 0 },
		{ "simdata",		0, NULL, 0 },
		{ "simdataall",		0, NULL, 0 },
		{ "info",		0, NULL, 0 },
		{ "check",		0, NULL, 0 },
		{ "cont",		0, NULL, 0 },
		{ "quiet",		0, NULL, 0 },
		{ "numSamples",		1, NULL, 0 },
		{ "test",		1, NULL, 0 },
		{ "channel",		1, NULL, 0 },
		{ 0,0,0,0 }
};

int main(int argc, char** argv){
	BError			err;
	BString			hostName = "localhost";
	TmsProcess		tmsProcess;
	TmsControl		tmsControl;
	int			optIndex = 0;
	int			c;
	BString			s;
	int			simData = 0;
	int			simDataAll = 0;
	int			info = 0;
	int			check = 0;
	int			cont = 0;
	BString			test;
	int			numSamples = 0;
	int			channel = 1;

	while((c = getopt_long_only(argc, argv, "", options, &optIndex)) == 0){
		s = options[optIndex].name;
		if(s == "help" || s == "h" || s == "?"){
			usage();
			return 1;
		}
		else if(s == "simdata"){
			simData = 1;
		}
		else if(s == "simdataall"){
			simDataAll = 1;
		}
		else if(s == "info"){
			info = 1;
		}
		else if(s == "check"){
			check = 1;
		}
		else if(s == "cont"){
			cont = 1;
		}
		else if(s == "quiet"){
			quiet = 1;
		}
		else if(s == "test"){
			test = optarg;
		}
		else if(s == "numSamples"){
			numSamples = strtol(optarg, 0, 0);
		}
		else if(s == "channel"){
			channel = strtol(optarg, 0, 0);
		}
		else {
			usage();
			return 1;
		}
	}
	
	if(optind == argc){
		usage();
		return 1;
	}

	hostName = argv[optind++];

	// Connect to the Control service
	if(err = tmsControl.connectService(BString("//") + hostName + "/tmsControl1")){
		cerr << "Error: " << err.getString() << "\n";
		return 1;
	}

	// Connect to the Process service
	if(err = tmsProcess.connectService(BString("//") + hostName + "/tmsProcess1")){
		cerr << "Error: " << err.getString() << "\n";
		return 1;
	}

	if(simData || simDataAll){
		if(err = tmsSimData(tmsControl, tmsProcess, simDataAll)){	
			cerr << "Error: " << err.getString() << "\n";
			return 1;
		}
		if(test != "")
			sleep(3);
	}

	if(info){
		if(err = tmsInformation(tmsControl, tmsProcess)){	
			cerr << "Error: " << err.getString() << "\n";
			return 1;
		}
	}

	if(test == "single"){
		// Run a normal data gathering cycle as a normal client would.
		do {
			if(err = tmsTestSingle(tmsControl, tmsProcess, numSamples, check, channel)){
				cerr << "Error: " << err.getString() << "\n";
				return 1;
			}
		} while(cont);
	}
	else if(test == "all"){
		do {
			if(err = tmsTestAll(tmsControl, tmsProcess, check, 0, numSamples)){	
				cerr << "Error: " << err.getString() << "\n";
				return 1;
			}
		} while(cont);
	}
	else if(test == "allManual"){
		do {
			if(err = tmsTestAllManual(tmsControl, tmsProcess, check)){	
				cerr << "Error: " << err.getString() << "\n";
				return 1;
			}
		} while(cont);
	}
	else if(test == "allMean"){
		do {
			if(err = tmsTestAll(tmsControl, tmsProcess, check, 1, numSamples)){	
				cerr << "Error: " << err.getString() << "\n";
				return 1;
			}
		} while(cont);
	}
	
	return 0;
}
