/***************************************************************************
**
**  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: 2009-06-16
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>

#include "ImportanceSampling.h"
#include "ModelSet.h"
#include "AbstractForward.h"
#include "ScaledModels.h"
#include "ReportWriter.h"
#include "VoronoiNavigator.h"

namespace DinverCore {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
ImportanceSampling::ImportanceSampling()
{
  TRACE;
  _forward=0;
  _models=0;
  _randomGenerator=new Random(0);
  _report=0;
  _parameterSpaceChecksum=0;
}

/*!
  Description of destructor still missing
*/
ImportanceSampling::~ImportanceSampling()
{
  TRACE;
  delete _models;
  delete _report;
  delete _randomGenerator;
  delete _forward;
}

/*!
  Initialize report. Can be called at any time.
*/
bool ImportanceSampling::openReport(const QString& fileName)
{
  if( !fileName.isEmpty()) {
    delete _report;
    _report=new ReportWriter(fileName);
    if( !_report->open()) {
      App::log(tr("Cannot open report %1 for writing\n").arg(fileName) );
      delete _report;
      _report=0;
      return false;
    }
  }
  return true;
}

void ImportanceSampling::setSeed(int seed)
{
  delete _randomGenerator;
  _randomGenerator=new Random(seed);
}

void ImportanceSampling::setForward(AbstractForward * forward)
{
  ASSERT(!_forward);
  _forward=forward;
  RealSpace& parameterSpace=_forward->parameterSpace();
  _parameterSpaceChecksum=parameterSpace.checksum();
  // With importance sampling, misfit is never computed, used the smallest misfit vector: 1
  _models=new ModelSet(parameterSpace.variableParameterCount(), 1);
}

bool ImportanceSampling::importModels(QString fileName, bool strict)
{
  return _models->importModels(_forward->parameterSpace(), fileName, strict);
}

void ImportanceSampling::generate(int ns, int nw)
{
  RealSpace& parameterSpace=_forward->parameterSpace();
  int ndVar=parameterSpace.variableParameterCount();
  ScaledModels scaledModels(_models, scales());
  VoronoiNavigator nav(&scaledModels);

  // Start sampling from any random model
  int modelIndex=_randomGenerator->uniform(0, _models->count()-1);
  //int modelIndex=_models->bestModel(); // Idea suggested during call on Monday May 4th, Matthias, Alexandros and Cecile
                                         // Minor effect, usually a NA distribution contains a high density close to low misfits
                                         // Hence the probability to select a good model is high.
  nav.setCurrentPoint(modelIndex);
  const int * m=_models->model(modelIndex);
  for(int i=0; i < ndVar; i++) {
    parameterSpace.variableParameter(i)->setGridValue(m[i]);
  }
  _forward->valueChanged();

  ConsoleProgress progress;
  progress.setCaption(tr("Generating models"));
  int nStep=ndVar * nw;
  double xMin, xMax;
  SAFE_UNINITIALIZED(xMin,0);
  SAFE_UNINITIALIZED(xMax,0);
  int cellIndex;
  double v;
  PdfCurve pdf;
  QTextStream sOut(stdout);
  for(int is=0;is<ns;is++) {
    progress.setValue(is);
    nav.setCurrentAxis(ndVar-1);
    for(int iStep=0;iStep < nStep;iStep++ ) {
      //nav.printCurrentPoint();
      //nav.checkCurrentPoint();
      nav.incrementAxis();
      Parameter * p=parameterSpace.variableParameter(nav.currentAxis());
      p->getGridLimits(xMin, xMax);
      //if(iStep==14 || iStep==31) {
        //sOut << "#" << xMin << " " << xMax << " " << p->gridValue(1480) << " " << p->gridValue(1540) << endl;
      //}
      pdf=nav.intersections(xMin, xMax);
      //if(iStep==14 || iStep==31) {
        //sOut << "#" << p->name() << endl;
        //pdf.printCumulative(*_models, *p);
        //pdf.toStream(sOut, *_models);
      //}
      if(p->isFussy()) {     // Parameters tagged as fussy need a validation of the random value
        do {
          v=pdf.randomValue(*_randomGenerator, *_models, cellIndex);
          p->setGridValue(v);
          // Some parametrizations may need some updates before proceeding
          _forward->valueChanged(p);
        } while( !_forward->isFussyOk(p));
        nav.setValue(v);
      } else {
        v=pdf.randomValue(*_randomGenerator, *_models, cellIndex);
        nav.setValue(v);
        p->setGridValue(v);
        _forward->valueChanged(p);  // Some parametrizations may need some updates before proceeding
      }
      nav.setCurrentCell(cellIndex);
    }
    _report->addModel(0.0, _parameterSpaceChecksum, parameterSpace);
    _forward->writeReport(_report);
  }
  progress.end(ns);
}

double * ImportanceSampling::scales() const
{
  const RealSpace& parameterSpace=_forward->parameterSpace();
  int ndVar=parameterSpace.variableParameterCount();
  double * s=new double[ ndVar ];
  double tmp;
  for(int ip=0;ip<ndVar; ip++) {
    tmp=parameterSpace.variableParameter(ip)->gridCount();
    if(tmp < 1.0) tmp=1.0;
    s[ip]=1.0/(tmp);
  }
  return s;
}

} // namespace DinverCore
