// @(#)root/mathcore:$Id$
// Author: L. Moneta Tue Sep  5 09:13:32 2006

/**********************************************************************
 *                                                                    *
 * Copyright (c) 2006  LCG ROOT Math Team, CERN/PH-SFT                *
 *                                                                    *
 *                                                                    *
 **********************************************************************/

// Header file for class Chi2FCN

#ifndef ROOT_Fit_Chi2FCN
#define ROOT_Fit_Chi2FCN

#include "ROOT/EExecutionPolicy.hxx"
#include "Fit/BasicFCN.h"
#include "Fit/BinData.h"
#include "Fit/FitUtil.h"
#include "Math/IFunction.h"
#include "Math/IFunctionfwd.h"
#include "Math/IParamFunction.h"

#include <memory>
#include <vector>

/**
@defgroup FitMethodFunc Fit Method Classes

Classes describing Fit Method functions
@ingroup Fit
*/

namespace ROOT {


   namespace Fit {

//___________________________________________________________________________________
/**
   Chi2FCN class for binned fits using the least square methods

   @ingroup  FitMethodFunc
*/
template<class DerivFunType, class ModelFunType = ROOT::Math::IParamMultiFunction>
class Chi2FCN : public BasicFCN<DerivFunType, ModelFunType, BinData> {

public:

   typedef typename ModelFunType::BackendType T;
   typedef  BasicFCN<DerivFunType, ModelFunType, BinData> BaseFCN;

   typedef  ::ROOT::Math::BasicFitMethodFunction<DerivFunType> BaseObjFunction;
   typedef typename  BaseObjFunction::BaseFunction BaseFunction;

   //typedef  typename ::ROOT::Math::ParamFunctionTrait<FunType>::PFType IModelFunction;
   typedef  ::ROOT::Math::IParamMultiFunctionTempl<T> IModelFunction;
   typedef typename BaseObjFunction::Type_t Type_t;

   /**
      Constructor from data set (binned ) and model function
   */
   Chi2FCN (const std::shared_ptr<BinData> & data, const std::shared_ptr<IModelFunction> & func, const ::ROOT::EExecutionPolicy &executionPolicy = ::ROOT::EExecutionPolicy::kSequential) :
      BaseFCN( data, func),
      fNEffPoints(0),
      fGrad ( std::vector<double> ( func->NPar() ) ),
      fExecutionPolicy(executionPolicy)
   { }

   /**
      Same Constructor from data set (binned ) and model function cloning the function and the data
   */
   Chi2FCN ( const BinData & data, const IModelFunction & func, const ::ROOT::EExecutionPolicy &executionPolicy = ::ROOT::EExecutionPolicy::kSequential) :
      BaseFCN(std::make_shared<BinData>(data), std::shared_ptr<IModelFunction>(dynamic_cast<IModelFunction*>(func.Clone() ) ) ),
      fNEffPoints(0),
      fGrad ( std::vector<double> ( func.NPar() ) ),
      fExecutionPolicy(executionPolicy)
   { }

   /**
      Destructor (no operations)
   */
   virtual ~Chi2FCN ()  {}
   /**
      Copy constructor
   */
   Chi2FCN(const Chi2FCN & f) :
      BaseFCN(f.DataPtr(), f.ModelFunctionPtr() ),
      fNEffPoints( f.fNEffPoints ),
      fGrad( f.fGrad),
      fExecutionPolicy(f.fExecutionPolicy)
   {  }

   /**
      Assignment operator
   */
   Chi2FCN & operator = (const Chi2FCN & rhs) {
      SetData(rhs.DataPtr() );
      SetModelFunction(rhs.ModelFunctionPtr() );
      fNEffPoints = rhs.fNEffPoints;
      fGrad = rhs.fGrad;
   }

   /*
      clone the function
    */
   BaseFunction * Clone() const override {
      return new Chi2FCN(*this);
   }



   using BaseObjFunction::operator();


   /// i-th chi-square residual
   double DataElement(const double *x, unsigned int i, double *g, double * h = nullptr, bool fullHessian = false) const override {
      if (i==0) this->UpdateNCalls();
      return FitUtil::Evaluate<T>::EvalChi2Residual(BaseFCN::ModelFunction(), BaseFCN::Data(), x, i, g, h, BaseFCN::IsAGradFCN(), fullHessian);
   }

   // need to be virtual to be instantiated
   void Gradient(const double *x, double *g) const override {
      // evaluate the chi2 gradient
      FitUtil::Evaluate<T>::EvalChi2Gradient(BaseFCN::ModelFunction(), BaseFCN::Data(), x, g, fNEffPoints,
                                             fExecutionPolicy);
   }


   /// get type of fit method function
   typename BaseObjFunction::Type_t Type() const override { return BaseObjFunction::kLeastSquare; }


protected:

   /// set number of fit points (need to be called in const methods, make it const)
   virtual void SetNFitPoints(unsigned int n) const { fNEffPoints = n; }

private:

   /**
      Evaluation of the  function (required by interface)
    */
   double DoEval (const double * x) const override {
      this->UpdateNCalls();
      if (BaseFCN::Data().HaveCoordErrors() || BaseFCN::Data().HaveAsymErrors())
         return FitUtil::Evaluate<T>::EvalChi2Effective(BaseFCN::ModelFunction(), BaseFCN::Data(), x, fNEffPoints);
      else
         return FitUtil::Evaluate<T>::EvalChi2(BaseFCN::ModelFunction(), BaseFCN::Data(), x, fNEffPoints, fExecutionPolicy);
   }

   // for derivatives
   double DoDerivative(const double * x, unsigned int icoord) const override {
      Gradient(x, fGrad.data());
      return fGrad[icoord];
   }


   mutable unsigned int fNEffPoints;  ///< number of effective points used in the fit

   mutable std::vector<double> fGrad; ///< for derivatives
   ::ROOT::EExecutionPolicy fExecutionPolicy;

};

      // define useful typedef's
      typedef Chi2FCN<ROOT::Math::IMultiGenFunction,ROOT::Math::IParamMultiFunction> Chi2Function;
      typedef Chi2FCN<ROOT::Math::IMultiGradFunction, ROOT::Math::IParamMultiFunction> Chi2GradFunction;


   } // end namespace Fit

} // end namespace ROOT


#endif /* ROOT_Fit_Chi2FCN */
