/***************************************************************************
**
**  This file is part of QGpCoreStat.
**
**  QGpCoreStat 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.
**
**  QGpCoreStat 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: 2017-06-11
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Likelihood.h"

namespace QGpCoreStat {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Likelihood::Likelihood(const GaussianMixtureParameters *p)
    : AbstractForward(), Densities(p->modeCount())
  {
    TRACE;
    _parameters=p;

    _minimumValue=0.0;
    _maximumValue=1.0;

    _sampleCount=0;
    _samples=nullptr;
  }

  /*!
    Description of constructor still missing
  */
  Likelihood::Likelihood(const Likelihood& o)
    : AbstractForward(), Densities(o)
  {
    TRACE;
    _parameters=o._parameters;
    _minimumValue=o._minimumValue;
    _maximumValue=o._maximumValue;

    _sampleCount=o._sampleCount;
    _samples=new double[_sampleCount];
    memcpy(_samples, o._samples, sizeof(double)*_sampleCount);
  }

  /*!
    Description of destructor still missing
  */
  Likelihood::~Likelihood()
  {
    TRACE;
    delete [] _samples;
  }

  void Likelihood::setSampleCount(int s)
  {
    TRACE;
    if(_sampleCount!=s) {
      _sampleCount=s;
      delete [] _samples;
      _samples=new double[_sampleCount];
    }
  }

  void Likelihood::setMinimumValue(double m)
  {
    TRACE;
    _minimumValue=m;
    _minimumValue=m;
  }
  void Likelihood::setMaximumValue(double m)
  {
    TRACE;
    _maximumValue=m;
    _maximumValue=m;
  }

  AbstractForward * Likelihood::clone() const
  {
    TRACE;
    Likelihood * f=new Likelihood(*this);
    if(f->setInversionParameters()) {
      return f;
    } else {
      return nullptr;
    }
  }

  /*!
    Initialize parameters for optimization
  */
  bool Likelihood::setInversionParameters()
  {
    TRACE;
    RealSpace& ps=parameterSpace();
    ps.clearParameters();
    QString meanName("m%1"), stddevName("s%1");
    Parameter * m1, *m2, *s;

    switch(_parameters->inversionType()) {
    case GaussianMixtureParameters::Full:
      m2=ps.addParameter(meanName.arg(0), "", _minimumValue, _maximumValue, ParameterGrid::Log, 0.01);
      if(!m2) {
        return false;
      }
      s=ps.addParameter(stddevName.arg(0), "", 0.0, _maximumValue, ParameterGrid::Linear, 0.0);
      ps.addCondition(new SimpleCondition(s, SimpleCondition::GreaterThan, 0.025, m2, 0.0));
      ps.addCondition(new SimpleCondition(s, SimpleCondition::LessThan, 0.25, m2, 0.0));
      for(int i=1; i<count(); i++) {
        m1=m2;
        m2=ps.addParameter(meanName.arg(i), "", _minimumValue, _maximumValue, ParameterGrid::Log, 0.01);
        if(!m2) {
          return false;
        }
        ps.addCondition(new SimpleCondition(m2, SimpleCondition::GreaterThan, 1.1, m1, 0.0));
        s=ps.addParameter(stddevName.arg(i), "", 0.0, _maximumValue, ParameterGrid::Linear, 0.0);
        ps.addCondition(new SimpleCondition(s, SimpleCondition::GreaterThan, 0.025, m2, 0.0));
        ps.addCondition(new SimpleCondition(s, SimpleCondition::LessThan, 0.25, m2, 0.0));
      }
      setWeightParameters();
      break;
    case GaussianMixtureParameters::MeanOnly:
      m2=ps.addParameter(meanName.arg(0), "", _minimumValue, _maximumValue, ParameterGrid::Log, 0.01);
      if(!m2) {
        return false;
      }
      for(int i=1; i<count(); i++) {
        m1=m2;
        m2=ps.addParameter(meanName.arg(i), "", _minimumValue, _maximumValue, ParameterGrid::Log, 0.01);
        if(!m2) {
          return false;
        }
        ps.addCondition(new SimpleCondition(m2, SimpleCondition::GreaterThan, 1.1, m1, 0.0));
      }
      setWeightParameters();
      break;
    }
    return true;
  }

  void Likelihood::setWeightParameters()
  {
    RealSpace& ps=parameterSpace();
    QString weightName("w%1");
    Parameter * w;
    SumCondition * weightCond=new SumCondition(SumCondition::LessThan, 1.0);
    for(int i=1; i<count(); i++) {
      w=ps.addParameter(weightName.arg(i), "", 0.0, 1.0, ParameterGrid::Linear, 0.0);
      weightCond->addParameter(w);
    }
    ps.addCondition(weightCond);
  }

  void Likelihood::parametersToDensities()
  {
    TRACE;
    switch(_parameters->inversionType()) {
    case GaussianMixtureParameters::Full:
      for(int i=0; i<count(); i++) {
        setDensity(i, parameterSpace().parameter(2*i)->realValue(),
                   parameterSpace().parameter(2*i+1)->realValue());
      }
      parametersToWeights(2*count());
      break;
    case GaussianMixtureParameters::MeanOnly:
      for(int i=0; i<count(); i++) {
        double m=parameterSpace().parameter(i)->realValue();
        setDensity(i, m, _parameters->absoluteError(m));
      }
      parametersToWeights(count());
      break;
    }
  }

  void Likelihood::parametersToWeights(int firstWeightIndex)
  {
    TRACE;
    double w;
    double w0=1.0;
    firstWeightIndex--;
    for(int i=1; i<count(); i++) {
      w=parameterSpace().parameter(firstWeightIndex+i)->realValue();
      setWeight(i, w);
      w0-=w;
    }
    ASSERT(w0>=0);
    setWeight(0, w0);
  }

  double Likelihood::misfit(bool&)
  {
    TRACE;
    parametersToDensities();

    double sum=0.0;
    for(int is=0; is<_sampleCount; is++) {
      sum+=::log10(density(_samples[is]));
    }
    //printDebug();
    //printf("%lf\n", -sum);
    return -sum;
  }

  void Likelihood::setValues(const int * values)
  {
    TRACE;
    int n=parameterSpace().variableParameterCount();
    for(int i=0; i<n; i++) {
      parameterSpace().parameter(i)->setGridValue(values[i]);
    }
    parametersToDensities();
  }

} // namespace QGpCoreStat

