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

#include "TMath.h"

#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 "JAAnet/JAAnetToolkit.hh"

#include "JDetector/JDetector.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/JSupport.hh"
#include "JSupport/JMeta.hh"
#include "JSupport/JSummaryFileRouter.hh"

#include "JFit/JEnergy.hh"
#include "JFit/JEnergyRegressor.hh"

#include "JReconstruction/JEvt.hh"
#include "JReconstruction/JEvtToolkit.hh"
#include "JReconstruction/JMuonEnergyParameters_t.hh"
#include "JReconstruction/JEnergyCorrection.hh"
#include "JReconstruction/JMuonEnergy.hh"

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


/**
 * \file
 *
 * Program to perform JFIT::JRegressor<JEnergy, JGandalf> fit using I/O of JFIT::JEvt data.
 * \author mdejong
 */
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;
  JEnergyCorrection        correct;
  JMuonEnergyParameters_t  parameters;
  int                      debug;

  try { 

    JParser<> zap("Program to perform fit of muon energy to data.");
    
    zap['f'] = make_field(inputFile);
    zap['o'] = make_field(outputFile)          = "energy.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['E'] = make_field(correct)             = JEnergyCorrection();            // off
    zap['@'] = make_field(parameters)          = JPARSER::initialised();
    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);
  }

  getMechanics.load(detector.getID());

  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);

  JMuonEnergy fit(parameters, router, summary, pdfFile, 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(JMUONENERGY));
    }

    cp.select(parameters.numberOfPrefits, qualitySorter);

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

    // fit

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

    // set corrected energy

    for (JFIT::JEvt::iterator i = out.begin(); i != out.end(); ++i) {
      i->setE(correct(i->getE()));
    }

    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();
}