/*******************************************************************************
 *	Control.cc	Control process
 *			T.Barnaby,	BEAM Ltd,	2007-02-07
 *******************************************************************************
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h> 
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/vfs.h>
#include <sys/param.h>
#include <sys/time.h>
#include <netdb.h>
#include <main.h>
#include <Control.h>
#include <BEntry.h>
#include <BDir.h>
#include <BString.h>
#include <Debug.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

#define	SINGLE_DMA	1		///< Only allow a single DMA accross the PCI bus at a time in getData

CycleParams::CycleParams(BString cycleType, CycleParam& params){
	this->cycleType = cycleType;
	this->params.append(params);
}


void	ControlTimer::sigFunc(int sig, siginfo_t* sigInfo, void* data){
	ControlTimer::timer->tick();
}

ControlTimer* ControlTimer::timer;

ControlTimer::ControlTimer(Control& control) : ocontrol(control){
	timer = this;
}

void ControlTimer::tick(){
	otick.post();
}

void ControlTimer::sync(){
	struct itimerval	t;
	
	t.it_interval.tv_sec = 0;
	t.it_interval.tv_usec = 100000;
	t.it_value = t.it_interval;

	setitimer(ITIMER_REAL, &t, NULL);
}

void* ControlTimer::function(){
	struct sigaction	sigAction;
	sigset_t		sigm;

	dprintf(DBG_THREADS, "ControlTimer Thread: %d\n", gettid());

	sigemptyset(&sigm);
	sigaddset(&sigm, SIGALRM);
	sigprocmask(SIG_UNBLOCK, &sigm, 0);
	
	memset(&sigAction, 0, sizeof(sigAction));
	sigAction.sa_sigaction = ControlTimer::sigFunc;
	sigAction.sa_flags = SA_SIGINFO;
	sigaction(SIGALRM, &sigAction, NULL);
	

	sync();
	while(1){
		otick.wait();
		ocontrol.timer();
	}

	return 0;
}


Control::Control(int dbFixed) :
	odebugFixed(dbFixed),
	otimer(*this),
	opuControlServer(*this, oboapServer, "puControl"),
	opuProcessServer(*this, oboapServer, "puProcess"){
	onum = 0;
	memset(oinitialised, 0, sizeof(oinitialised));
	omaster = 0;
	orings = 0;
	osimulate = 0;
	osimulateTiming = 0;
	osimulateDoubleInjection = 0;
	ocycleNumberNext = 0;
	oprocessingCycle = 0;
	oserverName = "localhost";
	ocycleTick = 12;
	
	ostartTime = getTime();
	ocycleProcessed = 0;
	owatchdog = -1;
}

Control::~Control(){

}

BString Control::getName(){
	return BString("Module") + moduleNum() + ": ";
}

BError Control::mergeError(BError err){
	if(err){
		err.set(err.getErrorNo(), getName() + err.getString());
	}
	return err;
}
			

BError Control::init(int number){
	BError		err;
	BString		s;
	sched_param	sp;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	if(number){
		onum = number;
	}
	else {
		if(s = config.findValue("ModuleControllerNumber:"))
			onum = s.retInt();
	}
	
	if(config.findValue("SimulateFpga:").retInt() == 1)
		osimulate = 1;
	else
		osimulate = 0;

	s = config.findValue("SimulateTiming:");
	if(s != "")
		osimulateTiming = s.retInt();
	else
		osimulateTiming = 0;

	if(s = config.findValue("TmsServer:"))
		oserverName = s;
		

	orings = config.findValue("Rings:").retInt();
	if(orings == 0)
		orings = 1;
	
	otempInternalMax = config.findValue("TempInternalMax:").retDouble();
	otempFpgaMax = config.findValue("TempFpgaMax:").retDouble();

	// Set default priority to real-time for all of these threads
	if(realTime){
//		seteuid(0);
		sp.sched_priority = 25;
		if(sched_setscheduler(0, SCHED_RR, &sp))
			fprintf(stderr, "Warning: unable to set as real-time process\n");
	}
	
	// Initialise sub systems
	otimer.start();
	
	// Setup watchdog timer, if available
	if(s = config.findValue("Watchdog:")){
		int	v = s.retInt();
		if(v){
			if((owatchdog = open("/dev/watchdog", O_RDWR)) >= 0){
				ioctl(owatchdog, WDIOC_SETTIMEOUT, &v);
				v = 0;
				ioctl(owatchdog, WDIOC_SETPRETIMEOUT, &v);
			}
		}
	}

	opuControlServer.setName(BString("puControl-") + onum);
	opuProcessServer.setName(BString("puProcess-") + onum);

	if(err = oboapServer.init(oserverName, 0, BoapServer::THREADED)){
		return mergeError(err);
	}

#ifdef ZAP
	if(realTime){
		// Set priority back to normal
		sp.sched_priority = 0;
		if(sched_setscheduler(0, SCHED_OTHER, &sp))
			fprintf(stderr, "Warning: unable to set as normal process\n");
		seteuid(getuid());
	}
#endif

	err = initCmd();

	return err;
}

void Control::abort(){
	// Disable watchdog
	if(owatchdog >= 0){
		write(owatchdog, "V", 1);
		close(owatchdog);
	}
}


BError Control::initCmd(){
	BError		err;
	BIter		i;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	olock.lock();
	memset(oinitialised, 0, sizeof(oinitialised));

	opupeEnginesLock.wrLock();
	for(opupeEngines.start(i); !opupeEngines.isEnd(i); ){
		delete opupeEngines[i];
		opupeEngines.del(i);
	}
	opupeEnginesLock.unlock();
	
	if(err = initPupeEngines()){
		olock.unlock();
		return err;
	}

	nprintf("%d Pupe Engines initialised\n", opupeEngines.number());

	olock.unlock();

	// Setup default parameters
	setNextCycle(0, "Beam3");

	dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__);

	return err;
}

BError Control::setProcessPriority(BUInt32 priority){
	BError			err;
	struct sched_param	sp;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);
	
	if(realTime){
		sp.sched_priority = 26;
		if(sched_setscheduler(0, SCHED_RR, &sp))
			fprintf(stderr, "Warning: unable to set as real-time process\n");
	}

	return err;
}

BError Control::configure(BUInt32 ring, ConfigInfo configInfo){
	BError	err;
	
	dprintf(DBG_CMD, "%s: Ring: %d\n", __PRETTY_FUNCTION__, ring);
	
	if((ring < 1) || (ring > 4))
		return err.set(ErrorParam, "Ring value out of range");
	
	oconfigInfoLock.lock();
	oconfigInfo[ring - 1] = configInfo;
	oconfigInfoLock.unlock();

	oinitialised[ring - 1] = 1;

	return err;
}

BError Control::test(BList<BError>& errors){
	BError	errRet;
	BError	err;
	BIter	i;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){
		if(err = opupeEngines[i]->status()){
			errors.append(BError(err.getErrorNo(), getName() + "Pupe" +  opupeEngines[i]->getSlot() + ": Status: " + err.getErrorNo() + " " + err.getString()));
		}
		else {
			errors.append(BError(0, getName() + "Pupe" +  opupeEngines[i]->getSlot() + ": Status: " + "Ok"));
		}
	}
	opupeEnginesLock.unlock();

	return errRet;
}

BError Control::getStatus(BList<NameValue>& statusList){
	BError			err;
	BError			e;
	int			numPupe = 0;
	BIter			i;
	BString			s;
	int			p;
	BList<NameValue>	l;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
#ifdef ZAP
	for(opupeEngines.start(i), p = 1; !opupeEngines.isEnd(i); opupeEngines.next(i), p++){
		e = opupeEngines[i]->status();
		s = BString(e.getErrorNo()) + ",\"" + e.getString() + "\"";
		statusList.append(NameValue(BString("TmsPuServer") + onum + "_Pupe" + p + "_Error", s));
		if(!e){
			numPupe++;
		}
	}
#else
	for(opupeEngines.start(i), p = 1; !opupeEngines.isEnd(i); opupeEngines.next(i), p++){
		e = opupeEngines[i]->status();
		if(!e){
			numPupe++;
		}

		opupeEngines[i]->getStatusList(PuChannel(1,1,1), statusList);
//		statusList = statusList + l;
	}
#endif
	opupeEnginesLock.unlock();
	
	olock.lock();
	statusList.append(NameValue(BString("Module") + moduleNum() + "_Running", "1"));
	statusList.append(NameValue(BString("Module") + moduleNum() + "_NumberPupe", numPupe));
	statusList.append(NameValue(BString("Module") + moduleNum() + "_CycleNumber", ocycleNumber));
	statusList.append(NameValue(BString("Module") + moduleNum() + "_CycleType", ocycleType));
	olock.unlock();
	
	return err;
}

BError Control::getStatistics(BList<NameValue>& statsList){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	statsList.append(NameValue(BString("Module") + moduleNum() + "_UpTime", getTime() - ostartTime));
	statsList.append(NameValue(BString("Module") + moduleNum() + "_CycleNumProcessed", ocycleProcessed));
	
	return err;
}

BError Control::getMasterPuChannel(PuChannel& puChannel){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	puChannel.moduleNum = onum;
	puChannel.pupeNum = omaster + 1;
	puChannel.pupeChan = 1;
	
	return err;
}


BError Control::addEventServer(BString name){
	BError	err;
	
	dprintf(DBG_CMD, "%s: %s\n", __PRETTY_FUNCTION__, name.retStr());
	
	oeventServers.append(name);

	return err;
}

BError Control::delEventServer(BString name){
	BError	err;
	BIter	i;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	oeventServers.del(name);

	return err;
}

BError Control::setControlInfo(CycleParam params){
	BError	err;
	BIter	i;
	BIter	i1, i2;
	int	found = 0;
	int	done = 0;
	
	dprintf(DBG_CMD, "Control::setControlInfo: %s %s\n", params.cycleType.retStr(),  params.name.retStr());
	
	if(params.ring > 4){
		return err.set(ErrorParam, BString("State table ring number is out of range for CycleType: ") +  params.cycleType);
	}

	ocycleParmsLock.lock();

	// Check if there is already an entry for this cycleType
	for(ocycleParms.start(i1); !ocycleParms.isEnd(i1); ocycleParms.next(i1)){
		if(ocycleParms[i1].cycleType == params.cycleType){
			found = 1;
			break;
		}
	}
	
	if(found){
		// See if there is an entry for this ring and channel
		found = 0;
		for(ocycleParms[i1].params.start(i2); !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){
			if((ocycleParms[i1].params[i2].ring == params.ring) && (ocycleParms[i1].params[i2].channel == params.channel)){
				found = 1;
				break;
			}
		}
		
		// If not already present, adds the statetable entry in ring then channel order. a ring of 0 or a channel of 0 is added at the end.
		if(found){
			ocycleParms[i1].params[i2] = params;
		}
		else {
			for(ocycleParms[i1].params.start(i2); !done && !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){
				if(params.ring && ((ocycleParms[i1].params[i2].ring == 0) || (params.ring < ocycleParms[i1].params[i2].ring))){
					ocycleParms[i1].params.insert(i2, params);
					done = 1;
				}
				else if(params.channel  && ((ocycleParms[i1].params[i2].channel == 0) || (params.channel < ocycleParms[i1].params[i2].channel))){
					ocycleParms[i1].params.insert(i2, params);
					done = 1;
				}
			}
			if(!done)
				ocycleParms[i1].params.append(params);
		}
	}
	else {
		ocycleParms.append(CycleParams(params.cycleType, params));
	}

#ifdef ZAP
	printf("CycleTypes:\n");
	for(ocycleParms.start(i); !ocycleParms.isEnd(i); ocycleParms.next(i)){
		printf("%s\n", ocycleParms[i].cycleType.retStr());
	}
#endif

	ocycleParmsLock.unlock();

	return err;
}

BError Control::setNextCycle(BUInt32 cycleNumber, BString cycleType){
	BError			err;
	BIter			i;
	BIter			i1, i2;
	BUInt			r;
	BUInt			c;
	PuChannel		puChannel;
	int			found = 0;
	int			done = 0;
	int			afterCycleStop = 0;
	double			ts, te;
	static double		tmax;
	
	dprintf(DBG_SETNEXTCYCLE, "%s %u %s\n", __PRETTY_FUNCTION__, cycleNumber, cycleType.retStr());

	ts = getTime();

	olock.lock();
	if(!oprocessingCycle)
		afterCycleStop = 1;

	ocycleNumberNext = cycleNumber;
	ocycleTypeNext = cycleType;

#ifdef ZAP
	printf("CycleTypes:\n");
	for(ocycleParms.start(i1); !ocycleParms.isEnd(i1); ocycleParms.next(i1)){
		printf("%s\n", ocycleParms[i1].cycleType.retStr());
		for(ocycleParms[i1].params.start(i2); !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){
			CycleParam&	cp = ocycleParms[i1].params[i2];
			printf("\tRing: %d Channel: %d Name: %s\n", cp.ring, cp.channel, cp.name.retStr());
		}
	}
#endif
	
	// Could improve this with a hashed name lookup
	ocycleParmsLock.lock();

	// Find the set of statetables for the cycleType in question
	for(ocycleParms.start(i1); !ocycleParms.isEnd(i1); ocycleParms.next(i1)){
		dprintf(DBG_SETNEXTCYCLE, "Control::setNextCycle: CheckCycleType: %s - %s\n", ocycleParms[i1].cycleType.retStr(), cycleType.retStr());
		if(ocycleParms[i1].cycleType == cycleType){
			found = 1;
			
			// Configure all PuChannels matching the ring and channel numbers. Params in ring/channel order with 0 entries at the end to catch any unspcified ones.
			for(r = 0; r < 4; r++){
				for(c = 0; c < oconfigInfo[r].puReferences.size(); c++){
					PuChannel&	puChannel = oconfigInfo[r].puReferences[c];
					
					// Only do channels managed by this module controller
					if(puChannel.moduleNum == onum){
						// Search for a matching ring/channel entry use the one for ring0/channel0 if no particular one found
						done = 0;
						for(ocycleParms[i1].params.start(i2); !ocycleParms[i1].params.isEnd(i2); ocycleParms[i1].params.next(i2)){
							CycleParam&	cp = ocycleParms[i1].params[i2];

							if(((cp.ring == (r + 1)) || (cp.ring == 0)) && ((cp.channel == (c + 1)) || (cp.channel == 0))){
								dprintf(DBG_SETNEXTCYCLE, "Ring: %d Channel: %d set PuChan: %d.%d.%d to params: %s ring: %d chan: %d\n", (r + 1), (c + 1), puChannel.moduleNum, puChannel.pupeNum, puChannel.pupeChan, cycleType.retStr(), cp.ring, cp.channel);
								opupeEnginesLock.rdLock();
								opupeEngines[puChannel.pupeNum - 1]->setNextCycle(puChannel, cycleNumber, cycleType, cp);
								opupeEnginesLock.unlock();
								done = 1;
								break;
							}
						}

						if(!done){
							ocycleParmsLock.unlock();
							olock.unlock();
							return err.set(ErrorParam, BString("No state table found for ring: ") +  (r + 1) + " chan: " + (c + 1));
						}
					}
				}
			}
		}
	}
	
	ocycleParmsLock.unlock();

	olock.unlock();

	if(!found)
		err.set(ErrorParam, BString("There are no Cycle Parameters for cycle type: ") + cycleType);

	if(!err && afterCycleStop){
		if(oprocessingCycle){
			err.set(ErrorCycleNumber, getName() + "The next cycle has already started");
		}
	}

	dprintf(DBG_SETNEXTCYCLE, "Tick: %d\n", ocycleTick);

	// Synchronise Module controllers if in simulation mode
	if(osimulate)
		ocycleTick = 10;

	if(osimulateTiming & TimingSigCycleStart){
		// Make sure timing simulation is in sync between multiple TmsPuServers when CYCLE_START is simulated
		otimer.sync();
		if(ocycleTick == 9){
			otimer.tick();
		}
		else {
			ocycleTick = 10;
		}
	}

	te = getTime();
	if((te - ts) > tmax){
		tmax = te - ts;
	}
	dprintf(DBG_SETNEXTCYCLE, "Control::setNextCycle: Time: %f MaxTime: %f\n", te - ts, tmax);
	dprintf(DBG_SETNEXTCYCLE, "%s: End\n", __PRETTY_FUNCTION__);

	return err;
}

BError Control::getStatus(PuChannel puChannel, PuStatus& puStatus){
	BError	err;
	BIter	i;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);
	opupeEnginesLock.rdLock();
	if(puChannel.pupeNum == 0){
		for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){
			opupeEngines[i]->getStatus(puChannel, puStatus);
		}
	}
	else {
		if(puChannel.pupeNum > opupeEngines.number()){
			err.set(ErrorParam, getName() + "No Pupe Engine Numbered: " + opupeEngines.number());
		}
		else {
			opupeEngines[puChannel.pupeNum - 1]->getStatus(puChannel, puStatus);
		}
	}
	opupeEnginesLock.unlock();

	return err;
}

BError Control::getCycleInformation(BUInt32 cycleNumber, CycleInformation& cycleInformation){
	BError		err;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	err = mergeError(opupeEngines[0]->getCycleInformation(cycleNumber, cycleInformation));
	opupeEnginesLock.unlock();

	return err;
}


BError Control::getData(PuChannel puChannel, DataInfo dataInfo, Data& data, BUInt32& orbitNumber){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	// Setup for error return
	data.numValues = 0;
	data.dataType = 0;
	data.numBunches = 0;
	data.numChannels = 0;

	opupeEnginesLock.rdLock();
#if SINGLE_DMA
	opupeLock.lock();
#endif
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
#if SINGLE_DMA
		opupeLock.unlock();
#endif
		opupeEnginesLock.unlock();
		return err.set(ErrorParam, getName() + "PuChannel: PupeNum out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->getData(puChannel, dataInfo, data, orbitNumber));
#if SINGLE_DMA
	opupeLock.unlock();
#endif
	opupeEnginesLock.unlock();
	
	return err;
}

BError Control::requestData(PuChannel puChannel, DataInfo dataInfo){
	BError	err;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	return err;
}

BError Control::setTestMode(PuChannel puChannel, BUInt32 testOutput, BUInt32 timingDisableMask){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTestMode(puChannel, testOutput, timingDisableMask));

	opupeEnginesLock.unlock();

	return err;
}

BError Control::setTimingSignals(PuChannel puChannel, BUInt32 timingSignals){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTimingSignals(puChannel, timingSignals));

	opupeEnginesLock.unlock();

	return err;
}

BError Control::captureDiagnostics(PuChannel puChannel, TestCaptureInfo captureInfo, BArray<BUInt64>& data){
	BError	err;
	
	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}

	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->captureDiagnostics(puChannel, captureInfo, data));

	opupeEnginesLock.unlock();

	dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__);

	return err;
}

BError Control::setTestData(PuChannel puChannel, BInt32 on, BArray<BUInt32> data){
	BError	err;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setTestData(puChannel, on, data));

	opupeEnginesLock.unlock();
	dprintf(DBG_CMD, "%s: End\n", __PRETTY_FUNCTION__);

	return err;
}

BError Control::setPupeConfig(PuChannel puChannel, PupeConfig pupeConfig){
	BError	err;

	dprintf(DBG_CMD, "%s\n", __PRETTY_FUNCTION__);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->setPupeConfig(puChannel, pupeConfig));
	
	osimulateTiming = pupeConfig.internalTimingMask;
	osimulateDoubleInjection = pupeConfig.doubleInjection;

	opupeEnginesLock.unlock();
	return err;
}

BError Control::getPupeConfig(PuChannel puChannel, PupeConfig& pupeConfig){
	BError	err;

	dprintf(DBG_CMD, "Control::getPupeConfig: %d.%d.%d\n", puChannel.moduleNum, puChannel.pupeNum, puChannel.pupeChan);

	opupeEnginesLock.rdLock();
	if((puChannel.pupeNum < 1) || (puChannel.pupeNum > opupeEngines.number())){
		opupeEnginesLock.unlock();
		return err.set(ErrorMisc, getName() + "PuChannel: PupeNum  out of range");
	}
	
	err = mergeError(opupeEngines[puChannel.pupeNum - 1]->getPupeConfig(puChannel, pupeConfig));
	
	opupeEnginesLock.unlock();
	return err;
}



void Control::run(){
	dprintf(DBG_CMD, "Control::run\n");
	oboapServer.run();
}


void Control::timer(){
	static uint32_t	tick = 0;
	static int	simEnable = 0;
	BError		err;
	BIter		i;

//	printf("Control::timer: %d\n", 	ocycleTick);
	if(simEnable){
		if(++ocycleTick >= 12)
			ocycleTick = 0;

//printf("CycleTick: %d\n", ocycleTick);
		if(osimulate){
			opupeEnginesLock.rdLock();
			if(ocycleTick == 0){
				for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i))
					opupeEngines[i]->cycleStart();
			}
			else if(ocycleTick == 4){
				if(opupeEngines.number())
					opupeEngines.front()->fpgaTriggerTimingInputs(TimingSigInjection);
			}
			else if(osimulateDoubleInjection && (ocycleTick == 5)){
				if(opupeEngines.number())
					opupeEngines.front()->fpgaTriggerTimingInputs(TimingSigInjection);
			}
			else if(ocycleTick == 6){
				if(opupeEngines.number())
					opupeEngines.front()->fpgaTriggerTimingInputs(TimingSigHChange);
			}
			else if(ocycleTick == 10){
				for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i))
					opupeEngines[i]->cycleStop();
			}
			opupeEnginesLock.unlock();
		}
		else {
			opupeEnginesLock.rdLock();
			if((osimulateTiming & TimingSigCycleStart) && (ocycleTick == 0)){
				if(opupeEngines.number())
					opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigCycleStart);
			}
			else if((osimulateTiming & TimingSigInjection) && (ocycleTick == 4)){
				if(opupeEngines.number())
					opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigInjection);
			}
			else if(osimulateDoubleInjection && (osimulateTiming & TimingSigInjection) && (ocycleTick == 5)){
				if(opupeEngines.number())
					opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigInjection);
			}
			else if((osimulateTiming & TimingSigHChange) && (ocycleTick == 6)){
				if(opupeEngines.number())
					opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigHChange);
			}
			else if((osimulateTiming & TimingSigCycleStop) && (ocycleTick == 10)){
				if(opupeEngines.number())
					opupeEngines[omaster]->fpgaTriggerTimingInputs(TimingSigCycleStop);
			}

			opupeEnginesLock.unlock();
		}
	}

	if(tick == 30){
		simEnable = 1;
	}

	// Every 1 seconds do some house keeping
	if((tick % 10) == 9){
		int	r;

		for(r = 0; r < orings; r++){
			TmsControl	tmsControl(BString("//") + oserverName + "/tmsControl" + (r + 1));

			if(!oinitialised[r]){
				if(err = tmsControl.puServerStarted(onum)){
					wprintf("Unable to call puServerStarted on %s: for ring: %d %s\n", oserverName.retStr(), r, err.getString().retStr());
				}
			}
		}
		
		// Check ADC Pll Locks
		opupeEnginesLock.rdLock();
		for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){
			opupeEngines[i]->adcPllLockCheck();
		}
		opupeEnginesLock.unlock();
		
		// Ping the watchdog
		if(owatchdog >= 0){
			ioctl(owatchdog, WDIOC_KEEPALIVE, 0);
		}
	}
	
	// Every 10 seconds do some house keeping
	if((tick % 100) == 99){
		double	tempInternal = 0.0;
		double	tempFpga = 0.0;
		
		// Check Temperatures
		opupeEnginesLock.rdLock();
		for(opupeEngines.start(i); !opupeEngines.isEnd(i); opupeEngines.next(i)){
			opupeEngines[i]->getTemperatures(tempInternal, tempFpga);
			if(otempInternalMax && (tempInternal > otempInternalMax)){
				opupeEngines[i]->setStatus(BError(ErrorFpga, "Temperature Internal to high: FPGA shutdown"), 1);
				wprintf("Temperature Internal to high on %s: FPGA shutdown\n", (getName() + opupeEngines[i]->getName().retStr()).retStr());
			}
			if(otempFpgaMax && (tempFpga > otempFpgaMax)){
				opupeEngines[i]->setStatus(BError(ErrorFpga, "Temperature Fpga to high: FPGA shutdown"), 1);
				wprintf("Temperature FPGA to high on %s: FPGA shutdown\n", (getName() + opupeEngines[i]->getName().retStr()).retStr());
			}
		}
		opupeEnginesLock.unlock();
	}
	tick++;
}

void Control::cycleStart(BUInt32 cycleNumber){
	BError	err;
	
	dprintf(DBG_CYCLE, "Control::cycleStart\n");
	
	olock.lock();

	oprocessingCycle = 1;
	
	ocycleNumber = ocycleNumberNext;
	ocycleType = ocycleTypeNext;

#ifdef ZAP
	// Make sure timing simulation is in sync with cycle start
	otimer.sync();
	ocycleTick = 0;
#endif

	olock.unlock();

	if(onum == 1){
		if(err = oeventServers.cycleStartEvent(cycleNumber))
			wprintf(getName() + "Error send CycleStart event: " + err.getString() + "\n");
	}

}

void Control::cycleError(BUInt32 cycleNumber, BError error){
	dprintf(DBG_CYCLE, "Control::cycleError\n");
	
	oeventServers.errorEvent(cycleNumber, error);
	eprintf(getName() + "Control::cycleError: Cycle: %d Error: %s\n", cycleNumber, error.getString().retStr());
}

void Control::cycleStop(BUInt32 cycleNumber){
	dprintf(DBG_CYCLE, "Control::cycleStop\n");

	olock.lock();

	ocycleProcessed++;
	oprocessingCycle = 0;

//	printf("Control::cycleStop: %u CurrentCycleType: %s NextCycleType: %s\n", cycleNumber, ocycleType.retStr(), ocycleTypeNext.retStr());

	olock.unlock();

	if(onum == 1){
		oeventServers.cycleStopEvent(cycleNumber);
	}
}

BError Control::initPupeEngines(){
	BError		err;
	BError		e;
	int		physical = 0;
	int		slot = 0;
	int		board;
	Pupe*		pupe;
	BArray<int>	devs;
	int		d = 0;
	BString		str;
	BString		s;
	int		numBoards = 5;
	
	if((s = config.findValue("PupeNumber:")) != ""){
		numBoards = config.findValue("PupeNumber:").retInt();
	}

	if((s = config.findValue("PupeMaster:")) != ""){
		omaster = config.findValue("PupeMaster:").retInt() - 1;
	}
	
	if(config.findValue("PupePhysicalOn:").retInt() == 1){
		str = config.findValue("PupePhysicalDevices:");
		while((s = str.pullToken(",")) != ""){
			devs.resize(d + 1);
			devs[d] = s.retInt();
			d++;
		}
		
		if(err = opupeGeog.scan(devs))
			return err;
		physical = 1;
	}

	opupeEnginesLock.wrLock();
	for(slot = 0; slot < numBoards; slot++){
		if(!osimulate && physical)
			board = opupeGeog.getBoardId(slot);
		else
			board = slot;
		
		if(board < 0){
			wprintf(getName() + "PupeEngine no board in slot: " + slot + "\n");
		}
		
		pupe = new Pupe(*this, slot, board);

		opupeEngines.append(pupe);
		if(e = pupe->init()){
			wprintf(getName() + "PupeEngine init error: " + e.getString() + "\n");
		}

		if(omaster == slot)
			pupe->setMaster(1);
		else
			pupe->setMaster(0);
	}
	opupeEnginesLock.unlock();

	return err;
}

BError Control::getPuChannel(PuChannel puPhysChannel, BUInt32& ring, BUInt32& puChannel){
	BError		err;
	BUInt		r;
	BUInt		i;
	
	// Could use a mapping table to speed this up if necessary
	oconfigInfoLock.lock();
	ring = 0;
	puChannel = 0;
	for(r = 0; r < 4; r++){
		for(i = 0; i < oconfigInfo[r].puReferences.size(); i++){
			if((puPhysChannel.moduleNum == oconfigInfo[r].puReferences[i].moduleNum) &&
				(puPhysChannel.pupeNum == oconfigInfo[r].puReferences[i].pupeNum) &&
				(puPhysChannel.pupeChan == oconfigInfo[r].puReferences[i].pupeChan)){
					ring = r + 1;
					puChannel = i + 1;
					oconfigInfoLock.unlock();
					return err;
			}
		}
	}
	oconfigInfoLock.unlock();
	
	return err.set(ErrorParam, getName() + "Physical PuChannel not configured");
}

BError Control::getPuPhysChannel(BUInt32 ring, BUInt32 puChannel, PuChannel& puPhysChannel){
	BError		err;

	if((ring < 1) || (ring > 4))
		return err.set(ErrorParam, "Ring value out of range");
	
	oconfigInfoLock.lock();
	if((puChannel < 1) || (puChannel > oconfigInfo[ring - 1].puReferences.size())){
		oconfigInfoLock.unlock();
		return err.set(ErrorParam, getName() + "Physical PuChannel not configured");
	}
	
	puPhysChannel = oconfigInfo[ring - 1].puReferences[puChannel - 1];
	oconfigInfoLock.unlock();
	
	return err;
}

int Control::moduleNum(){
	return onum;
}
