/***************************************************************************
**
**  This file is part of dinverext.
**
**  dinverext is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  dinverext 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 the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-04-10
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QDir>
#include <QProcess>

#include <DinverCore.h>
#include "Forward.h"
#include "ExtTarget.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=nullptr;
  _param=nullptr;
}

/*!
  Destructor
*/
Forward::~Forward()
{
  TRACE;
  // TODO BRANCH 3.4: keep track of working directories and clean them
  delete _param;
  delete _target;
}

bool Forward::setTarget(ExtTarget * target)
{
  TRACE;
  if(target!=_target) {
    delete _target;
    _target=target;
  }
  return _target->isOk(parameterSpace().allParameterCount());
}

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

AbstractForward * Forward::clone() const
{
  TRACE;
  Forward * forward=new Forward;
  if(_target) {
    if(!forward->setTarget(new ExtTarget(*_target))) {
      delete forward;
      return nullptr;
    }
  }
  if(_param) {
    if(!forward->setParam(new ParamSpaceScript(*_param))) {
      delete forward;
      return nullptr;
    }
  }
  return forward;
}

/*!
  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;
  if(_target->command().isEmpty()) return 0.0;
  QDir d(_target->workingDirectory());
  // Make sure that each thread has its own directory to communicate with forward code
  QString threadName=QString::number(reinterpret_cast<qint64>(Thread::currentThread()), 16);
  if(!d.exists(threadName)) {
    d.mkdir(threadName);
  }
  d.cd(threadName);

  QFile fp(d.absoluteFilePath("parameters"));
  if(!fp.open(QIODevice::WriteOnly)) {
    App::log(tr("Cannot write to file %1").arg(d.absoluteFilePath("parameters\n")) );
    ok=false;
    return 0.0;
  }
  QTextStream s(&fp);
  int n=parameterSpace().allParameterCount();
  for(int i=0;i<n;i++) {
    s << parameterSpace().parameter(i)->realValue() << "\n";
  }
  fp.close();

  QProcess p;
  p.setWorkingDirectory(d.absolutePath());
  p.start(_target->command());
  while(!p.waitForStarted (1000) && p.state()==QProcess::Starting) {}
  while(!p.waitForFinished (1000) && p.state()==QProcess::Running) {}
  QFile fm(d.absoluteFilePath("misfit"));
  if(!fm.open(QIODevice::ReadOnly)) {
    App::log(tr("Cannot read file %1").arg(d.absoluteFilePath("misfit\n")) );
    ok=false;
    return 0.0;
  }
  s.setDevice(&fm);
  double misfitValue;
  s >> misfitValue;
  if(isnan(misfitValue)) {
    ok=false;
  }
  return misfitValue;
}

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=="ExtTarget" ) {
    delete _target;
    _target=new ExtTarget;
    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();
  setTarget(_target);
  return true;
}
