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

#include "GeneratorModels.h"
#include "ModelSet.h"
#include "ActiveModels.h"
#include "ScaledModels.h"
#include "BestModels.h"
#include "RealSpace.h"
#include "NewModel.h"

namespace DinverCore {

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

    Full description of class still missing
  */

  int GeneratorModels::_nextSerialNumber=0;

  /*!
    Description of constructor still missing
  */
  GeneratorModels::GeneratorModels(ModelSet * allModels)
    : SharedObject()
  {
    TRACE;
    _activeModels=new ActiveModels(allModels);
    _bestModels=new BestModels(_activeModels);
    _scaledModels=nullptr;
    _serialNumber=_nextSerialNumber++;

    _rejectedCount=0;
    _giveUpCount=0;
    _quarantinedCount=0;
  }

  /*!
    Description of constructor still missing
  */
  GeneratorModels::GeneratorModels(const GeneratorModels& o)
    : SharedObject()
  {
    TRACE;
    _activeModels=new ActiveModels(o._activeModels->allModels());
    _bestModels=new BestModels(_activeModels);
    _scaledModels=nullptr;
    _serialNumber=_nextSerialNumber++;

    _rejectedCount=o._rejectedCount;
    _giveUpCount=o._giveUpCount;
    _quarantinedCount=o._quarantinedCount;
  }

  /*!
    Description of destructor still missing
  */
  GeneratorModels::~GeneratorModels()
  {
    TRACE;
    delete _scaledModels;
    delete _bestModels;
    delete _activeModels;
  }

  /*!
    Required for the first GeneratorModels. Those produced by removeDeadModels()
    have best models yet fully initialized.
  */
  void GeneratorModels::setBestModelCount(int nr)
  {
    TRACE;
    if(nr<1) {
      App::log(tr("nr is less than 1, set to 1\n") );
      nr=1;
    }
    _bestModels->setNr(nr);
  }

  void GeneratorModels::clear()
  {
    TRACE;
    _bestModels->clear();
    _activeModels->clear();
    delete _scaledModels;
    _scaledModels=nullptr;

    _giveUpCount=0;
    _rejectedCount=0;
    _quarantinedCount=0;
  }

  /*!
    Wathelet(2008): Voronoi geometry is not invariant against axis scaling
    and it has a strong effect on exploration properties.

    Scales are calculated in the integer space based on the size of the
    best models region.
  */
  void GeneratorModels::setScaledModels(const RealSpace& parameterSpace)
  {
    TRACE;
    ASSERT(!_scaledModels);
    int ndVar=parameterSpace.variableParameterCount();
    double * s =new double[ndVar];
    if(_bestModels->count()< 2) { // Use parameter space limits instead of
                                  // the best models
      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;
      }
    } else {
      int * minModel=new int[ndVar];
      int * maxModel=new int[ndVar];

      const int * model=_bestModels->model(0);
      for(int ip=0; ip<ndVar; ip++) {
        minModel[ip]=model[ip];
        maxModel[ip]=model[ip];
      }

      for(int im=_bestModels->count()-1; im>=0; im--) {
        model=_bestModels->model(im);
        for(int ip=0; ip<ndVar; ip++) {
          if(model[ip]<minModel[ip]) minModel[ip]=model[ip];
          else if(model[ip]>maxModel[ip]) maxModel[ip]=model[ip];
        }
      }

      // Calculate the scaling factors
      double tmp;
      //App::log(tr("Size of best region:\n");
      for(int ip=0; ip<ndVar; ip++) {
        tmp=maxModel[ip]-minModel[ip];
        if(tmp < 1.0) tmp=1.0;
        s[ip]=1.0/tmp;
        //App::log("  " << tmp;
      }
      //App::log(endl;
      delete [] minModel;
      delete [] maxModel;
    }
    _scaledModels=new ScaledModels(_activeModels, s);
  }

  /*!
    Create a new GeneratorModels with all dead models removed.
  */
  GeneratorModels * GeneratorModels::removeDeadModels() const
  {
    TRACE;
    GeneratorModels * newGeneratorModels=new GeneratorModels(*this);
    ActiveModels * newActiveModels=newGeneratorModels->_activeModels;
    BestModels * newBestModels=newGeneratorModels->_bestModels;

    /*
    // Gain with this method is not measurable
    // The overhead here is porbably higher than the speed gain in Navigator
    int * bestModelIndex=_bestModels->allModelMap();
    int activeCount=_activeModels->count();

    // Sort model to keep models with the best hit count in front
    // Potential speed up in VoronoiNavigator
    QVector<ActiveModel *> sortedModels(activeCount);
    for(int i=0; i<activeCount; i++) {
      sortedModels[i]=&_activeModels->at(i);
    }
    std::sort(sortedModels.begin(), sortedModels.end(), ActiveModel::compareHitCount);

    for(int i=0; i<activeCount; i++) {
      const ActiveModel& m=*sortedModels[i];
      newActiveModels->add(m);
      if(bestModelIndex[m.modelIndex()]>-1 && !m.isQuarantined()) { // A best model and not a quarantined best cell
        newBestModels->add(newActiveModels->count()-1);
      }
    }*/

    int * bestModelIndex=_bestModels->activeModelMap();
    int activeCount=_activeModels->count();
    for(int i=0; i<activeCount; i++) {
      const ActiveModel& m=_activeModels->at(i);
      if(bestModelIndex[i]==-1 || m.isQuarantined()) { // Not a best model or a quarantined best cell
        if(!m.isDying()) {
          newActiveModels->add(m).healthCheck();
        }
      } else {
        newActiveModels->add(m);
        newBestModels->add(newActiveModels->count()-1);
      }
    }

    newBestModels->setModels(newActiveModels);
    delete [] bestModelIndex;
    return newGeneratorModels;
  }

  void GeneratorModels::commitStatistics(NewModel * m, double giveUp)
  {
    TRACE;
    if(m->parentIndex()) {  // Neighborhood
      // Fully commit statistics only to models based on this GeneratorModels
      if(m->parentIndex()->serialNumber()==serialNumber()) {
        _activeModels->addNavigatorHits(m->navigatorHits());
        _activeModels->setNavigatorHits(m->navigatorHits());
        ActiveModel& am=_activeModels->at(m->parentIndex()->activeIndex());
        if(m->isValidParent()) {
          if(m->isValidMisfit()) {
            am.addAccepted();
          } else {
            _rejectedCount++;
            am.addRejected();
            if(am.giveUp(giveUp)) {
              _giveUpCount++;
              am.setQuarantined();
              _quarantinedCount++;
              App::log(1, tr("Give up cell: quarantine\n"));
            }
          }
        } else {
          // This is an atomic cell, keep it but remove its parent from best cells and prevent its re-insertion
          am.setQuarantined();
          _quarantinedCount++;
          App::log(1, tr("Atomic cell detected: quarantine\n"));
        }
      } else { // Count only rejected models, we do not have access to parent cell anymore
               // Atomic cells must not be confused with rejected ones 
        if(!m->isValidMisfit() && m->isValidParent()) {
          _rejectedCount++;
        }
      }
    } else {                 // Monte-Carlo
      if(!m->isValidMisfit()) {
        _rejectedCount++;
      }
    }
  }

  /*!
    Adds model \a m to active and best models.
  */
  void GeneratorModels::add(int modelIndex)
  {
    TRACE;
    _activeModels->add(modelIndex);
    _bestModels->add(_activeModels->count()-1);
  }

} // namespace DinverCore
