#include <string>
#include <iostream>
#include <iomanip>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#include <memory>

#include "km3net-dataformat/offline/Head.hh"
#include "km3net-dataformat/offline/MultiHead.hh"
#include "km3net-dataformat/offline/Evt.hh"
#include "km3net-dataformat/definitions/fitparameters.hh"

#include "JDAQ/JDAQEventIO.hh"
#include "JDAQ/JDAQTimesliceIO.hh"
#include "JDAQ/JDAQSummarysliceIO.hh"

#include "JTrigger/JTriggerParameters.hh"

#include "JDetector/JDetector.hh"
#include "JDetector/JDetectorSubset.hh"
#include "JDetector/JDetectorToolkit.hh"
#include "JDetector/JModuleRouter.hh"

#include "JDynamics/JDynamics.hh"

#include "JSupport/JSingleFileScanner.hh"
#include "JSupport/JParallelFileScanner.hh"
#include "JSupport/JFileRecorder.hh"
#include "JSupport/JSummaryFileRouter.hh"
#include "JSupport/JSupport.hh"
#include "JSupport/JMeta.hh"

#include "JReconstruction/JEvt.hh"
#include "JReconstruction/JEvtToolkit.hh"
#include "JReconstruction/JMuonStartParameters_t.hh"
#include "JReconstruction/JMuonStart.hh"

#include "JPhysics/JK40Rates.hh"
#include "JPhysics/KM3NeT.hh"

#include "Jeep/JPrint.hh"
#include "Jeep/JParser.hh"
#include "Jeep/JMessage.hh"

/**
 * \file
 *
 * Program to perform JFIT::JRegressor<JEnergy> fit with I/O of JFIT::JEvt data.
 * \author mdejong, azegarelli, scelli
 */
int main(int argc, char **argv)
{
  using namespace std;
  using namespace JPP;
  using namespace KM3NETDAQ;

  typedef JTYPELIST<JAllTypes_t, JFIT::JEvt>::typelist             typelist;
  typedef JParallelFileScanner< JTypeList<JDAQEvent, JFIT::JEvt> > JParallelFileScanner_t;
  typedef JParallelFileScanner_t::multi_pointer_type               multi_pointer_type;
  typedef JTYPELIST<JCOMPASS::JOrientation,
		    JACOUSTICS::JEvt>::typelist                    calibration_types;
  typedef JMultipleFileScanner<calibration_types>                  JCalibration_t;

  JParallelFileScanner_t   inputFile;
  JFileRecorder<typelist>  outputFile;
  JLimit_t&                numberOfEvents = inputFile.getLimit();
  string                   detectorFile;
  JCalibration_t           calibrationFile;
  double                   Tmax_s;
  string                   pdfFile;
  JMuonStartParameters_t   parameters;
  JK40Rates                rates_Hz;
  int                      debug;

  try { 

    JParser<> zap("Program to perform PDF fit of muon trajectory to data.");
    
    zap['f'] = make_field(inputFile);
    zap['o'] = make_field(outputFile)          = "start.root";
    zap['a'] = make_field(detectorFile);
    zap['+'] = make_field(calibrationFile)     = JPARSER::initialised();
    zap['T'] = make_field(Tmax_s)              = 100.0;
    zap['n'] = make_field(numberOfEvents)      = JLimit::max();
    zap['P'] = make_field(pdfFile);
    zap['@'] = make_field(parameters)          = JPARSER::initialised();
    zap['B'] = make_field(rates_Hz)            = KM3NET::getK40Rates();
    zap['d'] = make_field(debug)               = 1;
    
    zap(argc, argv);
  }
  catch(const exception& error) {
    FATAL(error.what() << endl);
  }


  JDetector detector;

  try {
    load(detectorFile, detector);
  }
  catch(const JException& error) {
    FATAL(error);
  }

  unique_ptr<JDynamics> dynamics;

  try {

    dynamics.reset(new JDynamics(detector, Tmax_s));

    dynamics->load(calibrationFile);
  }
  catch(const exception& error) {
    if (!calibrationFile.empty()) {
      FATAL(error.what());
    }
  }

  const JModuleRouter router(dynamics ? dynamics->getDetector() : detector);

  JSummaryFileRouter  summary(inputFile, parameters.R_Hz);

  JMuonStart fit(parameters, router, summary, pdfFile, rates_Hz, debug);

  outputFile.open();
  outputFile.put(JMeta(argc, argv));

  while (inputFile.hasNext()) {

    STATUS("event: " << setw(10) << inputFile.getCounter() << '\r'); DEBUG(endl);

    multi_pointer_type ps  = inputFile.next();

    const JDAQEvent*   tev = ps;
    const JFIT::JEvt*  in  = ps;

    summary.update(*tev);

    if (dynamics) { 
      dynamics->update(*tev);
    }

    // select start values

    JFIT::JEvt cp  = *in;

    if (parameters.reprocess) {
      cp.select(JHistory::is_not_event(JMUONSTART));
    }

    cp.select(parameters.numberOfPrefits, qualitySorter);

    if (!cp.empty()) {
      cp.select(JHistory::is_event(cp.begin()->getHistory()));
    }

    // fit

    JFIT::JEvt out = fit(*tev, cp);
 
    if (dynamics) { 

      const JDynamics::coverage_type coverage = dynamics->getCoverage();

      for (JFIT::JEvt::iterator i = out.begin(); i != out.end(); ++i) {
	i->setW(JPP_COVERAGE_ORIENTATION, coverage.orientation);
	i->setW(JPP_COVERAGE_POSITION,    coverage.position);
      }
    }

    // apply default sorter
      
    sort(out.begin(), out.end(), qualitySorter);
    
    copy(in->begin(), in->end(), back_inserter(out));
    
    outputFile.put(out);
  }
  STATUS(endl);
  
  JSingleFileScanner<JRemove<typelist, JFIT::JEvt>::typelist> io(inputFile);
  
  io >> outputFile;
  
  outputFile.close();
}