#ifndef DaqReadout_HH
#define DaqReadout_HH

#include <vector>
#include <fstream>
#include <iostream>

#include "RcdUserRW.hh"

#include "SubInserter.hh"
#include "SubAccessor.hh"

//#include "DaqStateData.hh"
//#include "DaqStartUp.hh"
#include "DaqRunStart.hh"
#include "DaqRunEnd.hh"
#include "DaqConfigurationStart.hh"
#include "DaqConfigurationEnd.hh"
#include "DaqAcquisitionStart.hh"
#include "DaqAcquisitionEnd.hh"
#include "DaqSpillStart.hh"
#include "DaqSpillEnd.hh"
#include "DaqTransferStart.hh"
#include "DaqTransferEnd.hh"


class DaqReadout : public RcdUserRW {

public:
  enum Counter {
    cfgInRun,
    slwInRun,slwInCfg,
    acqInRun,acqInCfg,
    splInRun,splInCfg,splInAcq,
    tsfInRun,tsfInCfg,tsfInAcq,
    trgInRun,trgInCfg,trgInAcq,trgInSpl,
    evtInRun,evtInCfg,evtInAcq,evtInTsf,
    //    ptevtInRun,ptevtInCfg,ptevtInAcq,ptevtInSpl,ptevtInTsf,
    endOfCounterEnum
  };

  DaqReadout() :
    _shmRunControl(RunControl::shmKey), _pRc(_shmRunControl.payload()) {
    assert(_pRc!=0);

    _inRun=false;
    _inConfiguration=false;
    _inAcquisition=false;
  }

  virtual ~DaqReadout() {
  }

  bool record(RcdRecord &r) {
    if(doPrint(r.recordType())) {
      std::cout << "DaqReadout::record()" << std::endl;
      r.RcdHeader::print(std::cout," ") <<std::endl;
    }

    SubInserter inserter(r);

    UtlPack tid;
    tid.halfWord(1,SubHeader::daq);
    tid.byte(2,1);
    DaqTwoTimer *t(inserter.insert<DaqTwoTimer>(true));
    t->timerId(tid.word());


    // Get time of record and check durations
    _headerTime=r.recordTime();

    if(_inRun           && _headerTime-_time[0]>_runStart.maximumTimeOfRun()) {
      if(_pRc->flag()>RunControl::runEnd) {
	_pRc->flag(RunControl::runEnd);
	if(doPrint(r.recordType(),1)) {
	  std::cout << " Run timeout " << _headerTime-_time[0]
		    << " > " << _runStart.maximumTimeOfRun() << std::endl;
	}
      }
    }

    if(_inConfiguration && _headerTime-_time[1]>_configurationStart.maximumTimeOfConfiguration()) {
      if(_pRc->flag()>RunControl::configurationEnd) {
	_pRc->flag(RunControl::configurationEnd);
	if(doPrint(r.recordType(),1)) {
	  std::cout << " Configuration timeout " << _headerTime-_time[1]
		    << " > " << _configurationStart.maximumTimeOfConfiguration() << std::endl;
	}
      }
    }

    if(_inAcquisition   && _headerTime-_time[2]>_acquisitionStart.maximumTimeOfAcquisition()) {
      if(_pRc->flag()>RunControl::acquisitionEnd) {
	_pRc->flag(RunControl::acquisitionEnd);
	if(doPrint(r.recordType(),1)) {
	  std::cout << " Acquisition timeout " << _headerTime-_time[2]
		    << " > " << _acquisitionStart.maximumTimeOfAcquisition() << std::endl;
	}
      }
    }

    if(_inSpill         && _headerTime-_time[3]>_spillStart.maximumTimeOfSpill()) {
      if(_pRc->flag()>RunControl::spillEnd) {
	_pRc->flag(RunControl::spillEnd);
	if(doPrint(r.recordType(),1)) {
	  std::cout << " Spill timeout " << _headerTime-_time[3]
		    << " > " << _spillStart.maximumTimeOfSpill() << std::endl;
	}
      }
    }

    // Check record type
    switch (r.recordType()) {

    case RcdHeader::runStart: {
      _count[cfgInRun]=0; //c
      _count[slwInRun]=0; //sr
      _count[acqInRun]=0; //a
      _count[splInRun]=0; //s
      _count[tsfInRun]=0; //t
      _count[trgInRun]=0; //e
      //_count[ptevtInRun]=0; //e
      _count[evtInRun]=0; //e

      _inRun=true;
      _time[0]=_headerTime;

      SubAccessor accessor(r);
      std::vector<const DaqRunStart*> v(accessor.extract<DaqRunStart>());
      assert(v.size()<=1);

      if(doPrint(r.recordType(),1)) v[0]->print(std::cout," ");
      
      // Save start of run information
      _runStart=*(v[0]);

      break;
    }
      
    case RcdHeader::runEnd: {
      _inRun=false;

      SubInserter inserter(r);
      DaqRunEnd *d(inserter.insert<DaqRunEnd>(true));
      
      d->runNumber(_runStart.runNumber());
      d->runType(_runStart.runType());
      d->actualNumberOfConfigurationsInRun(_count[cfgInRun]);
      d->actualNumberOfSlowReadoutsInRun(_count[slwInRun]);
      d->actualNumberOfAcquisitionsInRun(_count[acqInRun]);
      if(trgInRun>evtInRun) d->actualNumberOfEventsInRun(_count[trgInRun]);
      else                  d->actualNumberOfEventsInRun(_count[evtInRun]);
      d->actualTimeOfRun(_headerTime-_time[0]);

      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      break;
    }

    case RcdHeader::configurationStart: {
      _count[slwInCfg]=0; //sr
      _count[acqInCfg]=0; //a
      _count[splInCfg]=0; //s
      _count[tsfInCfg]=0; //t
      _count[trgInCfg]=0; //e
      //_count[ptevtInCfg]=0; //e
      _count[evtInCfg]=0; //e
 
      _inConfiguration=true;
      _time[1]=_headerTime;

      SubAccessor accessor(r);
      std::vector<const DaqConfigurationStart*> v(accessor.extract<DaqConfigurationStart>());
      assert(v.size()<=1);

      if(doPrint(r.recordType(),1)) v[0]->print(std::cout," ");
      
      // Save start of configuration information
      _configurationStart=*(v[0]);

      break;
    }

    case RcdHeader::configurationEnd: {
      _inConfiguration=false;

      SubInserter inserter(r);
      DaqConfigurationEnd *d(inserter.insert<DaqConfigurationEnd>(true));
      
      d->configurationNumberInRun(_count[cfgInRun]);
      d->actualNumberOfSlowReadoutsInConfiguration(_count[slwInCfg]);
      d->actualNumberOfAcquisitionsInConfiguration(_count[acqInCfg]);
      if(trgInCfg>evtInCfg) d->actualNumberOfEventsInConfiguration(_count[trgInCfg]);
      else                  d->actualNumberOfEventsInConfiguration(_count[evtInCfg]);
      d->actualTimeOfConfiguration(_headerTime-_time[1]);

      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      // Increment counts
      _count[cfgInRun]++;

      // Check for end of run conditions
      if(_count[cfgInRun]>=_runStart.maximumNumberOfConfigurationsInRun()) {
	if(_pRc->flag()>RunControl::runEnd) _pRc->flag(RunControl::runEnd);
      }

      break;
    }

    case RcdHeader::slowReadout: {
      _count[slwInRun]++;
      _count[slwInCfg]++;
 
      break;
    }

    case RcdHeader::acquisitionStart: {
      _count[splInAcq]=0;//s
      _count[tsfInAcq]=0;//t
      _count[trgInAcq]=0;//e
      //_count[ptevtInAcq]=0;//e
      _count[evtInAcq]=0;//e
     
      _inAcquisition=true;
      _time[2]=_headerTime;
    
      SubAccessor accessor(r);
      std::vector<const DaqAcquisitionStart*> v(accessor.extract<DaqAcquisitionStart>());
      assert(v.size()<=1);

      if(doPrint(r.recordType(),1)) v[0]->print(std::cout," ");
      
      // Save start of configuration information
      _acquisitionStart=*(v[0]);

      // Default to no spill signal
      _spilling=false;
      _inSpill=false;
      _count[trgInSpl]=0;

      break;
    }
      
    case RcdHeader::acquisitionEnd: {
      _inAcquisition=false;

      SubInserter inserter(r);
      DaqAcquisitionEnd *d(inserter.insert<DaqAcquisitionEnd>(true));

      d->acquisitionNumberInRun(_count[acqInRun]);
      d->acquisitionNumberInConfiguration(_count[acqInCfg]);
      if(trgInAcq>evtInAcq) d->actualNumberOfEventsInAcquisition(_count[trgInAcq]);
      else                  d->actualNumberOfEventsInAcquisition(_count[evtInAcq]);
      d->actualTimeOfAcquisition(_headerTime-_time[2]);

      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      // Increment counts
      _count[acqInRun]++;
      _count[acqInCfg]++;
 
      // Check for end of configuration conditions
      if(_count[acqInCfg]>=_configurationStart.maximumNumberOfAcquisitionsInConfiguration()) {
	if(_pRc->flag()>RunControl::configurationEnd) _pRc->flag(RunControl::configurationEnd);
      }

      // Check for end of run conditions
      if(_count[acqInRun]>=_runStart.maximumNumberOfAcquisitionsInRun()) {
	if(_pRc->flag()>RunControl::runEnd) _pRc->flag(RunControl::runEnd);
      }

      break;
    }

    case RcdHeader::spillStart: {
      _inSpill=true;

      // Include triggering event in count
      _count[trgInSpl]=1;
      //_count[ptevtInSpl]=0;//e
      //_count[evtInSpl]=0;//e
     
      _time[3]=_headerTime;

      // Correct time for spill poll length
      SubAccessor accessor(r);
      std::vector<const CrcLocationData<CrcBeTrgPollData>*>
	w(accessor.access< CrcLocationData<CrcBeTrgPollData> >());
      if(w.size()==1) _time[3]=w[0]->data()->endTime();
    

      std::vector<const DaqSpillStart*> v(accessor.extract<DaqSpillStart>());
      assert(v.size()<=1);

      if(doPrint(r.recordType(),1)) v[0]->print(std::cout," ");
      
      // Save start of configuration information
      _spillStart=*(v[0]);

      break;
    }
      
    case RcdHeader::spillEnd: {
      _inSpill=false;

      SubInserter inserter(r);
      DaqSpillEnd *d(inserter.insert<DaqSpillEnd>(true));

      d->spillNumberInRun(_count[splInRun]);
      d->spillNumberInConfiguration(_count[splInCfg]);
      d->actualNumberOfEventsInSpill(_count[trgInSpl]);
      d->actualTimeOfSpill(_headerTime-_time[3]);

      _count[splInRun]++;
      _count[splInCfg]++;
      _count[splInAcq]++;
 
      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      break;
    }
      
    case RcdHeader::spill: {
      _count[trgInSpl]=0;

      _time[3]=_headerTime;

      SubAccessor accessor(r);
      std::vector<const CrcLocationData<CrcBeTrgEventData>*>
	v(accessor.access< CrcLocationData<CrcBeTrgEventData> >());
      if(v.size()==1) _count[trgInSpl]=v[0]->data()->triggerCounter();

      SubInserter inserter(r);
      DaqSpillEnd *d(inserter.insert<DaqSpillEnd>(true));

      d->spillNumberInRun(_count[splInRun]);
      d->spillNumberInConfiguration(_count[splInCfg]);
      d->actualNumberOfEventsInSpill(_count[trgInSpl]);
      d->actualTimeOfSpill(_headerTime-_time[3]);

      _count[splInRun]++;
      _count[splInCfg]++;
      _count[splInAcq]++;
 
      _count[trgInRun]+=_count[trgInSpl];
      _count[trgInCfg]+=_count[trgInSpl];
      _count[trgInAcq]+=_count[trgInSpl];
      
      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      break;
    }
      
    case RcdHeader::transferStart: {
      //_count[trgInTsf]=0;//e
      //_count[ptevtInTsf]=0;//e
      _count[evtInTsf]=0;//e
     
      _time[3]=_headerTime;

      break;
    }
      
    case RcdHeader::transferEnd: {
      SubInserter inserter(r);
      DaqTransferEnd *d(inserter.insert<DaqTransferEnd>(true));

      d->transferNumberInRun(_count[tsfInRun]);
      d->transferNumberInConfiguration(_count[tsfInCfg]);
      d->actualNumberOfEventsInTransfer(_count[evtInTsf]);
      d->actualTimeOfTransfer(_headerTime-_time[3]);
      
      _count[tsfInRun]++;
      _count[tsfInCfg]++;
      _count[tsfInAcq]++;
      
      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      break;
    }

    case RcdHeader::transfer: {
      //_count[trgInTsf]=0;//e
      //_count[ptevtInTsf]=0;//e

      _count[evtInTsf]=_count[trgInSpl];
      
      SubInserter inserter(r);
      DaqTransferEnd *d(inserter.insert<DaqTransferEnd>(true));

      d->transferNumberInRun(_count[tsfInRun]);
      d->transferNumberInConfiguration(_count[tsfInCfg]);
      d->actualNumberOfEventsInTransfer(_count[evtInTsf]);
      
      _count[tsfInRun]++;
      _count[tsfInCfg]++;

      _count[evtInRun]+=_count[trgInSpl];
      _count[evtInCfg]+=_count[trgInSpl];
      _count[evtInAcq]+=_count[trgInSpl];

      if(doPrint(r.recordType(),1)) d->print(std::cout," ");

      break;
    }

    case RcdHeader::trigger: {
      /* Done in DaqConfiguration
      SubInserter inserter(r);
      DaqEvent *d(inserter.insert<DaqEvent>(true));
      
      d->eventNumberInRun(_count[trgInRun]);
      d->eventNumberInConfiguration(_count[trgInCfg]);
      d->eventNumberInAcquisition(_count[trgInAcq]);
      */
      
      _count[trgInRun]++;
      _count[trgInCfg]++;
      _count[trgInAcq]++;
      if(_inSpill) _count[trgInSpl]++;

      //RunControl::Flag oldFlag(_pRc->flag());


#ifdef SPILL_INPUT

      SubAccessor accessor(r);
      std::vector<const CrcLocationData<CrcBeTrgEventData>*>
        v(accessor.access<CrcLocationData<CrcBeTrgEventData> >());

      if(v.size()>0) {
	//bool oldS=_spilling;
	_spilling=(v[0]->data()->inputStatus()&(1<<SPILL_INPUT))!=0;
	/*
	if(_spilling!=oldS) {
	  std::cout << "DaqReadout spill status changed from ";
	  if(oldS) std::cout << "spilling to ";
	  else std::cout << "not spilling to ";
	  if(_spilling) std::cout << "spilling";
	  else std::cout << "not spilling";
	  std::cout << " input status = " << printHex(v[0]->data()->inputStatus()) << std::endl;
	}
	*/
      }

#endif

      // Check for start-of-spill conditions
      if(!_inSpill) {
	if(_spilling) {
	  if(_pRc->flag()>RunControl::spillStart) {
	    _pRc->flag(RunControl::spillStart);

	    if(doPrint(r.recordType(),1)) {
	      std::cout << " Spill start " << std::endl;
	    }
	  }
	}

      // Check for end-of-spill conditions
      } else {
	if(!_spilling || _count[trgInSpl]>=_spillStart.maximumNumberOfEventsInSpill()) {
	  if(_pRc->flag()>RunControl::spillEnd) {
	    _pRc->flag(RunControl::spillEnd);

	    if(doPrint(r.recordType(),1)) {
	      std::cout << " Spill end  spilling ";
	      if(_spilling) std::cout << "true, ";
	      else          std::cout << "false, ";
	      std::cout << _count[trgInSpl] << " >= " 
			<< _spillStart.maximumNumberOfEventsInSpill() << std::endl;
	    }
	  }
	}
      }

      // Check for end-of-acquisition conditions
      if(_count[trgInAcq]>=_acquisitionStart.maximumNumberOfEventsInAcquisition()) {
	if(_pRc->flag()>RunControl::acquisitionEnd) {
	  _pRc->flag(RunControl::acquisitionEnd);

	  if(doPrint(r.recordType(),1)) {
	    std::cout << " Acquisition end " << _count[trgInAcq] << " >= " 
		      << _acquisitionStart.maximumNumberOfEventsInAcquisition() << std::endl;
	  }
	}
      }
      
      // Check for end-of-configuration conditions
      if(_count[trgInCfg]>=_configurationStart.maximumNumberOfEventsInConfiguration()) {
	if(_pRc->flag()>RunControl::configurationEnd) {
	  _pRc->flag(RunControl::configurationEnd);
	  
	  if(doPrint(r.recordType(),1)) {
	    std::cout << " Configuration end " << _count[trgInCfg] << " >= " 
		      << _configurationStart.maximumNumberOfEventsInConfiguration() << std::endl;
	  }
	}
      }
      
      // Check for end-of-run conditions
      if(_count[trgInRun]>=_runStart.maximumNumberOfEventsInRun()) {
	if(_pRc->flag()>RunControl::runEnd) {
	  _pRc->flag(RunControl::runEnd);
	  
	  if(doPrint(r.recordType(),1)) {
	    std::cout << " Run end " << _count[trgInRun] << " >= " 
		      << _runStart.maximumNumberOfEventsInRun() << std::endl;
	  }
	}
      }
      
      //std::cout << "Trgs in acq,spill = " << _count[trgInAcq] << " " << _count[trgInSpl] << std::endl;

      //if(oldFlag!=_pRc->flag()) std::cout << "DaqReadout flag status changed from "
      //				  << oldFlag << " to " << _pRc->flag() << std::endl;

      break;
    }
      
    case RcdHeader::event: {
      _count[evtInRun]++;
      _count[evtInCfg]++;
      _count[evtInAcq]++;
      _count[evtInTsf]++;
      
      break;
    }

      /*      
    case RcdHeader::postEvent: {
      SubInserter inserter(r);
      DaqEvent *d(inserter.insert<DaqEvent>(true));
      
      d->eventNumberInRun(_count[ptevtInRun]);
      d->eventNumberInConfiguration(_count[ptevtInCfg]);
      d->eventNumberInAcquisition(_count[ptevtInAcq]);
      
      _count[ptevtInRun]++;
      _count[ptevtInCfg]++;
      _count[ptevtInAcq]++;
      _count[ptevtInTsf]++;

      if(doPrint(r.recordType(),1)) d->print(std::cout," ");
      
      break;
    }
      */

    default: {
      break;
    }
    };

    // Close off overall timer
    t->setEndTime();
    if(doPrint(r.recordType())) t->print(std::cout," ") << std::endl;

    return true;
  }

private:
  ShmObject<RunControl> _shmRunControl;
  RunControl *_pRc;

  bool _inRun,_inConfiguration,_inAcquisition,_inSpill;
  DaqRunStart _runStart;
  DaqConfigurationStart _configurationStart;
  DaqAcquisitionStart _acquisitionStart;
  DaqSpillStart _spillStart;

  bool _spilling;

  unsigned _count[endOfCounterEnum];

  unsigned _numberOfEventsInTransfer;

  UtlTime _headerTime;
  UtlTime _time[4];
};

#endif
