// @(#)root/mathmore:$Id$
// Author: L. Moneta Wed Dec  6 11:45:55 2006

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

// Header file for class MultiDimParamFunctionAdapter

#ifndef ROOT_Math_MultiDimParamFunctionAdapter
#define ROOT_Math_MultiDimParamFunctionAdapter

#include "Math/IFunction.h"
#include "Math/IParamFunction.h"

#include "Math/WrappedFunction.h"

namespace ROOT {

   namespace Math {


      /**
         MultiDimParamFunctionAdapter class to wrap a one-dimensional parametric function in
         a multi dimensional parametric function interface
         This is used typically in fitting where internally the function is stored as multidimensional

         To wrap a non-parametric one-dim function in a multi-dim interface one can use simply a
         ROOT::Math::WrappedFunction<ROOT::Math::IGenFunction> or ROOT::Math::Functor
         and ROOT::Math::GradFunctor for gradient functions

         This class differs from WrappedParamFunction in the fact that the parameters are not stored in
         the adapter class and optionally it keeps a cloned and managed copy of the adapter class.

         @ingroup  ParamFunc

      */
      class MultiDimParamFunctionAdapter : public IParametricFunctionMultiDimTempl<double>  {

      public:

         typedef IParamMultiFunction::BaseFunc BaseFunc;


         /**
            Constructor from a parametric one dim function interface from a const reference
            Own the function in this case
         */
         MultiDimParamFunctionAdapter(const IParamFunction &f) :
            fOwn(true)
         {
            fFunc = dynamic_cast<IParamFunction *>(f.Clone());
         }

         /**
            Constructor from a parametric one dim function interface from a non-const reference
            Do not own the function in this case
         */
         MultiDimParamFunctionAdapter(IParamFunction &f) :
            fOwn(false),
            fFunc(&f)
         { }


         /**
            Copy constructor. Different behaviour according if function is owned or not
          */
         MultiDimParamFunctionAdapter(const MultiDimParamFunctionAdapter &rhs) :
            BaseFunc(),
            IParamMultiFunction(),
            fOwn(rhs.fOwn),
            fFunc(nullptr)
         {
            if (fOwn)
               fFunc = dynamic_cast<IParamFunction *>((rhs.fFunc)->Clone());
         }

         /**
            Destructor (no operations)
         */
         ~MultiDimParamFunctionAdapter() override
         {
            if (fOwn && fFunc) delete fFunc;
         }


         /**
            Assignment operator
          */
         MultiDimParamFunctionAdapter &operator=(const MultiDimParamFunctionAdapter &rhs)
         {
            fOwn = rhs.fOwn;
            if (fOwn) {
               if (fFunc) delete fFunc; // delete previously existing copy
               fFunc = dynamic_cast<IParamFunction *>((rhs.fFunc)->Clone());
            } else
               fFunc = rhs.fFunc;

            return *this;
         }

         /**
            clone
         */
         BaseFunc *Clone() const override
         {
            return new MultiDimParamFunctionAdapter(*this);
         }

      public:

         // methods required by interface
         const double *Parameters() const override
         {
            return  fFunc->Parameters();
         }

         void SetParameters(const double *p) override
         {
            fFunc->SetParameters(p);
         }

         unsigned int NPar() const override
         {
            return fFunc->NPar();
         }

         unsigned int NDim() const override
         {
            return 1;
         }


      private:

         /// needed by the interface
         double DoEvalPar(const double *x, const double *p) const override
         {
            return (*fFunc)(*x, p);
         }


      private:

         bool fOwn;
         IParamFunction *fFunc;

      };



      /**
         MultiDimParamGradFunctionAdapter class to wrap a one-dimensional parametric gradient function in
         a multi dimensional parametric gradient function interface
         This is used typically in fitting where internally the function is stored as multidimensional

         To wrap a non-parametric one-dim gradient function in a multi-dim interface one can use simply a
           a ROOT::Math::GradFunctor

         The parameters are not stored in the adapter class and by default the pointer to the 1D function is owned.
         This means that deleting the class deletes also the 1D function and copying the class copies also the
         1D function
         This class differs from WrappedParamFunction in the fact that the parameters are not stored in
         the adapter class and optionally it keeps a cloned and managed copy of the adapter class.

         @ingroup  ParamFunc

      */
      class MultiDimParamGradFunctionAdapter : public IParamMultiGradFunction  {

      public:

         typedef IParamMultiGradFunction::BaseFunc BaseFunc;


         /**
            Constructor from a param one dim function interface from a const reference
            Copy and manage the own function pointer
         */
         MultiDimParamGradFunctionAdapter(const IParamGradFunction &f) :
            fOwn(true)
         {
            fFunc = dynamic_cast<IParamGradFunction *>(f.Clone());
         }

         /**
            Constructor from a param one dim function interface from a non const reference
            Do not  own the function pointer in this case
         */
         MultiDimParamGradFunctionAdapter(IParamGradFunction &f) :
            fOwn(false),
            fFunc(&f)
         { }


         /**
            Copy constructor. Different behaviour according if function is owned or not
          */
         MultiDimParamGradFunctionAdapter(const MultiDimParamGradFunctionAdapter &rhs) :
            BaseFunc(),
            IParamMultiGradFunction(),
            fOwn(rhs.fOwn),
            fFunc(rhs.fFunc)
         {
            if (fOwn)
               fFunc = dynamic_cast<IParamGradFunction *>((rhs.fFunc)->Clone());
         }

         /**
            Destructor (no operations)
         */
         ~MultiDimParamGradFunctionAdapter() override
         {
            if (fOwn && fFunc) delete fFunc;
         }


         /**
            Assignment operator
          */
         MultiDimParamGradFunctionAdapter &operator=(const MultiDimParamGradFunctionAdapter &rhs)
         {
            fOwn = rhs.fOwn;
            if (fOwn) {
               if (fFunc) delete fFunc; // delete previously existing copy
               fFunc = dynamic_cast<IParamGradFunction *>((rhs.fFunc)->Clone());
            } else
               fFunc = rhs.fFunc;

            return *this;
         }

         /**
            clone
         */
         BaseFunc *Clone() const override
         {
            return new MultiDimParamGradFunctionAdapter(*this);
         }

      public:

         // methods required by interface
         const double *Parameters() const override
         {
            return  fFunc->Parameters();
         }

         void SetParameters(const double *p) override
         {
            fFunc->SetParameters(p);
         }

         unsigned int NPar() const override
         {
            return fFunc->NPar();
         }

         unsigned int NDim() const override
         {
            return 1;
         }

//    void Gradient(const double *x, double * grad) const {
//       grad[0] = fFunc->Derivative( *x);
//    }

         void ParameterGradient(const double *x, const double *p, double *grad) const override
         {
            fFunc->ParameterGradient(*x, p, grad);
         }

         //  using IParamMultiGradFunction::BaseFunc::operator();

      private:

         /// functions needed by interface
         double DoEvalPar(const double *x, const double *p) const override
         {
            return (*fFunc)(*x, p);
         }

//    double DoDerivative(const double * x, unsigned int ) const {
//       return fFunc->Derivative(*x);
//    }

         double DoParameterDerivative(const double *x, const double *p, unsigned int ipar) const override
         {
            return fFunc->ParameterDerivative(*x, p, ipar);
         }

      private:

         bool fOwn;
         IParamGradFunction *fFunc;

      };




   } // end namespace Math

} // end namespace ROOT


#endif /* ROOT_Math_MultiDimParamFunctionAdapter */
