/***************************************************************************
**
**  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: 2010-12-21
**  Copyright: 2010-2019
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "Generator.h"
#include "ParentIndex.h"
#include "RealSpace.h"
#include "AbstractForward.h"
#include "VoronoiNavigator.h"
#include "ModelRepository.h"
#include "NewModel.h"

namespace DinverCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Generator::Generator(ModelRepository * repository,
                       AbstractForward * forward,
                       int seed)
    : Thread(), _terminated(false)
  {
    TRACE;
    _type=MonteCarlo;
    _walkCount=2;

    // In a parallel implementations, random seed is meanningless
    // The random pathway depends upon CPU load and task distributions
    // Internal seed cannot be set from current time: all threads are created
    // at the same time, even if they will rapidly diverge, we ensure different values.
    _randomNumbers=new Random(seed);
    _repository=repository;
    _forward=forward;
    _generatorModelsSerialNumber=0; // First S/N starts at 1;
    _nav=nullptr;
  }

  /*!
    Description of destructor still missing
  */
  Generator::~Generator()
  {
    TRACE;
    delete _randomNumbers;
    delete _forward;
    delete _nav;
  }

  /*!
  */
  void Generator::run()
  {
    TRACE;
    switch(_type) {
    case MonteCarlo:
      monteCarloLoop();
      break;
    case Neighborhood:
      neighborhoodLoop();
      break;
    }
  }

  /*!
  */
  void Generator::monteCarloLoop()
  {
    TRACE;

    forever {
      NewModel * newModel=new NewModel;

      monteCarlo(newModel);

      if(_terminated.value()) {
        delete newModel;
        break;
      } else {
        if(newModel->isValidMisfit()) {
          _repository->addReport(newModel, _forward);
        }
        _repository->addModel(newModel);
      }
    }
  }

  void Generator::monteCarlo(NewModel * newModel)
  {
    TRACE;

    RealSpace& parameterSpace=_forward->parameterSpace();
    int ndVar=parameterSpace.variableParameterCount();
    ASSERT(parameterSpace.isOkDebug());
    int minp, maxp;
    SAFE_UNINITIALIZED(minp, 0);
    SAFE_UNINITIALIZED(maxp, 0);

    // Save current model is case of failure
    int * backupModel=new int[ndVar];
    for(int ip=0; ip<ndVar; ip++) {
      backupModel[ip]=parameterSpace.variableParameter(ip)->gridValue();
    }

    // Some parametrizations may need some updates before proceeding
    _forward->valueChanged();

//#define MONTECARLO_DEBUG
    forever {
      // Makes a minimum number of random walks to provide a statistically
      // independent model (from the current one)
      for(int iw=0; iw<_walkCount; iw++) {
        for(int i=0; i<ndVar; i++) {
          Parameter * p=parameterSpace.variableParameter(i);
#ifdef MONTECARLO_DEBUG
          App::log(tr("randomWalk: parameter[%1]:%2\n").arg(i).arg(p->name()));
          App::log(parameterSpace.toString()+"\n");
#endif
          p->getGridLimits(minp, maxp);
#ifdef MONTECARLO_DEBUG
          App::log(tr(" --> from %1 to %2\n").arg(minp).arg(maxp));
#endif
          if(maxp<minp) {
            App::log(tr("Generating random model with random walk\n") );
            App::log(tr("Parameter %1: bad parameter range [%2, %3]\n").arg(p->name())
                .arg(p->realValue(minp)).arg(p->realValue(maxp)));
            App::log(parameterSpace.toString()+"\n");
            _repository->stop(); // force termination of inversion
            delete [] backupModel;
            return;
          } else if(minp<maxp) {
            if(p->isFussy()) {
              int nTries;
              // Allow for a maximum of 100 tries before giving up
              // This is not really a good solution but in some cases it avoids the process to be blocked
              for(nTries=0; nTries<100; nTries++) {
                p->setGridValue(_randomNumbers->uniform(minp, maxp) );
                // Some parametrizations may need some updates before proceeding
                _forward->valueChanged(p);
                if(_terminated.value()) {
                  //App::log(tr("Aborting Monte Carlo.\n") );
                  // Current model does not satisfy all conditions, we fix it
                  for(int ip=0; ip<ndVar; ip++) {
                    parameterSpace.variableParameter(ip)->setGridValue(backupModel[ip]);
                  }
                  _forward->valueChanged();
                  delete [] backupModel;
                  return;
                }
                if(_forward->isFussyOk(p)) {
                  /*if(nTries>0) {
                    App::log(tr("Fussy parameter accepted after %1 tries\n").arg(nTries) );
                  }*/
                  break;
                }
              }
              if(nTries==100) {
                App::log(tr("Fussy parameter rejected after %1 tries, trying another model.\n").arg(nTries) );
                // Current model does not satisfy all conditions, we fix it
                for(int ip=0; ip<ndVar; ip++) {
                  parameterSpace.variableParameter(ip)->setGridValue(backupModel[ip]);
                }
                _forward->valueChanged();
                delete [] backupModel;
                return;
              }
            } else {
              p->setGridValue(_randomNumbers->uniform(minp, maxp));
              // Some parametrizations may need some updates before proceeding
              _forward->valueChanged(p);
            }
          } else {
            p->setGridValue(minp);
            // Some parametrizations may need some updates before proceeding
            _forward->valueChanged(p);
          }
#ifdef MONTECARLO_DEBUG
          App::log(tr(" \t---> grid %1 real %2\n").arg(p->gridValue()).arg(p->realValue()));
          ASSERT(_forward->isOk());
          if(fabs(parameterSpace.variableParameter(1)->realValue()-234.5)<0.1 &&
             fabs(parameterSpace.variableParameter(2)->realValue()-2.78677)<0.00001) {
            printf("\n");
          }
          ASSERT(parameterSpace.isOkDebug());
#endif
        }
      }
      newModel->setModelIndex(_repository->allModels()->add(parameterSpace));
      if(newModel->modelIndex()>-1) {
        ASSERT(parameterSpace.isOkDebug());
        bool ok=true;
        newModel->setMisfit(_forward->misfit(ok));
        newModel->setValidMisfit(ok);
        newModel->setValidParent(true);
        delete [] backupModel;
        return;
      } else {
        // For low dimension parameter spaces (<3), the space can be fully investigated
        // We put a threshold at 80% coverage to abort the inversion process.
        double nPossibleModels=parameterSpace.possibleCount();
        _repository->allModels()->lock();
        if(_repository->allModels()->count()>0.8*nPossibleModels) {
          _repository->allModels()->unlock();
          _repository->stop(); // force termination of inversion
          return;
        }
        _repository->allModels()->unlock();
      }
    }
  }

  /*!
  */
  void Generator::neighborhoodLoop()
  {
    TRACE;

   forever {
      // Selects randomly one of the best cells
      ParentIndex * parentIndex=_repository->createModel(_randomNumbers->ran2(), _forward->parameterSpace());
      if(_generatorModelsSerialNumber!=parentIndex->serialNumber()) {
        delete _nav;
        _nav=new VoronoiNavigator(parentIndex->scaledModels());
        _generatorModelsSerialNumber=parentIndex->serialNumber();
      }
      NewModel * newModel=new NewModel;
      newModel->setParentIndex(parentIndex);

      neighborhood(newModel);
      if(_terminated.value()) {
        delete newModel;
        break;
      } else {
        if(newModel->isValidMisfit()) {
          _repository->addReport(newModel, _forward);
        }
        _repository->addModel(newModel);
      }
    }
  }

  void Generator::neighborhood(NewModel * newModel)
  {
    TRACE;

    RealSpace& parameterSpace=_forward->parameterSpace();
    int ndVar=parameterSpace.variableParameterCount();
    int nSteps=ndVar*_walkCount;

    _nav->setCurrentPoint(newModel->parentIndex()->activeIndex());
    _nav->setCurrentAxis(ndVar-1);

    // Some parametrizations may need some updates before proceeding
    _forward->valueChanged();

    // Do random walks, if after 10 tries no new model can be generated (different from
    // all others generated so far), the cell is considered as atomic (only one point inside).
    int iStep, iTries;
    int xMin, xMax, iMin, iMax;
    for(iTries=0; iTries<10; iTries++) {
      //printf("  Try %3i\n", iTries);
      int atomicAxis=0;
      //_parentVolume=1.0;
      for(iStep=0; iStep<nSteps; iStep++) {
        _nav->incrementAxis();
        //printf("    Step %3i Axis %i\n", iStep, nav.currentAxis());
        //_nav->checkAxisDistances();
        Parameter * p=parameterSpace.variableParameter(_nav->currentAxis());
        p->getGridLimits(xMin, xMax);
        //printf("      Parameter min %5i max %5i\n", xMin, xMax);
        if(xMin>=xMax) {
          atomicAxis++;
          if(atomicAxis==ndVar && iStep==(ndVar-1)) {
            return;
          }
        }
        _nav->cellLimits(xMin, xMax, iMin, iMax);

        // Update volume estimation
        //_parentVolume*=abs(xMax-xMin)+1;

        // Marks neighboring cells as active
        if(iMin>-1) {
          newModel->addNavigatorHit(iMin);
        }
        if(iMax>-1) {
          newModel->addNavigatorHit(iMax);
        }

        //printf("      Cell min %5i max %5i\n", xMin, xMax);
        // Check for atomic cell. A cell becomes atomic when it is atomic in all directions.
        if(xMin>=xMax) {
          atomicAxis++;
          if(atomicAxis==ndVar && iStep==(ndVar-1)) {
            return;
          }
        } else {
          if(p->isFussy()) {     // Parameters tagged as fussy need a validation of the random value
            int nTries;
            // Allow for a maximum of 100 tries before giving up
            // This is not really a good solution but in some cases it avoids the process to be blocked
            for(nTries=0; nTries<100; nTries++) {
              p->setGridValue(_randomNumbers->uniform(xMin, xMax));
              // Some parametrizations may need some updates before proceeding
              _forward->valueChanged(p);
              if(_terminated.value()) {
                return;
              }
              if(_forward->isFussyOk(p)) {
                //if(nTries>0) {
                //  App::log(tr("Fussy parameter accepted after %1 tries\n").arg(nTries) );
                //}
                break;
              }
            }
            if(nTries==100) {
              //App::log(tr("Fussy parameter rejected after %1 tries, trying another model.\n").arg(nTries) );
              return;
            }
            _nav->setValue(p->gridValue());
          } else {
            int newValue=_randomNumbers->uniform(xMin, xMax);
            _nav->setValue(newValue);
            //printf("      New value %5i\n", newValue);
            p->setGridValue(newValue);
            // Some parametrizations may need some updates before proceeding
            _forward->valueChanged(p);
          }
        }
      }
      // Average volume on all random walks
      //_parentVolume/=_nWalks;

      // ModelSet has its own mutex... thread safe.
      newModel->setModelIndex(_repository->allModels()->add(parameterSpace));
      if(newModel->modelIndex()>-1) {
        //printf("  --- ACCEPT new model ---\n" );
        //nav.currentPoint().print();
        // Check that generated model belongs to the right cell
        /*QVector<int> iGenModel=nav.cellAt(nav.currentPoint());
        if( ! iGenModel.contains(nav.currentCell()) ) {
          printf("Model effectively NOT generated in cell %i but:\n", nav.currentCell());
          for(QVector<int>::iterator it=iGenModel.begin(); it!=iGenModel.end(); it++) {
            printf("%i ", *it);
          }
          printf("\n" );
        }*/
        //ASSERT(parameterSpace.isOkDebug());
        bool ok=true;
        newModel->setMisfit(_forward->misfit(ok));
        newModel->setValidMisfit(ok);;
        newModel->setValidParent(true);;
        return;
      } else {
        // For low dimension parameter spaces (<3), the space can be fully investigated
        // We put a threshold at 80% coverage to abort the inversion process.
        double nPossibleModels=parameterSpace.possibleCount();
        _repository->allModels()->lock();
        if(_repository->allModels()->count()>0.8*nPossibleModels) {
           _repository->allModels()->unlock();
          _repository->stop(); // force termination of inversion
          return;
        }
        _repository->allModels()->unlock();
      }
    }
    /*
       After several tries, it was not possible to generate a valid model that is
       not already in the model set. The are good chances that the current parent cell
       is close to an atomic cell.
    */
    return;
  }

} // namespace DinverCore
