#ifndef RunRunner_HH
#define RunRunner_HH

#include <sstream>
#include <fstream>
#include <iostream>

#include "DaqRunStart.hh"
#include "DaqConfigurationStart.hh"
#include "DaqAcquisitionStart.hh"

#include "IlcRunStart.hh"
#include "IlcConfigurationStart.hh"

#include "DaqConfiguration.hh"
#include "IlcConfiguration.hh"

#include "RcdMultiUserRW.hh"
#include "RcdMultiUserRO.hh"

#include "SubAccessor.hh"

#include "RunControl.hh"
#include "ShmObject.hh"


class RunRunner {

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

#ifndef DAQ_ILC_TIMING
    DaqRunType::dumpTypes("RunControlTypes.txt");
#else
    IlcRunType::dumpTypes("RunControlTypes.txt");
#endif
  }

  virtual ~RunRunner() {
  }

  /*
  unsigned continuationFlag() const {
    return _continuationFlag;
  }
  */

  void continuationFlag(unsigned c) {
    if(c==0) _pRc->flag(RunControl::shutDown);
    if(c==1) _pRc->flag(RunControl::runEnd);
    if(c==2) _pRc->flag(RunControl::configurationEnd);
  }

#ifndef DAQ_ILC_TIMING
  void daqConfiguration(DaqConfiguration *d) {
#else
  void daqConfiguration(IlcConfiguration *d) {
#endif
    _daqConfiguration=d;
  }

  //bool run(unsigned nr, DaqRunType rt, RcdMultiUserRW rw, RcdMultiUserRO ro) {
  bool run(RcdMultiUserRW rw, RcdMultiUserRO ro) {
    _pRc->flag(RunControl::daqContinue);

    // Define record memory
    RcdArena *pArena(new RcdArena);
    assert(pArena!=0);

    RcdRecord &arena(*pArena);
    SubAccessor accessor(arena);

    // Outer loop
    while(_pRc->flag()>RunControl::shutDown) {

      // Get run information from shared memory
      _pRc->print(std::cout,"NEW SEQ ==>> ");
      unsigned nRun(_pRc->numberOfRuns());
      //unsigned pl(_pRc->printLevel());

#ifndef DAQ_ILC_TIMING
      DaqRunStart rs(_pRc->runStart());
      DaqRunType runType(rs.runType());
#else
      IlcRunStart rs(_pRc->runStart());
      IlcRunType runType(rs.runType());
#endif
      
      rw.printLevel(runType.printLevel());
      ro.printLevel(runType.printLevel());

      // Reset to default (slow monitoring) after copying contents
      _pRc->reset();
     
      // Set run type for DAQ configuration module
      _daqConfiguration->runStart(rs);

      arena.initialise(RcdHeader::sequenceStart);
      rw.record(arena);
      ro.record(arena);

      if(_pRc->flag()==RunControl::sequenceEnd) _pRc->flag(RunControl::daqContinue);


      // Loop over runs
      for(unsigned iRun(0);(iRun<nRun || nRun==0) && _pRc->flag()>RunControl::sequenceEnd;iRun++) {

	// If last run was stopped using the flag, allow the next
	if(_pRc->flag()==RunControl::runEnd) _pRc->flag(RunControl::daqContinue);
	//while(_pRc->flag()==RunControl::1) sleep(1); //???

	arena.initialise(RcdHeader::runStart);
	rw.record(arena);
	ro.record(arena);
	
	// Initialise time of last slow record to be a long time ago
	unsigned lastSlowRecord(0);


	// Loop over configurations
	while(_pRc->flag()>RunControl::runEnd) {
	  
	  // If last configuration was stopped using the flag, allow the next
	  if(_pRc->flag()==RunControl::configurationEnd) _pRc->flag(RunControl::daqContinue);
	  //while(_pRc->flag()==RunControl::2) sleep(1); //???
	  
	  arena.initialise(RcdHeader::configurationStart);
	  rw.record(arena);
	  ro.record(arena);

	  // Access the DaqConfigurationStart to get slow readout information
#ifndef DAQ_ILC_TIMING
	  std::vector<const DaqConfigurationStart*> v(accessor.access<DaqConfigurationStart>());
#else
	  std::vector<const IlcConfigurationStart*> v(accessor.access<IlcConfigurationStart>());
#endif
	  assert(v.size()==1);
	  const double slwDt(v[0]->minimumTimeBeforeSlowReadout().deltaTime());
	  

	  // Loop over acquisitions and slow readouts or over bunch trains
	  while(_pRc->flag()>RunControl::configurationEnd) {

	    // Do a slow readout if enough time since last; always done for first configuration
	    if((time(0)-lastSlowRecord)>=slwDt) {
	      arena.initialise(RcdHeader::slowReadout);
	      rw.record(arena);
	      ro.record(arena);
	      
	      lastSlowRecord=arena.recordTime().seconds();
	    }

#ifndef DAQ_ILC_TIMING
	    // If slow run, then no acquisitions, so sleep for a short while
	    // Cannot use interupts because of driver so don't sleep for total time
	    if(runType.majorType()==DaqRunType::slow) {
	      sleep(1);
	      
	    // Standard run
	    } else {

	      // If last acquisition was stopped using the flag, allow the next
	      if(_pRc->flag()==RunControl::acquisitionEnd) _pRc->flag(RunControl::daqContinue);
	      //while(_pRc->flag()==RunControl::3) sleep(1); //???
	      
	      arena.initialise(RcdHeader::acquisitionStart);
	      rw.record(arena);
	      ro.record(arena);
	      
	      
	      // Switch depending on spill state machine mode; buffer mode
	      if(runType.bufferRun()) {
		
		// Initialise event counter
		unsigned iEvt(0);
		
		// Switch if spill mode
		if(false) {
		  /*
		if(runType.spillRun()) {
		  arena.initialise(RcdHeader::spill);
		  rw.record(arena);
		  ro.record(arena);
		  
		  // Access the DaqSpillEnd
		  std::vector<const DaqSpillEnd*> v(accessor.access<DaqSpillEnd>());
		  assert(v.size()==1);
		  
		  // Get information on number of events to be read out
		  iEvt=v[0]->actualNumberOfEventsInSpill();
		  
		  */
		  
		  // Spill-with-triggers mode
		} else {
		  
		  if(_pRc->flag()==RunControl::spillEnd) _pRc->flag(RunControl::daqContinue);

		  arena.initialise(RcdHeader::spillStart);
		  rw.record(arena);
		  ro.record(arena);
		  
		  // Take triggers during spill
		  bool bufferedEvents(false);
		  while(_pRc->flag()>RunControl::transferStart) {
		    bufferedEvents=true;

		    // Take single or multiple triggers
		    if(runType.burstRun()) arena.initialise(RcdHeader::triggerBurst);
		    else                   arena.initialise(RcdHeader::trigger);
		    rw.record(arena);
		    ro.record(arena);
		    
		    // Count number of events to be read out
		    //iEvt++; // Now done below 
		  }
		  
		  // Get information on number of events to be read out
		  if(bufferedEvents) {
		    if(runType.burstRun()) {
		      std::vector<const DaqBurstEnd*> v(accessor.access<DaqBurstEnd>());
		      assert(v.size()==1);

		      //v[0]->print(std::cout," Buffer ") << std::endl;

		      iEvt=v[0]->lastTriggerNumberInAcquisition()+1;

		    } else {
		      std::vector<const DaqTrigger*> v(accessor.access<DaqTrigger>());
		      assert(v.size()==1);

		      //v[0]->print(std::cout," Buffer ") << std::endl;

		      iEvt=v[0]->triggerNumberInAcquisition()+1;
		    }
		  }

		  // If buffer fills before spillEnd, then start event transfer
		  if(_pRc->flag()==RunControl::transferStart) {
		    //std::cout << "Buffer full with " << iEvt << " triggers" << std::endl;

		    arena.initialise(RcdHeader::transferStart);
		    rw.record(arena);
		    ro.record(arena);
		    
		    while(_pRc->flag()>RunControl::spillEnd) {
		      arena.initialise(RcdHeader::trigger);
		      rw.record(arena);
		      ro.record(arena);
		      
		      arena.initialise(RcdHeader::event);
		      rw.record(arena);
		      ro.record(arena);
		    }
		    
		    arena.initialise(RcdHeader::spillEnd);
		    rw.record(arena);
		    ro.record(arena);

		  } else {
		    //std::cout << "Buffer not full with " << iEvt << " triggers" << std::endl;

		    // If buffer not full before spillEnd, then stop spill first
		    arena.initialise(RcdHeader::spillEnd);
		    rw.record(arena);
		    ro.record(arena);
		    
		    // Access the DaqSpillEnd
		    /*
		    std::vector<const DaqSpillEnd*> v(accessor.access<DaqSpillEnd>());
		    assert(v.size()==1);
		    
		    // Get information on number of events to be read out
		    iEvt=v[0]->actualNumberOfEventsInSpill();
		    */

		    arena.initialise(RcdHeader::transferStart);
		    rw.record(arena);
		    ro.record(arena);
		  }
		  
		  // Must always read out events if triggered
		  // so do not check RunControl flag here
		  for(unsigned jEvt(0);jEvt<iEvt;jEvt++) {
		    arena.initialise(RcdHeader::event);
		    rw.record(arena);
		    ro.record(arena);
		  }
		  
		  arena.initialise(RcdHeader::transferEnd);
		  rw.record(arena);
		  ro.record(arena);
		}
		
		// Single event mode
	      } else {
		
		// Loop over triggers and events
		while(_pRc->flag()>RunControl::acquisitionEnd) {
		  arena.initialise(RcdHeader::trigger);
		  rw.record(arena);
		  ro.record(arena);
		  
		  arena.initialise(RcdHeader::event);
		  rw.record(arena);
		  ro.record(arena);
		}      
	      }
	      
	      // Do acquisition end
	      arena.initialise(RcdHeader::acquisitionEnd);
	      rw.record(arena);
	      ro.record(arena);
	    }

#else
	    // If slow run, then no acquisitions, so sleep for a short while
	    // Cannot use interupts because of driver so don't sleep for total time
	    if(runType.majorType()==IlcRunType::slw) {
	      sleep(1);
	      
	    } else {
	      arena.initialise(RcdHeader::bunchTrain);
	      rw.record(arena);
	      ro.record(arena);
	    }
#endif

	  }
	
	  arena.initialise(RcdHeader::configurationEnd);
	  rw.record(arena);
	  ro.record(arena);
	}
	
	arena.initialise(RcdHeader::runEnd);
	rw.record(arena);
	ro.record(arena);
	
	// Ensure if run aborted that slow files have different timestamps
	sleep(1);
      }

      arena.initialise(RcdHeader::sequenceEnd);
      rw.record(arena);
      ro.record(arena);
    }
    
    // Clear up memory space
    delete pArena;
    
    return true;
  }
  

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

#ifndef DAQ_ILC_TIMING
  DaqConfiguration *_daqConfiguration;
#else
  IlcConfiguration *_daqConfiguration;
#endif
};

#endif
