/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2012-12-19
**  Copyright: 2012-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Histogram.h"

namespace QGpCoreMath {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Histogram::Histogram()
    : Curve<Point2D>()
  {
    TRACE;
    _lastClass=0.0;
  }

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

  void Histogram::setSampling(int n, double min, double max, SamplingOptions sampling)
  {
    TRACE;
    _sampling=sampling;
    line(Point2D(min, 0.0), Point2D(max, 0.0));
    resample(n, min, max, _sampling);
    _lastClass=0.0;
  }

  void Histogram::clear()
  {
    TRACE;
    if(count()>1) {
      setSampling(count(), first().x(), last().x(), _sampling);
    } else {
      Curve<Point2D>::clear();
      _lastClass=0.0;
    }
  }

  void Histogram::addValue(double v)
  {
    TRACE;
    int i=indexAfter(v);
    if(i<count()) {
      Point2D& p=(*this)[i];
      p.setY(p.y()+1.0);
    } else {
      _lastClass++;
    }
  }

  /*!
    The two ending classes are not considered: infinite width with eventually
    an infinite area. In fact, it assumes that these classes are null.
  */
  void Histogram::normalize()
  {
    TRACE;
    int n=count();
    Point2D lastp(first().x(), 0.0);
    double area=0.0;
    for(int i=1; i<n; i++) {
      const Point2D& p=at(i);
      Rect r(lastp, p);
      area+=r.area();
      lastp.setX(p.x());
    }
    double factor=1.0/area;
    for(int i=1; i<n; i++) {
      Point2D& p=(*this)[i];
      Rect r(lastp, p);
      p.setY(p.y()*r.area()*factor);
      lastp.setX(p.x());
    }
  }

  ::QVector<Rect> Histogram::boxes() const
  {
    TRACE;
    ::QVector<Rect> rects;
    int n=count();
    Point2D lastp(-std::numeric_limits<double>::infinity(), 0.0);
    for(int i=0; i<n; i++) {
      const Point2D& p=at(i);
      rects.append(Rect(lastp, p));
      lastp.setX(p.x());
    }
    rects.append(Rect(Point2D(lastp.x(), _lastClass), Point2D(std::numeric_limits<double>::infinity(), 0.0)));
    return rects;
  }

  Rect Histogram::limits() const
  {
    TRACE;
    double m=at(maximumY()).y();
    if(_lastClass>m) {
      m=_lastClass;
    }
    return Rect(first().x(), 0.0, last().x(), m);
  }

  /*!
    The variance is computed relative to \a average that can be any of mean, median or mode. If \a average is
    the median, the median deviation is returned.
  */
  double Histogram::variance(double average) const
  {
    TRACE;
    double sumX=0.0, sumX2=0.0;
    int n=count();
    int hitCount=n-1;
    Point2D lastVal(first().x(), 0.0);
    double cellWidth=0.0;
    for(int i=1; i<n; i++) {
      const Point2D& val=at(i);
      Rect r(lastVal, val);
      double p=r.area();
      if(p>0) {
        cellWidth=r.width();
        double v=0.5*(lastVal.x()+val.x());
        double vp=v*p;
        sumX+=vp;
        sumX2+=v*vp;
      } else {
        hitCount--;
      }
      lastVal.setX(val.x());
    }
    if(hitCount>1) {
      return sumX2+(average-2.0*sumX)*average;
    } else if(hitCount==1) {
      // The only one class, assumed to be 4*sigma (95% on a gaussian);
      return cellWidth*cellWidth/16;
    }
    App::log(tr("### ERROR ### : the histogram is certainly not normalized, you must call normalize() "
                "                before calling variance().\n"));
    return 0.0;
  }

  RealStatisticalValue Histogram::mean() const
  {
    TRACE;
    Curve<Point2D> curve=*this;
    if(_sampling & LogScale) curve.xLog10();
    else if(_sampling & InversedScale) curve.xInverse();

    int n=count();
    double sumX=0.0, sumP=0.0;
    Point2D lastVal(first().x(), 0.0);
    for(int i=1; i<n; i++) {
      const Point2D& val=curve.at(i);
      Rect r(lastVal, val);
      double p=r.area();
      double v=0.5*(lastVal.x()+val.x());
      sumP+=p;
      sumX+=v*p;
      lastVal.setX(val.x());
    }
    double average=sumX/sumP;

    RealStatisticalValue v;
    if(_sampling & LogScale) {
      v.setMean(pow(10.0, average));
      v.setStddev(pow(10.0, sqrt(variance(average))));
    } else if(_sampling  & InversedScale) {
      v.setMean(1.0/average);
      v.setStddev(1.0/sqrt(variance(average)));
    } else {
      v.setMean(average);
      v.setStddev(sqrt(variance(average)));
    }
    return v;
  }

  RealStatisticalValue Histogram::median() const
  {
    TRACE;
    Curve<Point2D> curve=*this;
    if(_sampling & LogScale) curve.xLog10();
    else if(_sampling & InversedScale) curve.xInverse();

    int n=count();
    double average=0.0, sumP=0.0;
    Point2D lastVal(first().x(), 0.0);
    for(int i=1; i<n; i++) {
      const Point2D& val=curve.at(i);
      Rect r(lastVal, val);
      double p=r.area();
      sumP+=p;
      if(sumP>0.5) {
        average=lastVal.x()+(1.0-(sumP-0.5)/p)*(val.x()-lastVal.x());
        break;
      }
      lastVal.setX(val.x());
    }

    RealStatisticalValue v;
    if(_sampling & LogScale) {
      v.setMean(pow(10.0, average));
      v.setStddev(pow(10.0, sqrt(variance(average))));
    } else if(_sampling  & InversedScale) {
      v.setMean(1.0/average);
      v.setStddev(1.0/sqrt(variance(average)));
    } else {
      v.setMean(average);
      v.setStddev(sqrt(variance(average)));
    }
    return v;
  }

  RealStatisticalValue Histogram::mode() const
  {
    Curve<Point2D> curve=*this;
    if(_sampling & LogScale) curve.xLog10();
    else if(_sampling & InversedScale) curve.xInverse();

    int n=count();
    double average=0.0, pmax=0.0;
    Point2D lastVal(first().x(), 0.0);
    for(int i=1; i<n; i++) {
      const Point2D& val=curve.at(i);
      Rect r(lastVal, val);
      double p=r.area();
      if(p>pmax) {
        pmax=p;
        average=0.5*(lastVal.x()+val.x());
      }
      lastVal.setX(val.x());
    }

    RealStatisticalValue v;
    if(_sampling & LogScale) {
      v.setMean(pow(10.0, average));
      v.setStddev(pow(10.0, sqrt(variance(average))));
    } else if(_sampling  & InversedScale) {
      v.setMean(1.0/average);
      v.setStddev(1.0/sqrt(variance(average)));
    } else {
      v.setMean(average);
      v.setStddev(sqrt(variance(average)));
    }
    return v;
  }

} // namespace QGpCoreMath
