// Author: Wim Lavrijsen   November 2010

// Bindings
#include "PyROOT.h"
#include "TPyFitFunction.h"
#include "ObjectProxy.h"
#include "MethodProxy.h"
#include "PyBufferFactory.h"

// Standard
#include <stdexcept>

//______________________________________________________________________________
//                       Python wrapper for Fit functions
//                       ================================
//


//- data ---------------------------------------------------------------------
ClassImp(TPyMultiGenFunction)
ClassImp(TPyMultiGradFunction)


//- helper functions ---------------------------------------------------------
static PyObject* GetOverriddenPyMethod( PyObject* pyself, const char* method )
{
// Retrieve an overriden method on pyself
   PyObject* pymethod = 0;

   if ( pyself && pyself != Py_None ) {
      pymethod = PyObject_GetAttrString( (PyObject*)pyself, const_cast< char* >( method ) );
      if ( ! PyROOT::MethodProxy_CheckExact( pymethod ) )
         return pymethod;

      Py_XDECREF( pymethod );
      pymethod = 0;
   }

   return pymethod;
}

static PyObject* DispatchCall( PyObject* pyself, const char* method, PyObject* pymethod = NULL,
   PyObject* arg1 = NULL, PyObject* arg2 = NULL, PyObject* arg3 = NULL )
{
// Forward <method> to python (need to refactor this with TPySelector).
   PyObject* result = 0;

// get the named method and check for python side overload by not accepting the
// binding's methodproxy
   if ( ! pymethod )
      pymethod = GetOverriddenPyMethod( pyself, method );

   if ( pymethod ) {
      result = PyObject_CallFunctionObjArgs( pymethod, arg1, arg2, arg3, NULL );
   } else {
   // means the method has not been overridden ... simply accept its not there
      result = 0; 
      PyErr_Format( PyExc_AttributeError,
         "method %s needs implementing in derived class", const_cast< char* >( method ) );
   }

   Py_XDECREF( pymethod );

   return result;
}


//- constructors/destructor --------------------------------------------------
TPyMultiGenFunction::TPyMultiGenFunction( PyObject* self ) : fPySelf( 0 )
{
// Construct a TPyMultiGenFunction derived with <self> as the underlying
   if ( self ) {
   // steal reference as this is us, as seen from python
      fPySelf = self;
   } else {
      Py_INCREF( Py_None );        // using None allows clearer diagnostics
      fPySelf = Py_None;
   }
}

//____________________________________________________________________________
TPyMultiGenFunction::~TPyMultiGenFunction()
{
// Destructor. Only deref if still holding on to Py_None (circular otherwise).
   if ( fPySelf == Py_None ) {
      Py_DECREF( fPySelf );
   }
}


//- public functions ---------------------------------------------------------
unsigned int TPyMultiGenFunction::NDim() const
{
// Simply forward the call to python self.
   PyObject* pyresult = DispatchCall( fPySelf, "NDim" );

   if ( ! pyresult ) {
      PyErr_Print();
      throw std::runtime_error( "Failure in TPyMultiGenFunction::NDim" );
   }

   unsigned int cppresult = (unsigned int)PyLong_AsLong( pyresult );
   Py_XDECREF( pyresult );

   return cppresult;
}

//____________________________________________________________________________
double TPyMultiGenFunction::DoEval( const double* x ) const
{
// Simply forward the call to python self.
   PyObject* xbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)x );
   PyObject* pyresult = DispatchCall( fPySelf, "DoEval", NULL, xbuf );
   Py_DECREF( xbuf );

   if ( ! pyresult ) {
      PyErr_Print();
      throw std::runtime_error( "Failure in TPyMultiGenFunction::DoEval" );
   }

   double cppresult = (double)PyFloat_AsDouble( pyresult );
   Py_XDECREF( pyresult );

   return cppresult;
}



//- constructors/destructor --------------------------------------------------
TPyMultiGradFunction::TPyMultiGradFunction( PyObject* self )
{
// Construct a TPyMultiGradFunction derived with <self> as the underlying
   if ( self ) {
   // steal reference as this is us, as seen from python
      fPySelf = self;
   } else {
      Py_INCREF( Py_None );        // using None allows clearer diagnostics
      fPySelf = Py_None;
   }
}

//____________________________________________________________________________
TPyMultiGradFunction::~TPyMultiGradFunction()
{
// Destructor. Only deref if still holding on to Py_None (circular otherwise).
   if ( fPySelf == Py_None ) {
      Py_DECREF( fPySelf );
   }
}


//- public functions ---------------------------------------------------------
unsigned int TPyMultiGradFunction::NDim() const
{
// Simply forward the call to python self.
   PyObject* pyresult = DispatchCall( fPySelf, "NDim" );

   if ( ! pyresult ) {
      PyErr_Print();
      throw std::runtime_error( "Failure in TPyMultiGradFunction::NDim" );
   }

   unsigned int cppresult = (unsigned int)PyLong_AsLong( pyresult );
   Py_XDECREF( pyresult );

   return cppresult;
}

//____________________________________________________________________________
double TPyMultiGradFunction::DoEval( const double* x ) const
{
// Simply forward the call to python self.
   PyObject* xbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)x );
   PyObject* pyresult = DispatchCall( fPySelf, "DoEval", NULL, xbuf );
   Py_DECREF( xbuf );

   if ( ! pyresult ) {
      PyErr_Print();
      throw std::runtime_error( "Failure in TPyMultiGradFunction::DoEval" );
   }

   double cppresult = (double)PyFloat_AsDouble( pyresult );
   Py_XDECREF( pyresult );

   return cppresult;
}

//____________________________________________________________________________
void TPyMultiGradFunction::Gradient( const double* x, double* grad ) const {
// Simply forward the call to python self.
   PyObject* pymethod = GetOverriddenPyMethod( fPySelf, "Gradient" );

   if ( pymethod ) {
      PyObject* xbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)x );
      PyObject* gbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)grad );
      PyObject* pyresult = DispatchCall( fPySelf, "Gradient", pymethod, xbuf, gbuf );
      Py_DECREF( gbuf );
      Py_DECREF( xbuf );

      if ( ! pyresult ) {
         PyErr_Print();
         throw std::runtime_error( "Failure in TPyMultiGradFunction::Gradient" );
      }

      Py_DECREF( pyresult );

   } else
      return ROOT::Math::IMultiGradFunction::Gradient( x, grad );
}

//____________________________________________________________________________
void TPyMultiGradFunction::FdF( const double* x, double& f, double* df ) const
{
// Simply forward the call to python self.
   PyObject* pymethod = GetOverriddenPyMethod( fPySelf, "FdF" );

   if ( pymethod ) {
      PyObject* xbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)x );
      PyObject* pyf = PyList_New( 1 );
      PyList_SetItem( pyf, 0, PyFloat_FromDouble( f ) );
      PyObject* dfbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)df );

      PyObject* pyresult = DispatchCall( fPySelf, "FdF", pymethod, xbuf, pyf, dfbuf );
      f = PyFloat_AsDouble( PyList_GetItem( pyf, 0 ) );

      Py_DECREF( dfbuf );
      Py_DECREF( pyf );
      Py_DECREF( xbuf );

      if ( ! pyresult ) {
         PyErr_Print();
         throw std::runtime_error( "Failure in TPyMultiGradFunction::FdF" );
      }

      Py_DECREF( pyresult );

   } else
      return ROOT::Math::IMultiGradFunction::FdF( x, f, df );
}

//____________________________________________________________________________
double TPyMultiGradFunction::DoDerivative( const double * x, unsigned int icoord ) const
{
// Simply forward the call to python self.
   PyObject* xbuf = PyROOT::TPyBufferFactory::Instance()->PyBuffer_FromMemory( (Double_t*)x );
   PyObject* pycoord = PyLong_FromLong( icoord );

   PyObject* pyresult = DispatchCall( fPySelf, "DoDerivative", NULL, xbuf, pycoord );
   Py_DECREF( pycoord );
   Py_DECREF( xbuf );

   if ( ! pyresult ) {
      PyErr_Print();
      throw std::runtime_error( "Failure in TPyMultiGradFunction::DoDerivative" );
   }

   double cppresult = (double)PyFloat_AsDouble( pyresult );
   Py_XDECREF( pyresult );

   return cppresult;
}

