/***************************************************************************
**
**  This file is part of DinverCore.
**
**  DinverCore 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.
**
**  DinverCore 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: 2008-06-13
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>
#include "BatchRun.h"
#include "XMLDinver.h"
#include "XMLDinverHeader.h"
#include "XMLDinverContext.h"
#include "AbstractForward.h"
#include "DinverInterface.h"
#include "ImportanceSampling.h"
#include "ReportReader.h"
#include "ReportWriter.h"
#include "ModelRepository.h"
#include "DinverCoreEngine.h"

namespace DinverCore {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
BatchRun::BatchRun(QObject * parent)
    : QObject(parent)
{
  TRACE;
}

/*!
  Description of destructor still missing
*/
BatchRun::~BatchRun()
{
  TRACE;
}

bool BatchRun::setParameters(const QString& paramFile)
{
  TRACE;
  DinverInterface * plugin=DinverCoreEngine::instance()->defaultPlugin();
  if(!plugin || !plugin->isValid()) {
    App::log(tr("No plugin selected or not a valid plugin (check versions)\n") );
    return false;
  }
  if(!plugin->xmlSupport()) {
    App::log(tr("Plugin does not support importation of parameters from XML\n") );
    return false;
  }
  XMLDinverContext dinverContext(XMLDinverContext::Parameters);
  XMLDinver dinver(plugin);
  XMLDinverHeader hdr(&dinver);
  return hdr.xml_restoreFile(paramFile, &dinverContext)==XMLClass::NoError;
}

bool BatchRun::setTargets(const QString& targetFile)
{
  TRACE;
  DinverInterface * plugin=DinverCoreEngine::instance()->defaultPlugin();
  if(!plugin || !plugin->isValid()) {
    App::log(tr("No plugin selected or not a valid plugin (check versions)\n") );
    return false;
  }
  if(!plugin->xmlSupport()) {
    App::log(tr("Plugin does not support importation of targets from XML\n") );
    return false;
  }
  XMLDinverContext dinverContext(XMLDinverContext::Targets);
  XMLDinver dinver(plugin);
  XMLDinverHeader hdr(&dinver);
  return hdr.xml_restoreFile(targetFile, &dinverContext)==XMLClass::NoError;
}

AbstractForward * BatchRun::createForward()
{
  TRACE;
  DinverInterface * plugin=DinverCoreEngine::instance()->defaultPlugin();
  AbstractForward * forwardTemplate=plugin->createForward();
  if(!plugin->initForward(forwardTemplate)) {
    App::log(tr("Error creating forward object\n") );
    delete forwardTemplate;
    return 0;
  }
  AbstractForward * forward=forwardTemplate->clone();
  delete forwardTemplate;
  if(!forward) {
    App::log(tr("Error creating forward object\n") );
    return 0;
  }
  forward->parameterSpace().setVariableParameters();
  if(!forward->parameterSpace().adjustRanges()) {
    App::log(tr("Error adjusting ranges\n") );
    delete forward;
    return 0;
  }
  forward->parameterSpace().humanInfo();
  return forward;
}

bool BatchRun::neighborhoodOptimization(int ns0, int ns, int nr,
                                        int expectedBestModelCount, double maximumSavedMisfit,
                                        QString fileName, ReportWriter::Action reportAction)
{
  return neighborhoodOptimization(createForward(),
                                  ns0, ns, nr,
                                  expectedBestModelCount, maximumSavedMisfit,
                                  fileName, reportAction);
}

bool BatchRun::neighborhoodOptimization(AbstractForward * forward, int ns0, int ns, int nr,
                                        int expectedBestModelCount, double maximumSavedMisfit,
                                        QString fileName, ReportWriter::Action reportAction)
{
  TRACE;
  LOCAL_POINTER(AbstractForward, forward);
  if(!forward) return false;

  ModelRepository rep;
  rep.setForward(forward);
  rep.setStorage();
  rep.setMaximumSavedMisfit(maximumSavedMisfit);
  rep.setMaximumBestModelCount(expectedBestModelCount);
  rep.setMaximumQueueLength(100);

  if(!ReportWriter::initReport(fileName, tr("Starting inversion run"), reportAction)) {
    App::log(tr("Error creating output report %1\n").arg(fileName) );
    return false;
  }
  if(reportAction==ReportWriter::Append) {
    rep.importModels(fileName, true);
  }
  if(!rep.openReport(fileName)) {
    App::log(tr("Error opening output report %1\n").arg(fileName) );
    return false;
  }

  App::log(tr("\n---------------------- Starting at ")
           +QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")+"\n\n"
           +tr(" Num samples Monte-Carlo             =%1\n").arg(ns0)
           +tr(" Num samples Neighborhood            =%1\n").arg(ns)
           +tr(" Num cells to resample               =%1\n").arg(nr)
           +tr(" Num cells to resample               =%1\n").arg(nr)
           +tr(" Num of walks before accepting model =%1\n").arg(2)
           +tr(" Max reject ratio before cell give up=%1\n").arg(0.9)
           +tr(" Dimension of parameter space        =%1\n").arg(forward->parameterSpace().variableParameterCount())
           +tr(" Parameterization checksum           =%1\n").arg(forward->parameterSpace().checksum())
           +tr(" Total number of models              =%1\n").arg(rep.validModelCount()+ns0+ns)
           +tr(" Model file                          =%1\n\n").arg(fileName));

  QElapsedTimer chrono;
  chrono.start();
  rep.setMaximumModelCount(ns0);
  if(!rep.start(Thread::idealThreadCount(), Generator::MonteCarlo)) {
    App::log(tr("Error in initialization of first model (Monte-Carlo)\n") );
    return false;
  }
  rep.wait();
  int monteCarloTime=chrono.elapsed();

  rep.setMaximumModelCount(ns);
  if(rep.validModelCount()>0) {
    if(!rep.start(Thread::idealThreadCount(), Generator::Neighborhood)) {
      App::log(tr("Error in initialization of first model (Neighborhood)\n") );
      return false;
    }
    rep.wait();
  } else {
    App::log(tr("ERROR: at least one valid model is required to start neighborhood.\n"));
  }
  int neighborhoodTime=chrono.elapsed()-monteCarloTime;
  // Time report:
  App::log(tr("\nGlobal timing report\n"
                "--------------\n"));
  App::log(tr("Monte-Carlo generation: %1 ms (%2 models/s)\n").arg(monteCarloTime).arg(1000.0*ns0/monteCarloTime) );
  App::log(tr("Neighborhood generation: %1 ms (%2 models/s)\n").arg(neighborhoodTime).arg(1000.0*ns/neighborhoodTime) );
  rep.printActiveModels();
  return true;
}

bool BatchRun::importanceSampling(int ns, int seed, QString baseFileName,
                                  QString fileName, ReportWriter::Action reportAction)
{
  TRACE;
  AbstractForward * forward=createForward();
  if(!forward) return false;

  ImportanceSampling is;
  is.setForward(forward);
  is.setSeed(seed);
  if(!ReportWriter::initReport(fileName, tr("Starting inversion run"), reportAction)) {
    App::log(tr("Run not started\n") );
    return false;
  }
  if(!is.openReport(fileName)) {
    App::log(tr("Error opening output report %1\n").arg(fileName) );
    return false;
  }
  if(!is.importModels(baseFileName)) {
    App::log(tr("Error opening output report %1\n").arg(fileName) );
    return false;
  }
  is.generate(ns);
  return true;
}

} // namespace DinverCore
