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

#include "Densities.h"

namespace QGpCoreStat {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Densities::Densities()
  {
    TRACE;
    _count=0;
    _densities=nullptr;
    _weights=nullptr;
  }

  /*!
    Description of constructor still missing
  */
  Densities::Densities(int count)
  {
    TRACE;
    if(count<1) {
      count=1;
    }
    _count=count;
    _densities=new GaussDistribution[_count];
    _weights=new double[_count];
  }

  /*!
    Description of constructor still missing
  */
  Densities::Densities(const Densities& o)
  {
    TRACE;
    _densities=nullptr;
    _weights=nullptr;
    *this=o;
  }

  /*!
    Description of destructor still missing
  */
  Densities::~Densities()
  {
    TRACE;
    delete [] _densities;
    delete [] _weights;
  }

  /*!
  */
  void Densities::operator=(const Densities& o)
  {
    TRACE;
    delete [] _densities;
    delete [] _weights;
    _count=o._count;
    _densities=new GaussDistribution[_count];
    _weights=new double[_count];
    for(int i=0; i<_count; i++) {
      _densities[i]=o._densities[i];
      _weights[i]=o._weights[i];
    }
  }

  void Densities::printDebug() const
  {
    TRACE;
    for(int i=0; i<_count; i++) {
      const GaussDistribution& gd=_densities[i];
      printf("%lf %lf %lf ", _weights[i], gd.mean(), gd.stddev());
    }
  }

  QString Densities::humanInfo(const QString prefix) const
  {
    TRACE;
    QString s;
    QString t("%1%2 *[ %3 , %4 ] ~ %5\n");
    for(int i=0; i<_count; i++) {
      const GaussDistribution& gd=_densities[i];
      s+=t.arg(prefix).arg(_weights[i]).arg(gd.mean()).arg(gd.stddev()).arg(qualityFactor(i));
    }
    return s;
  }


  double Densities::density(double x) const
  {
    TRACE;
    double sum=0.0;
    for(int i=0; i<_count; i++) {
      sum+=_densities[i].rho(x)*_weights[i];
    }
    return sum;
  }

  double Densities::cumulativeDensity(double x) const
  {
    TRACE;
    double sum=0.0;
    for(int i=0; i<_count; i++) {
      sum+=_densities[i].part(x)*_weights[i];
    }
    return sum;
  }

  double Densities::qualityFactor(int index) const
  {
    const GaussDistribution& gd=_densities[index];
    return _weights[index]*(gd.mean()/gd.stddev());
  }

  void Densities::beginFilter(double *& newWeights, GaussDistribution *& newDensities, int newDensityCount)
  {
    TRACE;
    newWeights=new double[newDensityCount];
    newDensities=new GaussDistribution[newDensityCount];
  }

  void Densities::endFilter(double * newWeights, GaussDistribution * newDensities, int newDensityCount)
  {
    TRACE;
    delete [] _weights;
    delete [] _densities;
    _weights=newWeights;
    _densities=newDensities;
    _count=newDensityCount;
    // Adjust weights so that sum==1
    double sum=0.0;
    for(int i=0; i<_count; i++) {
      sum+=_weights[i];
    }
    sum=1.0/sum;
    for(int i=0; i<_count; i++) {
      _weights[i]*=sum;
    }
  }

  /*!
    Sort densities by decreasing weight
  */
  void Densities::sort()
  {
    TRACE;
    QVector<SortIndex> sortHelper(_count);
    for(int i=0; i<_count; i++) {
      sortHelper[i]=SortIndex(this, i);
    }
    std::sort(sortHelper.begin(), sortHelper.end(), lessThan);

    double * newWeights;
    GaussDistribution * newDensities;
    beginFilter(newWeights, newDensities, _count);
    for(int i=0; i<_count; i++) {
      int oldI=sortHelper[i]._i;
      newWeights[i]=_weights[oldI];
      newDensities[i]=_densities[oldI];
    }
    endFilter(newWeights, newDensities, _count);
  }

  bool Densities::lessThan(const SortIndex& i1, const SortIndex& i2)
  {
    return i1._parent->weight(i1._i)>i2._parent->weight(i2._i);
  }

  /*!

  */
  void Densities::filterQualityFactor(double min)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    for(int i=0; i<_count; i++) {
      if(qualityFactor(i)>min) {
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  /*!

  */
  void Densities::filterWeight(double min)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    for(int i=0; i<_count; i++) {
      if(_weights[i]>min) {
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  /*!
    Remove all solutions that do not correspond to a maximum of the density function.
    Densities are kept if the density at mean is larger than density at +/- \a stddevFactor*stddev.
  */
  void Densities::filterMaxima(double stddevFactor)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    for(int i=0; i<_count; i++) {
      const GaussDistribution& gd=_densities[i];
      double d0=density(gd.mean());
      if(App::verbosity()>=3) {
        App::log(3, tr("Sampling at %1, %2, %3 -> %4, %5, %6\n")
                          .arg(gd.mean()-stddevFactor*gd.stddev())
                          .arg(gd.mean())
                          .arg(gd.mean()+stddevFactor*gd.stddev())
                          .arg(density(gd.mean()-stddevFactor*gd.stddev()))
                          .arg(d0)
                          .arg(density(gd.mean()+stddevFactor*gd.stddev())));
      }
      if(d0>density(gd.mean()-stddevFactor*gd.stddev()) &&
         d0>density(gd.mean()+stddevFactor*gd.stddev())) {
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=gd;
        newDensityCount++;
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  /*!
    Mean must be between \a min and \a max.
  */
  void Densities::filterRange(double min, double max)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    for(int i=0; i<_count; i++) {
      double m=_densities[i].mean();
      if(m>min && m<max) {
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  /*!
  */
  void Densities::bestQualityFactors(int maxCount)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    double qf=0.0;
    for(int i=0; i<_count; i++) {
      if(qualityFactor(i)>qf) {
        qf=qualityFactor(i);
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
        if(newDensityCount==maxCount) {
          break;
        }
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  /*!
  */
  void Densities::bestWeights(int maxCount)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count);
    double wt=0.0;
    for(int i=0; i<_count; i++) {
      if(_weights[i]>wt) {
        wt=_weights[i];
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
        if(newDensityCount==maxCount) {
          break;
        }
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  int Densities::indexOf(double weight, const GaussDistribution& density) const
  {
    TRACE
    for(int i=0; i<_count; i++) {
      if(_weights[i]==weight && _densities[i]==density) {
        return i;
      }
    }
    return -1;
  }

  /*!
  */
  void Densities::intersection(const Densities& o)
  {
    TRACE;
    double * newWeights;
    GaussDistribution * newDensities;
    int newDensityCount=0;
    beginFilter(newWeights, newDensities, _count>o._count ? o._count : _count);
    for(int i=0; i<_count; i++) {
      if(o.indexOf(_weights[i], _densities[i])>-1) {
        newWeights[newDensityCount]=_weights[i];
        newDensities[newDensityCount]=_densities[i];
        newDensityCount++;
      }
    }
    endFilter(newWeights, newDensities, newDensityCount);
  }

  void Densities::merge()
  {
    TRACE;
    bool merged=true;
    while(merged) {
      merged=false;
      for(int i=0; i<_count; i++) {
        if(_weights[i]>0.0) {
          for(int j=0; j<_count; j++) {
            if(i!=j && _weights[j]>0.0) {
              if(fabs(_densities[i].mean()-_densities[j].mean())<_densities[i].stddev()+_densities[j].stddev()) {
                RealStatisticalValue v1(_densities[i].mean(), _densities[i].stddev(), _weights[i]);
                RealStatisticalValue v2(_densities[j].mean(), _densities[j].stddev(), _weights[j]);
                v1.average(v2);
                _densities[i].setMean(v1.mean());
                _densities[i].setStddev(v1.stddev());
                _weights[i]=v1.weight();
                _weights[j]=0.0;
                merged=true;
              }
            }
          }
        }
      }
    }
    filterWeight(0.0);
  }

  void Densities::excludeMerge()
  {
    TRACE;
    for(int i=0; i<_count; i++) {
      if(_weights[i]>0.0) {
        for(int j=0; j<_count; j++) {
          if(i!=j && _weights[j]>0.0) {
            if(fabs(_densities[i].mean()-_densities[j].mean())<_densities[i].stddev()+_densities[j].stddev()) {
              _weights[j]=0.0;
              _weights[i]=0.0;
            }
          }
        }
      }
    }
    filterWeight(0.0);
  }

} // namespace QGpCoreStat

