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

#include "TROOT.h"
#include "TFile.h"

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

#include "JAAnet/JHead.hh"
#include "JAAnet/JHeadToolkit.hh"
#include "JAAnet/JAAnetToolkit.hh"

#include "JDAQ/JDAQEventIO.hh"

#include "JSupport/JParallelFileScanner.hh"
#include "JSupport/JTreeScanner.hh"
#include "JSupport/JMonteCarloFileSupportkit.hh"
#include "JSupport/JSupport.hh"

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

#include "JLang/JVectorize.hh"

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


/**
 * \author mdejong
 */

namespace {

  /**
   * Auxiliary data structure for formatted printing.
   */
  struct JTrack :
    public JGEOMETRY3D::JTrack3E
  {
    /**
     * Constructor.
     *
     * \param  track    track
     */
    JTrack(const JTrack3E& track) :
      JGEOMETRY3D::JTrack3E(track)
    {}

    
    /**
     * Print track.
     *
     * \param  out      output stream
     * \param  track    track
     * \return          output stream
     */
    friend inline std::ostream& operator<<(std::ostream& out, const JTrack& track)
    {
      using namespace JPP;
      
      out << FIXED(8,2)       << track.getX()  << ' '
	  << FIXED(8,2)       << track.getY()  << ' '
	  << FIXED(8,2)       << track.getZ()  << ' '
	  << FIXED(7,3)       << track.getDX() << ' '
	  << FIXED(7,3)       << track.getDY() << ' '
	  << FIXED(7,3)       << track.getDZ() << ' '
	  << FIXED(10,1)      << track.getT()  << ' '
	  << SCIENTIFIC(12,3) << track.getE();
      
      return out;
    }
  };


  /**
   * Auxiliary class to get weight of given fit.
   */
  struct JWeight : 
    public std::map<std::string, int>
  {
    /**
     * Default constructor.
     */
    JWeight() 
    {
#define MAKE_ENTRY(A) std::make_pair(#A, A)

      this->insert(MAKE_ENTRY(JGANDALF_BETA0_RAD));
      this->insert(MAKE_ENTRY(JGANDALF_BETA1_RAD));
      this->insert(MAKE_ENTRY(JGANDALF_CHI2));
      this->insert(MAKE_ENTRY(JGANDALF_NUMBER_OF_HITS));
      this->insert(MAKE_ENTRY(JENERGY_ENERGY));
      this->insert(MAKE_ENTRY(JENERGY_CHI2));
      this->insert(MAKE_ENTRY(JGANDALF_LAMBDA));
      this->insert(MAKE_ENTRY(JGANDALF_NUMBER_OF_ITERATIONS));
      this->insert(MAKE_ENTRY(JSTART_NPE_MIP));
      this->insert(MAKE_ENTRY(JSTART_NPE_MIP_TOTAL));
      this->insert(MAKE_ENTRY(JSTART_LENGTH_METRES));
      this->insert(MAKE_ENTRY(JVETO_NPE));
      this->insert(MAKE_ENTRY(JVETO_NUMBER_OF_HITS));
      this->insert(MAKE_ENTRY(JENERGY_MUON_RANGE_METRES));
      this->insert(MAKE_ENTRY(JENERGY_NOISE_LIKELIHOOD));
      this->insert(MAKE_ENTRY(JENERGY_NDF));
      this->insert(MAKE_ENTRY(JENERGY_NUMBER_OF_HITS));
      this->insert(MAKE_ENTRY(JCOPY_Z_M));
      this->insert(MAKE_ENTRY(JPP_COVERAGE_ORIENTATION));
      this->insert(MAKE_ENTRY(JPP_COVERAGE_POSITION));
      this->insert(MAKE_ENTRY(JENERGY_MINIMAL_ENERGY));
      this->insert(MAKE_ENTRY(JENERGY_MAXIMAL_ENERGY));
      this->insert(MAKE_ENTRY(JSHOWERFIT_ENERGY));
      this->insert(MAKE_ENTRY(AASHOWERFIT_ENERGY));
      this->insert(MAKE_ENTRY(AASHOWERFIT_NUMBER_OF_HITS));

#undef MAKE_ENTRY
    }

    /**
     * Get weight.
     *
     * \param  fit             fit
     * \param  key             key
     * \param  value           default value
     * \return                 value
     */
    double operator()(const JRECONSTRUCTION::JFit& fit, const std::string& key, const double value) const
    {
      const_iterator p = this->find(key);

      if (p != this->end() && fit.hasW(p->second))
	return fit.getW(p->second);
      else
	return value;
    }

  } getWeight;

  static const char* const MONTECARLO  =  "MonteCarlo";
  static const char* const HISTORY     =  "history";
}

/**
 * Auxiliary program to print fit results;
 */
int main(int argc, char **argv)
{
  using namespace std;
  using namespace JPP;
  using namespace KM3NETDAQ;

  typedef JParallelFileScanner< JTypeList<JDAQEvent, JEvt> >  JParallelFileScanner_t;
  typedef JParallelFileScanner_t::multi_pointer_type          multi_pointer_type;

  JParallelFileScanner_t inputFile;
  JLimit_t&              numberOfEvents = inputFile.getLimit();
  JQualitySorter         quality_sorter;
  size_t                 numberOfFits;
  vector<string>         keys;
  set   <string>         option;
  int                    debug;

  try {

    JParser<> zap("Auxiliary program to print fit results.");

    zap['f'] = make_field(inputFile);
    zap['n'] = make_field(numberOfEvents)      = JLimit::max();
    zap['L'] = make_field(quality_sorter)      = JPARSER::initialised();
    zap['N'] = make_field(numberOfFits)        = 1;
    zap['k'] = make_field(keys,   MAKE_STRING("print optional weights: " << get_keys(getWeight)))         = JPARSER::initialised();
    zap['+'] = make_field(option, MAKE_STRING("print optional data: " << MONTECARLO << ", " << HISTORY))  = JPARSER::initialised();
    zap['d'] = make_field(debug)               = 2;

    zap(argc, argv);
  }
  catch(const exception& error) {
    FATAL(error.what() << endl);
  }


  const int WIDTH = 16;
  
  JTreeScanner<Evt> mc(inputFile);

  JPosition3D center(0,0,0);

  try {
    center = get<JPosition3D>(getHeader(inputFile));
  } catch(const exception& error) {}
  
    
  while (inputFile.hasNext()) {

    cout << "event: " << setw(10) << inputFile.getCounter() << endl;

    multi_pointer_type ps = inputFile.next();

    JDAQEvent* tev   = ps;
    JEvt*      evt   = ps;
    
    if (quality_sorter.is_valid()) {
      sort(evt->begin(), evt->end(), quality_sorter);
    }
      
    time_converter converter;

    cout << "trigger: " << setw(10) << tev->getCounter() << ' ' 
	 << tev->getTimesliceStart() << endl;
    
    if (mc.getEntries() != 0) {

      Evt* event = mc.getEntry(tev->getCounter());

      if (event != NULL) {

	converter = time_converter(*event, *tev);

	if (option.count(MONTECARLO) != 0) {

	  if (has_neutrino(*event)) {

	    JTrack ta = getTrack(get_neutrino(*event));

	    ta.add(center);
	    
	    cout << LEFT(WIDTH) << "neutrino" << right << ' ' << ta << endl;
	  }
	  

	  for (vector<Trk>::const_iterator i = event->mc_trks.begin(); i != event->mc_trks.end(); ++i) {

	    JTrack ta = getTrack(*i);

	    ta.add(center);
	  
	    cout << LEFT(WIDTH) << (is_muon    (*i) ? "muon"     :
				    is_electron(*i) ? "electron" :
				    is_hadron  (*i) ? "hadron"   : "other") << right << ' ' << ta << endl;
	  }
	}
      }
    }

    cout << "number of fits " << setw(4) << right << evt->size() << endl;

    for (size_t i = 0; i != min(evt->size(), numberOfFits); ++i) {

      const JFit& fit = (*evt)[i];
      
      JTrack tb = getTrack(fit);
      
      tb.sub(converter.putTime());
      
      cout << LEFT(WIDTH) << "fit" << right          << ' '  
	   << tb                                     << ' ' 
	   << FIXED(7,2)  << fit.getQ()              << ' '
	   << setw(2)     << fit.getHistory().size() << '/' << fit.getStatus();

      for (const auto& key : keys) {
	cout << ' ' << SCIENTIFIC(12,3) << getWeight(fit, key, 0.0);
      }
      
      cout << endl;

      for (size_t row = 0; row != fit.getDimensionOfErrorMatrix(); ++row) {
	for (size_t col = 0; col <= row; ++col) {
	  cout << ' ' << SCIENTIFIC(12,3) << fit.getV(row,col);
	}
	cout << endl;
      }

      if (option.count(HISTORY) != 0) {
	cout << fit.getHistory() << endl;
      }
    }
  }
}