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

#include "ModelSet.h"
#include "RealSpace.h"
#include "ReportReader.h"
#include "Model.h"

namespace DinverCore {

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
ModelSet::ModelSet(int parameterCount, int targetCount, int defaultCapacity)
    : IncreaseStorage(defaultCapacity)
{
  ASSERT(parameterCount>0);
  _parameterCount=parameterCount;
  _targetCount=targetCount;
  _parameters=(int*)allocateVector(sizeof(int)*_parameterCount);
  _misfits=(double*)allocateVector(sizeof(double)*_targetCount);
  //_volumes=(double*)allocateVector(sizeof(double));
  _highestValidIndex=-1;
}

ModelSet::~ModelSet()
{
  free(_parameters);
  free(_misfits);
}

void ModelSet::reallocate()
{
  _modelLock.lockForWrite();
  _parameters=(int*)reallocateVector((uchar *) _parameters, sizeof(int)*_parameterCount);
  _misfits=(double*)reallocateVector((uchar *) _misfits, sizeof(double)*_targetCount);
  //_volumes=(double*)reallocateVector((uchar *) _volumes, sizeof(double));
  _modelLock.unlock();
}


void ModelSet::clear()
{
  free(_parameters);
  free(_misfits);
  QSet<Model>::clear();
  IncreaseStorage::clear();
  _parameters=(int*)allocateVector(sizeof(int)*_parameterCount);
  _misfits=(double*)allocateVector(sizeof(double)*_targetCount);
  //_volumes=(double*)allocateVector(sizeof(double));
  _highestValidIndex=-1;
}

/*!
  Add model \a to the model set. Returns -1 if model already exists.
  Else, it returns the index of the added model. This function is thread safe.
*/
int ModelSet::add(const RealSpace& parameterSpace, const double * misfits)
{
  // Used to test whether the new model already exist in main model set
  ModelSet newModelSet(_parameterCount, 1);
  int * v=newModelSet._parameters;
  for(int ip=0; ip<_parameterCount; ip++) {
    v[ip]=parameterSpace.variableParameter(ip)->gridValue();
  }
  Model newModel(0, &newModelSet);
  _setLock.lock();
  if(!contains(newModel)) {
    int index=IncreaseStorage::size();
    double * misfitPtr=_misfits+_targetCount*index;
    // highestValidIndex is increased only if models have their misfit values
    // If no misfit is provided here, we expect a setMisfit() call once computed.
    if(misfits) {
      memcpy((char *)misfitPtr, misfits, sizeof(double)*_targetCount);
      if(index>_highestValidIndex) {
        _highestValidIndex=index;
      }
    } else {
      for(int i=0;i<_targetCount;i++) {
        misfitPtr[i]=std::numeric_limits<double>::infinity();
      }
    }
    //_volumes[index]=0.0; // It will generate error immediately detectable if not initialized
    //                     // A null volume is considered as a non initialized volume
    int * paramPtr=_parameters+_parameterCount*index;
    memcpy((char *)paramPtr, newModel.data(), sizeof(int)*_parameterCount);
    // Misfits and model values are already written, now the size can be changed.
    // This way is always safe to access all models with index < size() without
    // locking if no capacity increase required.
    IncreaseStorage::add();
    insert(Model(index, this));
    _setLock.unlock();
     return index;
  } else {
    _setLock.unlock();
    return -1;
  }
}

void ModelSet::print(const int * model) const
{
  for(int i=0; i<_parameterCount; i++) {
    printf("%i\t", model[i]);
  }
  printf("\n");
}

void ModelSet::print(int modelIndex) const
{
  const int * paramPtr=model(modelIndex);
  printf("%i : ", modelIndex);
  for(int i=0; i<_parameterCount; i++) {
    printf("%i\t", paramPtr[i]);
  }
  printf(" --> ");
  const double * misfitPtr=misfit(modelIndex);
  for(int i=0; i<_targetCount; i++) {
    printf("%15lg\t", misfitPtr[i]);
  }
  //printf(" --vol-- ");
  //for(int i=0; i<_targetCount; i++) {
  //  printf("%15lg\t", misfitPtr[ i ]/_volumes[modelIndex]);
  //}
  printf("\n");
}

/*!
  Returns the index of the best model. All models are checked.
  This function is valid only for single misfit inversion.
*/
int ModelSet::bestModel() const
{
  TRACE;
  int nm=count();
  double minMisfit=std::numeric_limits<double>::infinity();
  int index=-1;
  for(int im=0; im<nm; im++) {
    if(_misfits[im]<minMisfit) {
      index=im;
      minMisfit=misfit(im)[0];
    }
  }
  return index;
}

/*!
  If \a strict is true (default), a strict match of the current parameterization and the one of the report
  is checked. Else, only the number of parameters is checked.

  \todo kept for compatibility with importance sampling, revise
*/
bool ModelSet::importModels(RealSpace& parameterSpace, QString fileName, bool strict)
{
  TRACE;
  if(ReportReader::isReportFormat(fileName)) {
    ReportReader report(fileName);
    if(!report.open()) {
      App::log(tr("Error opening report %1\n").arg(fileName) );
      return false;
    }
    report.synchronize();
    return importModelsFromReport(parameterSpace, report, strict);
  } else {
    return importModelsFromAscii(parameterSpace, fileName);
  }
}

/*!
  Format is:

  \code
  # This is a comment, one line per model
  p1 p2 .... pn misfit
  p1 p2 .... pn misfit
  ...
  \endcode
*/
bool ModelSet::importModelsFromAscii(RealSpace& parameterSpace, QString fileName)
{
  TRACE;
  ASSERT(parameterSpace.variableParameterCount()==_parameterCount);
  QFile f(fileName);
  if( !f.open(QIODevice::ReadOnly) ) {
    App::log(tr("Error opening file %1\n").arg(fileName) );
    return false;
  }
  QTextStream s(&f);
  QString line;
  StringSection val, content(line);
  const QChar * ptr;
  int i;
  int iLine=0;
  int nParams=parameterSpace.allParameterCount();
  while(!s.atEnd()) {
    line=s.readLine();
    iLine++;
    StringSection content(line);
    if(line[0]!='#') {
      ptr=0;
      for(i=0; i<nParams; i++) {
        val=content.nextField(ptr);
        if(val.isValid()) parameterSpace.parameter(i)->setRealValue(val.toDouble()); else break;
      }
      if(i==nParams) {
        double misfits[_targetCount];
        for(i=0; i<_targetCount; i++) {
          val=content.nextField(ptr);
          if(val.isValid()) misfits[i]=val.toDouble(); else break;
        }
        // All parameter values were correctly retreived and also misfit (last one), then add a new model
        if(i==_targetCount) {
          add(parameterSpace, misfits);
        } else {
          App::log(tr("Erreur parsing file %1 at line %2, misfit[%3]\n").arg(fileName).arg(iLine).arg(i) );
          return false;
        }
      } else {
        App::log(tr("Erreur parsing file %1 at line %2, parameter[%3]\n").arg(fileName).arg(iLine).arg(i) );
        return false;
      }
    }
  }
  return true;
}

/*!
  If \a strict is true, the parameter space checksum must exactly match
*/
bool ModelSet::importModelsFromReport(RealSpace& parameterSpace, ReportReader& report, bool strict)
{
  TRACE;
  /*TRACE_BUG;
  TRACE_BUG_INT(parameterSpace.variableParameterCount());
  TRACE_BUG_INT(_parameterCount);
  ASSERT(parameterSpace.variableParameterCount()==_parameterCount);
  printf("importing models start\n");*/

  uint refChecksum=parameterSpace.checksum();

  int nParams, nTargets;
  uint checksum;
  double misfits[_targetCount];
  int nImportedModels=0;
  int varAllCompat=0;
  int n=report.nModels();
  QDataStream& s=report.stream();
  //printf("importing models %i\n", n);
  for(int im=0;im<n;im++) {
    report.modelBlock(im);
    if(_targetCount>1) {
      s >> nParams >> nTargets >> checksum;
      if(nTargets!=_targetCount) {
        App::log(tr("N targets (%1) must be %2\n").arg(nTargets).arg(_targetCount) );
        continue;
      }
      for(int im=0;im<_targetCount; im++) {
        s >> misfits[im];
      }
    } else { // needed for compatibility (<=20091120 target count always equal 1)
      s >> misfits[0] >> nParams >> checksum;
    }
    if(!strict || checksum==refChecksum) {
      if(nParams==parameterSpace.allParameterCount()) {
        int ip;
        for(ip=0; ip<nParams; ip++) {
          if(!parameterSpace.parameter(ip)->setRealValue(s)) break;
        }
        if(ip==nParams) {
          add(parameterSpace, misfits);
          nImportedModels++;
        } else {
          App::log(tr("Warning cannot import model at index %1 in file %2\n").arg(im).arg(report.fileName()) );
        }
      } else if(nParams==parameterSpace.variableParameterCount()) {
        varAllCompat++;
        int ip;
        for(ip=0; ip<nParams; ip++) {
          if(!parameterSpace.variableParameter(ip)->setRealValue(s) ) break;
        }
        if(ip==nParams) {
          add(parameterSpace, misfits);
          nImportedModels++;
          printf("%i imported models\n", nImportedModels);
        } else {
          App::log(tr("Warning cannot import model at index %1 in file %2\n").arg(im).arg(report.fileName()));
        }
      } else {
        App::log(tr("N parameters (%1) must be %2\n").arg(nParams).arg(_parameterCount));
      }
    } else {
      App::log(tr("Checksum (%1) must be %2\n").arg(checksum).arg(refChecksum));
    }
  }
  App::log(tr("Importing %1 models from report %2\n").arg(nImportedModels).arg(report.fileName()));
  if(varAllCompat>0) {
    App::log(tr("Compatibility with previous reports, only variable parameters "
                 "stored in report file (%1 models).\n").arg(varAllCompat));
  }
  return true;
}

} // namespace DinverCore
