/***************************************************************************
**
**  This file is part of dinvermatlab.
**
**  This file is distributed under the terms of the Geopsy.org Commercial
**  License appearing in the file LICENSE.COMMERCIAL included in the packaging
**  of this file.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See Geopsy.org Commercial License for
**  more details.
**
**  You should have received a copy of the Geopsy.org Commercial License
**  along with this program. If not, see <http://www.geopsy.org>.
**
**  See http://www.geopsy.org for more information.
**
**  Created : 2008-11-23
**  Authors :
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifdef GP_MATLAB_LIBS
#include <engine.h>
#endif

#include <DinverCore.h>
#include "Forward.h"
#include "MatlabTarget.h"

/*!
  \class Forward Forward.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Default constructor: initialize parameters
*/
Forward::Forward()
{
  TRACE;
  _target=0;
  _param=0;
#ifdef GP_MATLAB_LIBS
  _matlab=0;
#endif
}

/*!
  Destructor
*/
Forward::~Forward()
{
  TRACE;
  delete _param;
  delete _target;
#ifdef GP_MATLAB_LIBS
  if(_matlab) engClose(_matlab);
#endif
}

bool Forward::setTarget(MatlabTarget * target, bool skipTest)
{
  TRACE;
  if(target!=_target) {
    delete _target;
    _target=target;
  }
  if(_target->isEmpty()) {
    App::log(tr("Empty target, skipping matlab testing\n") );
    return true;
  }
  if(!skipTest) {
    return _target->isOk(parameterSpace().allParameterCount());
  }
  return true;
}

AbstractForward * Forward::clone() const
{
  TRACE;
  Forward * forward=new Forward;
  if(forward->setTarget(new MatlabTarget(*_target), true) &&
     forward->setParam(new ParamSpaceScript(*_param))) {
    return forward;
  } else {
    delete forward;
    return 0;
  }
}

bool Forward::setParam(ParamSpaceScript * param)
{
  TRACE;
  delete _param;
  _param=param;
  return _param->apply(&parameterSpace());
}

/*!
  This is the heart of the problem, enter here the forward and the misfit computation.

  You can set ok to false if the input model contains unconsistent information. By default it is set to true.
*/
double Forward::misfit(bool& ok)
{
  TRACE;
#ifdef GP_MATLAB_LIBS
  if(!_matlab) {
    App::log(tr("Initialize Matlab engine...\n") );
    App::setStreamPrefix("[Init] ");
    _matlab=engOpen(_target->startup().toLatin1().data());
    if(_matlab) {
      char buffer[MATLAB_OUTPUT_BUFFER_SIZE];
      buffer[MATLAB_OUTPUT_BUFFER_SIZE-1]='\0';
      engOutputBuffer(_matlab, buffer, MATLAB_OUTPUT_BUFFER_SIZE);
      if(!_target->scriptPath().isEmpty()) {
        QString cmd="addpath('" + _target->scriptPath() + "');";
        App::log(cmd+"\n");
        if(engEvalString( _matlab, cmd.toLatin1().data())!=0) {
          if(buffer[0]!='\0') App::log(QString(buffer+4)+"\n");
          App::setStreamPrefix("");
          engClose(_matlab);
          _matlab=0;
        }
        if(buffer[0]!='\0') App::log(QString(buffer+4)+"\n");
      }
      App::log(_target->initScript()+"\n");
      if(engEvalString( _matlab, _target->initScript().toLatin1().data())!=0) {
        if(buffer[0]!='\0') App::log(QString(buffer+4)+"\n");
        App::setStreamPrefix("");
        engClose(_matlab);
        _matlab=0;
      }
      if(buffer[0]!='\0') App::log(buffer+4)+"\n";
      App::setStreamPrefix("");
    }
  }
  if(_matlab) {
    char buffer[MATLAB_OUTPUT_BUFFER_SIZE];
    buffer[MATLAB_OUTPUT_BUFFER_SIZE-1]='\0';
    engOutputBuffer(_matlab, buffer, MATLAB_OUTPUT_BUFFER_SIZE);
    // Create matlab model variable
    int n=parameterSpace().allParameterCount();
    mxArray * dinverModel=mxCreateDoubleMatrix(1, n, mxREAL);
    for(int i=0; i<n;i++) {
      mxGetPr(dinverModel)[i]=parameterSpace().parameter(i)->realValue();
    }
    if(engPutVariable( _matlab, "dinverModel", dinverModel)!=0) {
      ok=false;
      mxDestroyArray(dinverModel);
      return 0.0;
    }
    if(engEvalString( _matlab, _target->forwardScript().toLatin1().data())!=0) {
      ok=false;
      if(buffer[0]!='\0') App::log(QString(buffer+4)+"\n");
      return 0.0;
    }
    if(buffer[0]!='\0') App::log(QString(buffer+4)+"\n");
    mxArray * dinverMisfit=engGetVariable(_matlab, "dinverMisfit" );
    if(!dinverMisfit) {
      ok=false;
      mxDestroyArray(dinverMisfit);
      return 0.0;
    }
    double misfitValue=mxGetPr(dinverMisfit)[0];
    if(isnan(misfitValue)) {
      ok=false;
    }
    mxDestroyArray(dinverMisfit);
    return misfitValue;
  } else
#endif
  {
    Q_UNUSED(ok)
    App::log(tr("Matlab engine not available, check your installation.\n") );
    return 0.0;
  }
}

void Forward::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  _target->xml_save(s, context);
  _param->xml_save(s, context);
}

XMLMember Forward::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="MatlabTarget" ) {
    delete _target;
    _target=new MatlabTarget;
    return XMLMember(_target);
  }
  else if(tag=="ParamSpaceScript" ) {
    delete _param;
    _param=new ParamSpaceScript;
    return XMLMember(_param);
  }
  else return XMLMember(XMLMember::Unknown);
}

bool Forward::xml_polish(XML_POLISH_ARGS)
{
  TRACE;
  Q_UNUSED(context)
  parameterSpace().clearParameters();
  _param->apply(&parameterSpace());
  parameterSpace().setVariableParameters();
  parameterSpace().adjustRanges();
  parameterSpace().humanInfo();
  MatlabTarget * target=_target;
  setTarget(target, true);
  return true;
}
