#ifndef JSHOWERFIT_INCLUDE
#define JSHOWERFIT_INCLUDE

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

#include "km3net-dataformat/online/JDAQTimeslice.hh"
#include "km3net-dataformat/online/JDAQSummaryslice.hh"

#include "JTrigger/JHit.hh"
#include "JTrigger/JTimeslice.hh"
#include "JTrigger/JHitL0.hh"
#include "JTrigger/JHitL1.hh"
#include "JTrigger/JHitR1.hh"
#include "JTrigger/JBuildL0.hh"
#include "JTrigger/JBuildL2.hh"
#include "JTrigger/JAlgorithm.hh"
#include "JTrigger/JMatch3G.hh"
#include "JTrigger/JBind2nd.hh"

#include "JSupport/JSummaryRouter.hh"

#include "JFit/JShower3EZRegressor.hh"
#include "JFit/JFitToolkit.hh"
#include "JFit/JPoint4D.hh"
#include "JFit/JModel.hh"
#include "JFit/JSimplex.hh"

#include "JAAnet/JAAnetToolkit.hh"

#include "JReconstruction/JHitW0.hh"
#include "JReconstruction/JEvt.hh"
#include "JReconstruction/JEvtToolkit.hh"
#include "JReconstruction/JShowerEnergyCorrection.hh"
#include "JReconstruction/JShowerFitParameters_t.hh"

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

#include "JGeometry3D/JVector3D.hh"
#include "JGeometry3D/JGeometry3DToolkit.hh"
#include "JGeometry3D/JOmega3D.hh"
#include "JGeometry3D/JTrack3D.hh"
#include "JGeometry3D/JTrack3E.hh"
#include "JGeometry3D/JShower3E.hh"

#include "JLang/JSharedPointer.hh"


/**
 * \author adomi
 */
namespace JRECONSTRUCTION {}
namespace JPP { using namespace JRECONSTRUCTION; }
  
namespace JRECONSTRUCTION {

  using JDETECTOR::JModuleRouter;
  using JSUPPORT::JSummaryRouter;
  using JFIT::JShowerEnergyCorrection;
  using JFIT::JRegressor;
  using JFIT::JEnergy;
  using JFIT::JShower3EZ;
  using JFIT::JSimplex;

  /**
   * class to handle the direction fit of the shower reconstruction, mainly dedicated for ORCA
   */
  class JShowerFit : 
    public JShowerFitParameters_t,
    public JRegressor<JShower3EZ, JSimplex>
  {

    typedef JRegressor<JShower3EZ, JSimplex>  JRegressor_t;
    using JRegressor_t::operator();
    
  public:

    /**
     * Parameterized constructor
     *
     * \param parameters   struct that holds default-optimized parameters for the reconstruction, available in $JPP_DATA. 
     * \param router       module router, this is built via detector file. 
     * \param summary      summary router 
     * \param pdfFile      PDF file
     * \param correct      energy correction
     * \param debug        debug
     */
    JShowerFit(const JShowerFitParameters_t&   parameters,
	       const JModuleRouter&            router,
	       const JSummaryRouter&           summary,
	       const std::string               pdfFile,
	       const JShowerEnergyCorrection&  correct,
	       const int                       debug = 0):
      JShowerFitParameters_t(parameters),
      JRegressor_t(pdfFile),
      router(router),
      summary(summary),
      correct(correct)
    {
      using namespace JPP;
      
      JRegressor_t::debug  = 1;
      JRegressor_t::T_ns.setRange(parameters.TMin_ns, parameters.TMax_ns);
      JRegressor_t::Vmax_npe = 20.0;
      JRegressor_t::MAXIMUM_ITERATIONS = 1000;
      
      this->estimator.reset(getMEstimator(parameters.mestimator));
    }

    /**
     * Declaration of the member function that actually performs the reconstruction
     *
     * \param event = JDAQEvent
     * \param in = input fits
     */ 
    JEvt operator()(const KM3NETDAQ::JDAQEvent& event, const JFIT::JEvt &in) 
    {
      using namespace std;
      using namespace JPP;

      typedef vector<JHitL0>  JDataL0_t;
      JBuildL0<JHitL0>        buildL0;

      JEvt out;

      const JDetector &detector = router.getReference();

      JDataL0_t dataL0;
      buildL0(JDAQTimeslice(event, true), router, back_inserter(dataL0));
  
      for (JEvt::const_iterator shower = in.begin(); shower != in.end(); ++shower) {

	JShower3E sh(getPosition(*shower), getDirection(*shower), shower->getT(), shower->getE());
	
	multiset<JDAQPMTIdentifier> top;

	const JFIT::JModel<JPoint4D> match(JPoint4D(sh.getPosition(), sh.getT()), roadWidth_m, JRegressor_t::T_ns);

	for (JDataL0_t::const_iterator i = dataL0.begin(); i != dataL0.end(); ++i) {

	  if (match(*i)) {
	    top.insert(i->getPMTIdentifier());
	  }
	}
	
	JDetectorSubset_t subdetector(detector, sh.getPosition(), roadWidth_m);

	const JDirection3D conversion(sh.getDirection());	    
	const JRotation3D R(conversion);
	
	vector<JPMTW0> buffer;
	  
	for (JDetectorSubset_t::iterator module = subdetector.begin();
	     module != subdetector.end(); ++module) {
	    
	  JModule dom(*module); 
	  dom.rotate(R);  

	  for (unsigned int i = 0; i != dom.size(); ++i) {

	    const JDAQPMTIdentifier id(dom.getID(), i);

	    const double rate_Hz = summary.getRate(id);
	    
	    buffer.push_back(JPMTW0(dom.getPMT(i), rate_Hz, top.count(id)));
	  }      
	}
	  
	this->step.resize(2);
	this->step[0] = JShower3EZ(JPoint4D(JVector3D(), 0.0), JVersor3Z(fit_step, 0.0), JEnergy(0.4));
	this->step[1] = JShower3EZ(JPoint4D(JVector3D(), 0.0), JVersor3Z(0.0, fit_step), JEnergy(0.4));
	  
	double chi2 = (*this)(JShower3EZ(JVertex3D(JVector3D(0,0,0), sh.getT()), JVersor3Z(), 
					 JEnergy(log10(sh.getE()))), buffer.begin(), buffer.end());
	  
	double NDF = getCount(buffer.begin(), buffer.end()) - this->step.size(); 

	JShower3E sh_fit(this->value.getPosition(), this->value.getDirection(),
			 this->value.getT(), correct(this->value.getE()));
	
	sh_fit.rotate_back(R);

	sh_fit.add(sh.getPosition());
	  
	out.push_back(getFit(JHistory(shower->getHistory()).add(JSHOWERCOMPLETEFIT), sh_fit, getQuality(chi2), 
			     NDF, sh_fit.getE())); 
	  
	out.rbegin()->setW(JSHOWERFIT_ENERGY,  this->value.getE());  // Uncorrected Energy
      }

      return out;
    }
    
    const JModuleRouter&            router;
    const JSummaryRouter&           summary;
    const JShowerEnergyCorrection&  correct;
  };
}

#endif