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

#include <QGpCoreTools.h>

#include "BestModels.h"

namespace DinverCore {

  /*!
    \class BestModels BestModels.h
    \brief A sorted list (by increasing misfit) of indexes of best models

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  BestModels::BestModels(ActiveModels * models)
      : IncreaseStorage(64)
  {
    TRACE;
    _models=models;
    _nrMax=50;
    _nr2=1;
    _indexes=(int *)allocateVector(sizeof(int));
  }

  BestModels::BestModels(const BestModels& o)
      : IncreaseStorage(o)
  {
    TRACE;
    _models=o._models;
    _nrMax=o._nrMax;
    _nr2=o._nr2;
    _indexes=(int *)allocateVector(sizeof(int));
    memcpy(_indexes, o._indexes, size()*sizeof(int));
  }


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

  void BestModels::clear()
  {
    free(_indexes);
    IncreaseStorage::clear();
    _indexes=(int *)allocateVector(sizeof(int));
    _nrMax=50;
    _nr2=1;
  }

  void BestModels::reallocate()
  {
    _indexes=static_cast<int *>(reallocateVector((uchar *)_indexes, sizeof(int)));
  }

  /*!

  */
  void BestModels::setNr(int nr)
  {
    TRACE;
    if(nr!=_nrMax && nr > 0) {
      if(nr > _nrMax) {
        _nrMax=nr;
        update();
      } else {
        // When reducing the number of best models, make sure that the nr first best models
        // do not have the same misfit. If so leave best model count as it is.
        if(size()<=nr || _models->misfit(_indexes[ nr-1 ] )[0] > _models->misfit(_indexes[ 0 ] )[0] ) {
          _nrMax=nr;
          if(size() > _nrMax) {
            downSize(_nrMax);
          }
        }
      }
    }
  }

  /*!
    Returns an index vector of size equal to active model count. Indexes are set to
    -1 if they do not correspond to a best model.
  */
  int * BestModels::activeModelMap() const
  {
    TRACE;
    int activeCount=_models->count();
    int * map=new int[activeCount];
    memset(map, -1, activeCount*sizeof(int));
    for(int i=count()-1; i>=0; i--) {
      map[_indexes[i]]=i;
    }
    return map;
  }

  /*!
    Returns an index vector of size equal to all model count. Indexes are set to
    -1 if they do not correspond to a best model.
  */
  int * BestModels::allModelMap() const
  {
    TRACE;
    int allCount=_models->allModels()->count();
    int * map=new int[allCount];
    memset(map, -1, allCount*sizeof(int));
    for(int i=count()-1; i>=0; i--) {
      map[_models->at(_indexes[i]).modelIndex()]=i;
    }
    return map;
  }

  /*!
    If possible add models to the list of best model until reaching nr==nrMax.
  */
  void BestModels::update()
  {
    TRACE;
    if(size()==_nrMax) return;
    int * map=activeModelMap(); // a quick way to check if model is already in current list
    for(int i=_models->count()-1; i>=0; i--) {
      if(map[i]==-1 && !_models->at(i).isQuarantined()) {
        add(i);
      }
    }
    delete map;
  }
  /*!
    Checks if model with index \a modelIndex can be considered as a best model and if so
    add it to the current list.
  */
  void BestModels::add(int modelIndex)
  {
    int i;
    if(_models->targetCount()==1) { // best models in the classical sense
      double modelMisfit=_models->misfit(modelIndex)[0];
      if(size()<_nrMax) {
        if(size()==0) {
          add();
          _indexes[0]=modelIndex;
        } else {
          i=indexOf(modelMisfit);
          if(exactIndexOf(modelIndex, modelMisfit, i-1)==-1) {
            int n=size();
            add();
            if(i<n) {
              memmove((char *)(_indexes+i+1), (char *)(_indexes+i), sizeof(int)*(n-i));
            }
            _indexes[i]=modelIndex;
          }
        }
      } else if(modelMisfit<misfit(size()-1)[0]) {
        i=indexOf(modelMisfit);
        if(exactIndexOf(modelIndex, modelMisfit, i-1)==-1) {
          memmove(( char * ) (_indexes + i + 1), (char * ) (_indexes + i),
                     sizeof(int) * (size() - i) );
          _indexes[i]=modelIndex;
          // Less than 5% difference between the very best one and the other best
          // Enlarge the best model ensemble. TODO: eventually reduce nr if the condition is not fulfilled
          /*if(misfit(0)[0]>0.95*misfit(size()-1)[0]) {
            add();
            _nrMax=size();
          }*/
        }
      } else if(modelMisfit==misfit(size()-1)[0]) {
        /* Due to clipped misfit (increase the effective nr)
           Do some sort of Monte-Carlo inside the "good" misfit region */
        int n=size();
        add();
        _indexes[n]=modelIndex;
        _nrMax=size();
      }
    } else { // best models contains the Pareto front

    }
  }

  /*!
    This function is called whenever a model listed in best models is generating a great number of bad models,
    exceding giveUp ratio or when a cell becomes atomic. This model must be removed from the best models if
    still present.

    It uses the first misfit to quickly locate the \a modelIndex in the list of best models.
  */
  void BestModels::remove(int modelIndex)
  {
    TRACE;
    // Check if index model is still listed
    double m= _models->misfit(modelIndex)[0];
    int indexBest=exactIndexOf(modelIndex, m, indexOf(m)-1);
    if(indexBest!=-1) {
      // Effectively reduce the size by removing the indexBest item
      // _nr is always greater than 0, and indexBest<_nr
      memmove((char *) (_indexes+indexBest), (char *) (_indexes+indexBest+1), sizeof(int)*(size()-(indexBest+1)));
      downSize(size()-1);
    }
  }

  /*!
    Always return the index of the model having the first misfit just above \a m
    Hence misfit[i-1]<=m<misfit[i].

    This function is always used with exactIindexOf to find models.
  */
  int BestModels::indexOf(double m) const
  {
    if(m<misfit(0)[0]) return 0;
    int lasti=size() - 1;
    if(m>=misfit(lasti)[0]) return size();
    int i=_nr2;
    int step2=i >> 1;
    while(step2 > 0) {
      if(i > lasti) i -= step2;
      else if(m < misfit(i)[0]) {
        if(m >= misfit(i-1)[0]) break;
        i -= step2;
      } else i += step2;
      step2=step2 >> 1;
    }
    return i;
  }

  /*!
    Looks only for first misfit.

    This function is always used with indexOf to find models.
  */
  int BestModels::exactIndexOf(int modelIndex, double m, int approxIndex) const
  {
    if(approxIndex<0) return -1; // Misfit of modelIndex is less than all others
    // approxIndex can be in the middle of similar misfits, first search above
    for(int i=approxIndex; i<size() && misfit(i)[0]==m; i++ ) {
      if(_indexes[i]==modelIndex) return i;
    }
    // Then search below
    for(int i=approxIndex; i>=0 && misfit(i)[0]==m; i-- ) {
      if(_indexes[i]==modelIndex) return i;
    }
    return -1;
  }

  void BestModels::print()
  {
    TRACE;
    printf("#\n#                 Best models\n");
    for(int i=0; i<size();i++) _models->print(_indexes[i]);
  }

} // namespace DinverCore
