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


#include "Histogram2D.h"
#include "Point1D.h"
#include "GaussDistribution.h"

namespace QGpCoreMath {

  /*!
    Read and parse next available not blank and not starting with '#',
    and containing \a pattern.
  */
  bool Histogram2D::Sample::read(const QString& line, int xColumn, int yColumn, int validColumn)
  {
    TRACE;
    bool ok=true;
    LineParser lp(line);
    setX(lp.toDouble(xColumn, ok));
    setY(lp.toDouble(yColumn, ok));
    if(validColumn>=0) {
      _valid=(lp.toInt(validColumn, ok)==1);
    } else {
      _valid=true;
    }
    _line=line;
    if(ok) {
      return true;
    } else {
      // Double \n to avoid overwrite by progress value
      App::log(tr("Error parsing line '%1'\n\n").arg(line) );
      return false;
    }
  }

  void Histogram2D::Sample::writeData(QTextStream& s) const
  {
    TRACE;
    s << toString(20) << " " << (_valid ? "1" : "0") << "\n";
  }

  void Histogram2D::Sample::writeLine(QTextStream& s) const
  {
    TRACE;
    if(_valid) {
      s << _line << "\n";
    }
  }

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  Histogram2D::Histogram2D(int nx, int ny)
      : IrregularGrid2D(nx, ny)
  {
    TRACE;
    _xSampling=LinearScale;
    _ySampling=LinearScale;
    init(0.0);
  }

  Histogram2D::Histogram2D(const Histogram2D& o)
      : IrregularGrid2D(o)
  {
    TRACE;
    _xSampling=o._xSampling;
    _ySampling=o._ySampling;
    _xTitle=o._xTitle;
    _yTitle=o._yTitle;
    _samples=o._samples;
  }

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

  bool Histogram2D::setXSampling(const SamplingParameters& s)
  {
    TRACE;
    _xSampling=s.options();
    if(s.isValid()) {
      for(int i=0; i<nx(); i++) {
        setX(i, s.value(i));
      }
      return true;
    } else {
      App::log(tr("Bad min/max for x sampling\n") );
      return false;
    }
  }


  bool Histogram2D::setXSampling(const QVector<double>& x)
  {
    TRACE;
    if(x.count()>1 && x.first()<x.last()) {
      for(int i=0; i<nx(); i++) {
        setX(i, x.at(i));
      }
      return true;
    } else {
      App::log(tr("Bad x sampling\n") );
      return false;
    }
  }

  bool Histogram2D::setYSampling(const SamplingParameters& s)
  {
    TRACE;
    _ySampling=s.options();
    if(std::isfinite(s.minimum()) &&
       std::isfinite(s.maximum()) &&
       s.minimum()<s.maximum()) {
      Curve<Point1D> c;
      c.line(s.minimum(), s.maximum());
      c.resample(ny(), s.minimum(), s.maximum(), _ySampling);
      QVector<double> values=c.xVector();
      for(int i=0; i<ny(); i++) {
        setY(i, values[i]);
      }
      return true;
    } else {
      App::log(tr("Bad min/max for y sampling\n") );
      return false;
    }
  }

  void Histogram2D::setSamples(const QVector<Sample>& s)
  {
    TRACE;
    _samples=s;
    countSamples();
  }

  void Histogram2D::addSample(const Point2D &s)
  {
    TRACE;
    int ix=indexOfX(s.x());
    int iy=indexOfY(s.y());
    if(ix>-1 && ix<nx() && iy>-1 && iy<ny()) {
      (*valuePointer(ix, iy))++;
    }
    _samples.append(Sample(s));
  }

  void Histogram2D::countSamples(IrregularGrid2D * grid) const
  {
    TRACE;
    int stat=0;
    grid->init(0.0); // Reset all hit count to 0
    int ix, iy;
    if(nx()==1) {
      for(int i=_samples.count()-1; i>=0; i--) {
        const Sample& s=_samples.at(i);
        if(s.isValid()) {
          iy=indexOfY(s.y());
          if(iy>-1 && iy<ny()) {
            //ASSERT(left(ix)<=s.x() && s.x()<=right(ix));
            //ASSERT(bottom(iy)<=s.y() && s.y()<=top(iy));
            (*grid->valuePointer(0, iy))++;
          }
        }
      }
    } else {
      for(int i=_samples.count()-1; i>=0; i--) {
        const Sample& s=_samples.at(i);
        if(s.isValid()) {
          ix=indexOfX(s.x());
          iy=indexOfY(s.y());
          if(ix>-1 && ix<nx() && iy>-1 && iy<ny()) {
            //ASSERT(left(ix)<=s.x() && s.x()<=right(ix));
            //ASSERT(bottom(iy)<=s.y() && s.y()<=top(iy));
            (*grid->valuePointer(ix, iy))++;
          }
        } else {
          stat++;
        }
      }
    }
    App::log(1, tr("    %1/%2 samples are invalid\n").arg(stat).arg(_samples.count()));
  }

  void Histogram2D::setValid(const Rect& limits, bool v)
  {
    TRACE;
    QVector<Sample>::iterator it;
    for(it=_samples.begin();it!=_samples.end();++it) {
      Sample& s=*it;
      if(limits.includes(s)) {
        s.setValid(v);
      }
    }
  }

  void Histogram2D::setStatistics(RealStatisticalPoint &p,
                                  double average, double variance) const
  {
    TRACE;
    if(_ySampling & LogScale) {
      p.setMean(pow(10.0, average));
      p.setStddev(pow(10.0, sqrt(variance)));
    } else if(_ySampling  & InversedScale) {
      p.setMean(1.0/average);
      p.setStddev(1.0/sqrt(variance));
    } else {
      p.setMean(average);
      p.setStddev(sqrt(variance));
    }
  }

  /*!
  */
  Curve<RealStatisticalPoint> Histogram2D::meanCurve() const
  {
    TRACE;
    IrregularGrid2D grid=*this;
    if(_ySampling & LogScale) grid.log10(YAxis);
    else if(_ySampling & InversedScale) grid.inverse(YAxis);
    countSamples(&grid);

    Curve<RealStatisticalPoint> c;
    RealStatisticalPoint p;

    for(int i=0; i<grid.nx(); i++ ) {
      double normFactor=grid.normalizeArea(YAxis, i);
      p.setX(grid.x(i));
      if(normFactor>0.0) {
        double average=grid.mean(YAxis, i);
        setStatistics(p, average, grid.variance(YAxis, i));
        p.setWeight(grid.sum(YAxis, i)*normFactor);
        p.setValid(true);
      } else {
        p.setMean(0.0);
        p.setStddev(0.0);
        p.setValid(false);
        p.setWeight(0.0);
      }
      c.append(p);
    }
    c.sort();
    return c;

  }

  /*!
  */
  Curve<RealStatisticalPoint> Histogram2D::medianCurve() const
  {
    TRACE;
    IrregularGrid2D grid=*this;
    countSamples(&grid);

    Curve<RealStatisticalPoint> c;
    RealStatisticalPoint p;

    // Median computed on the real world values
    for(int i=0; i<grid.nx(); i++) {
      double normFactor=grid.normalizeArea(YAxis, i);
      p.setX(grid.x(i));
      if(normFactor>0.0) {
        p.setMean(grid.median(YAxis, i));
        p.setWeight(grid.sum(YAxis, i)*normFactor);
        p.setValid(true);
      } else {
        p.setMean(0.0);
        p.setStddev(0.0);
        p.setWeight(0.0);
        p.setValid(false);
      }
      c.append(p);
    }

    if(_ySampling & LogScale) {
      grid.log10(YAxis);
      c.yLog10();
    } else if(_ySampling & InversedScale) {
      grid.inverse(YAxis);
      c.yInverse();
    }

    for(int i=grid.nx()-1; i>=0; i--) {
      RealStatisticalPoint& p=c[i];
      if(p.isValid()) {
        setStatistics(p, p.mean(), grid.variance(YAxis, i, p.mean()));
      }
    }
    c.sort();
    return c;
  }

  /*!
  */
  Curve<RealStatisticalPoint> Histogram2D::modeCurve() const
  {
    TRACE;
    IrregularGrid2D grid=*this;
    if(_ySampling & LogScale) grid.log10(YAxis);
    else if(_ySampling & InversedScale) grid.inverse(YAxis);
    countSamples(&grid);

    Curve<RealStatisticalPoint> c;
    RealStatisticalPoint p;

    for(int i=0; i<grid.nx(); i++) {
      double normFactor=grid.normalizeArea(YAxis, i);
      p.setX(grid.x(i));
      if(normFactor>0.0) {
        double average=grid.mode(YAxis, i);
        setStatistics(p, average, grid.variance(YAxis, i, average));
        p.setWeight(grid.sum(YAxis, i)*normFactor);
        p.setValid(true);
      } else {
        p.setMean(0.0);
        p.setStddev(0.0);
        p.setValid(false);
        p.setWeight(0.0);
      }
      c.append(p);
    }
    c.sort();
    return c;
  }

  void Histogram2D::filterThreshold(double t)
  {
    TRACE;
    int ix, iy;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        ix=indexOfX(s.x());
        iy=indexOfY(s.y());
        if(ix>-1 && ix<nx() && iy>-1 && iy<ny()) {
          if(value(ix, iy)<=t) {
            s.setValid(false);
            stat++;
          }
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveOutsideAbsoluteRange(const Curve<RealStatisticalPoint>& curve, double dy)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          if(s.y()<p.mean()-dy || s.y()>p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        } else {
          s.setValid(false);
          stat++;
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveOutsideRelativeRange(const Curve<RealStatisticalPoint>& curve, double factor)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          double dy=p.mean()*factor;
          if(s.y()<p.mean()-dy || s.y()>p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        } else {
          s.setValid(false);
          stat++;
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveOutsideSigmaRange(const Curve<RealStatisticalPoint>& curve, double factor)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          double dy=p.stddev()*factor;
          if(s.y()<p.mean()-dy || s.y()>p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        } else {
          s.setValid(false);
          stat++;
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveInsideAbsoluteRange(const Curve<RealStatisticalPoint>& curve, double dy)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          if(s.y()>p.mean()-dy && s.y()<p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveInsideRelativeRange(const Curve<RealStatisticalPoint>& curve, double factor)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          double dy=p.mean()*factor;
          if(s.y()>p.mean()-dy && s.y()<p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterCurveInsideSigmaRange(const Curve<RealStatisticalPoint>& curve, double factor)
  {
    TRACE;
    int stat=0;
    for(int i=_samples.count()-1; i>=0; i--) {
      Sample& s=_samples[i];
      if(s.isValid()) {
        if(curve.isInsideRange(s.x())) {
          const RealStatisticalPoint& p=curve.at(s.x());
          double dy=p.stddev()*factor;
          if(s.y()>p.mean()-dy && s.y()<p.mean()+dy) {
            s.setValid(false);
            stat++;
          }
        }
      }
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterAboveCurve(const Curve<RealStatisticalPoint>& curve)
  {
    TRACE;
    int stat=0;
    switch(_yScaleType) {
    case Scale::Inversed:
    case Scale::InversedLog: {
        Curve<RealStatisticalPoint> c=curve; // improve interpolation
        c.yInverse();
        for(int i=_samples.count()-1; i>=0; i--) {
          Sample& s=_samples[i];
          if(s.isValid()) {
            if(curve.isInsideRange(s.x())) {
              const RealStatisticalPoint& p=c.at(s.x());
              if(s.y()<1.0/p.mean()) {
                s.setValid(false);
                stat++;
              }
            }
          }
        }
      }
      break;
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      for(int i=_samples.count()-1; i>=0; i--) {
        Sample& s=_samples[i];
        if(s.isValid()) {
          if(curve.isInsideRange(s.x())) {
            const RealStatisticalPoint& p=curve.at(s.x());
            if(s.y()>p.mean()) {
              s.setValid(false);
              stat++;
            }
          }
        }
      }
      break;
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterBelowCurve(const Curve<RealStatisticalPoint>& curve)
  {
    TRACE;
    int stat=0;
    switch(_yScaleType) {
    case Scale::Inversed:
    case Scale::InversedLog: {
        Curve<RealStatisticalPoint> c=curve; // improve interpolation
        c.yInverse();
        for(int i=_samples.count()-1; i>=0; i--) {
          Sample& s=_samples[i];
          if(s.isValid()) {
            if(curve.isInsideRange(s.x())) {
              const RealStatisticalPoint& p=c.at(s.x());
              if(s.y()>1.0/p.mean()) {
                s.setValid(false);
                stat++;
              }
            }
          }
        }
      }
      break;
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      for(int i=_samples.count()-1; i>=0; i--) {
        Sample& s=_samples[i];
        if(s.isValid()) {
          if(curve.isInsideRange(s.x())) {
            const RealStatisticalPoint& p=curve.at(s.x());
            if(s.y()<p.mean()) {
              s.setValid(false);
              stat++;
            }
          }
        }
      }
      break;
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterOutsideCurves(const Curve<RealStatisticalPoint>& curve1,
                                        const Curve<RealStatisticalPoint>& curve2)
  {
    TRACE;
    int stat=0;
    switch(_yScaleType) {
    case Scale::Inversed:
    case Scale::InversedLog: {
        Curve<RealStatisticalPoint> c1=curve1; // improve interpolation
        c1.yInverse();
        Curve<RealStatisticalPoint> c2=curve2;
        c2.yInverse();
        for(int i=_samples.count()-1; i>=0; i--) {
          Sample& s=_samples[i];
          if(s.isValid()) {
            if(c1.isInsideRange(s.x()) && c2.isInsideRange(s.x())) {
              const RealStatisticalPoint& p1=c1.at(s.x());
              const RealStatisticalPoint& p2=c2.at(s.x());
              if(p1.y()<p2.y()) {
                if(s.y()<1.0/p2.y() || 1.0/p1.y()<s.y()) {
                  s.setValid(false);
                  stat++;
                }
              } else {
                if(s.y()<1.0/p1.y() || 1.0/p2.y()<s.y()) {
                  s.setValid(false);
                  stat++;
                }
              }
            }
          }
        }
      }
      break;
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      for(int i=_samples.count()-1; i>=0; i--) {
        Sample& s=_samples[i];
        if(s.isValid()) {
          if(curve1.isInsideRange(s.x()) && curve2.isInsideRange(s.x())) {
            const RealStatisticalPoint& p1=curve1.at(s.x());
            const RealStatisticalPoint& p2=curve2.at(s.x());
            if(p1.y()<p2.y()) {
              if(s.y()<p1.y() || p2.y()<s.y()) {
                s.setValid(false);
                stat++;
              }
            } else {
              if(s.y()<p2.y() || p1.y()<s.y()) {
                s.setValid(false);
                stat++;
              }
            }
          }
        }
      }
      break;
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  void Histogram2D::filterInsideCurves(const Curve<RealStatisticalPoint>& curve1,
                                       const Curve<RealStatisticalPoint>& curve2)
  {
    TRACE;
    int stat=0;
    switch(_yScaleType) {
    case Scale::Inversed:
    case Scale::InversedLog: {
        Curve<RealStatisticalPoint> c1=curve1; // improve interpolation
        c1.yInverse();
        Curve<RealStatisticalPoint> c2=curve2;
        c2.yInverse();
        for(int i=_samples.count()-1; i>=0; i--) {
          Sample& s=_samples[i];
          if(s.isValid()) {
            if(c1.isInsideRange(s.x()) && c2.isInsideRange(s.x())) {
              const RealStatisticalPoint& p1=c1.at(s.x());
              const RealStatisticalPoint& p2=c2.at(s.x());
              if(p1.y()<p2.y()) {
                if(1.0/p2.y()<s.y() && s.y()<1.0/p1.y()) {
                  s.setValid(false);
                  stat++;
                }
              } else {
                if(1.0/p1.y()<s.y() && s.y()<1.0/p2.y()) {
                  s.setValid(false);
                  stat++;
                }
              }
            }
          }
        }
      }
      break;
    case Scale::Linear:
    case Scale::Log:
    case Scale::AbsoluteTime:
    case Scale::RelativeTime:
      for(int i=_samples.count()-1; i>=0; i--) {
        Sample& s=_samples[i];
        if(s.isValid()) {
          if(curve1.isInsideRange(s.x()) && curve2.isInsideRange(s.x())) {
            const RealStatisticalPoint& p1=curve1.at(s.x());
            const RealStatisticalPoint& p2=curve2.at(s.x());
            if(p1.y()<p2.y()) {
              if(p1.y()<s.y() && s.y()<p2.y()) {
                s.setValid(false);
                stat++;
              }
            } else {
              if(p2.y()<s.y() && s.y()<p1.y()) {
                s.setValid(false);
                stat++;
              }
            }
          }
        }
      }
      break;
    }
    countSamples();
    App::log(tr("    %1 samples set as invalid\n").arg(stat) );
  }

  /*!

  */
  void Histogram2D::filterMisfit(const Curve<RealStatisticalPoint>& curve, double maxMisfit)
  {
    TRACE;
    IrregularGrid2D grid=*this;
    if(_ySampling & LogScale) {
      grid.log10(YAxis);
    } else if(_ySampling & InversedScale) {
      grid.inverse(YAxis);
    }

    int nx=curve.count();
    for(int ix=0; ix<nx; ix++) {
      RealStatisticalPoint sc=curve[ix];
      if(sc.isValid()) {
        GaussDistribution gd(sc.mean(), sc.stddev());
        misfit(sc.x(), grid, gd, maxMisfit);
      }
    }
    countSamples();
  }

  bool Histogram2D::save(const QString &fileName, const QString& headerLine, bool maxFormat) const
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream s(&f);
      if(!headerLine.isEmpty()) {
        if(maxFormat) {
          s << "# MAX FORMAT RELEASE 1.1\n";
          CoreApplication::signature(s);
          s << "#\n"
               "# BEGIN DATA\n"
               "#\n";
        }
        s << headerLine;
      }
      write(s);
      return true;
    } else {
      return false;
    }
  }

  bool Histogram2D::load(const QString &fileName)
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::ReadOnly)) {
      QTextStream s(&f);
      read(s);
      return true;
    } else {
      return false;
    }
  }

  void Histogram2D::write(QTextStream& s) const
  {
    TRACE;
    QVector<Sample>::const_iterator it;
    if(!_samples.isEmpty()) {
      if(_samples.first().line().isEmpty()) {
        for(it=_samples.begin(); it!=_samples.end(); ++it) {
          it->writeData(s);
        }
      } else {
        for(it=_samples.begin(); it!=_samples.end(); ++it) {
          it->writeLine(s);
        }
      }
    }
  }

  void Histogram2D::read(QTextStream& s)
  {
    TRACE;
    Sample sample;
    QString line;
    while(true) {
      if(File::readLine(s, line) && sample.read(line)) {
        _samples.append(sample);
      } else {
        break;
      }
    }
    countSamples();
  }

  /*!
    Compute misfit between theoretical normal distribution based on \a curve and the histogram
    If misfit is greater than \a maxMisfit, the sample of \a curve is set invalid.
    Cumulative function is compared and not the density function.
  */
  void Histogram2D::validateCurve(Curve<RealStatisticalPoint>& curve, double maxMisfit) const
  {
    TRACE;
    IrregularGrid2D grid=*this;
    if(_ySampling & LogScale) {
      grid.log10(YAxis);
    } else if(_ySampling & InversedScale) {
      grid.inverse(YAxis);
    }

    int nx=curve.count();
    for(int ix=0; ix<nx; ix++) {
      RealStatisticalPoint& sc=curve[ix];
      if(sc.isValid()) {
        GaussDistribution gd(sc.mean(), sc.stddev());
        if(misfit(sc.x(), grid, gd)>maxMisfit) {
          sc.setValid(false);
        }
      }
    }
  }

  /*!
    Compute misfit between theoretical normal distribution and a cross-section at \a x.
    Cumulative function is compared and not the density function.

    Return -1 is histogram is empty.
  */
  double Histogram2D::misfit(double x, const GaussDistribution& gd) const
  {
    TRACE;
    IrregularGrid2D grid=*this;
    if(_ySampling & LogScale) {
      grid.log10(YAxis);
    } else if(_ySampling & InversedScale) {
      grid.inverse(YAxis);
    }
    return misfit(x, grid, gd);
  }
  /*!
    Compute misfit between theoretical normal distribution and a cross-section at \a x.
    Cumulative function is compared and not the density function.

    Return -1 is histogram is empty.
  */
  double Histogram2D::misfit(double x, IrregularGrid2D& grid, const GaussDistribution& gd) const
  {
    TRACE;
    int ix=grid.indexOfX(x);
    if(ix<0) {
      ix++;
    } else if(ix==grid.nx()) {
      ix--;
    }
    return misfit(ix, grid, gd);
  }

  /*!
    Compute misfit between theoretical normal distribution and a cross-section at \a x.
    Cumulative function is compared and not the density function.

    Return -1 if histogram is empty.
  */
  double Histogram2D::misfit(int  ix, IrregularGrid2D& grid, const GaussDistribution& gd) const
  {
    TRACE;
    double factor=grid.normalizeArea(YAxis, ix);
    if(factor==0.0) {
      return -1.0;
    }
    Curve<Point2D> c=grid.crossSection(YAxis, ix);
    if(_ySampling & LogScale) {
      c.xExp10();
    } else if(_ySampling & InversedScale) {
      c.xInverse();
    }

    int ny=c.count();
    double m=0.0, sum=0.0;
    for(int iy=0; iy<ny; iy++) {
      const Point2D& p=c.at(iy);
      sum+=p.y()*grid.height(iy);
      double ygd;
      if(_ySampling & LogScale) {
        ygd=gd.part(::log10(p.x()));
      } else if(_ySampling & InversedScale) {
        ygd=gd.part(1.0/p.x());
      } else {
        ygd=gd.part(p.x());
      }
      double dy=sum-ygd;
      m+=dy*dy;
    }
    m/=ny;
    return ::sqrt(m);
  }

  /*!
    Compute misfit between theoretical normal distribution and a cross-section at \a x.
    Cumulative function is compared and not the density function.

    Samples are set invalid where local misfit is less than \a maxMisfit.
    Filter out samples that fit \a gd at \a x.
  */
  void Histogram2D::misfit(double x, IrregularGrid2D& grid, const GaussDistribution& gd, double maxMisfit)
  {
    TRACE;
    int ix=grid.indexOfX(x);
    if(ix<0) {
      ix++;
    } else if(ix==grid.nx()) {
      ix--;
    }
    double factor=grid.normalizeArea(YAxis, ix);
    if(factor==0.0) {
      return;
    }
    Curve<Point2D> c=grid.crossSection(YAxis, ix);
    if(_ySampling & LogScale) {
      c.xExp10();
    } else if(_ySampling & InversedScale) {
      c.xInverse();
    }

    int ny=c.count();
    double sum=0.0;
    for(int iy=0; iy<ny; iy++) {
      const Point2D& p=c.at(iy);
      sum+=p.y()*grid.height(iy);
      double ygd;
      if(_ySampling & LogScale) {
        ygd=gd.part(::log10(p.x()));
      } else if(_ySampling & InversedScale) {
        ygd=gd.part(1.0/p.x());
      } else {
        ygd=gd.part(p.x());
      }
      double dy=sum-ygd;
      if(::fabs(dy)<maxMisfit) {
        QVector<Sample>::iterator it;
        for(it=_samples.begin(); it!=_samples.end(); ++it) {
          Sample& s=*it;
          if(s.isValid() && grid.belongs(s, ix, iy)) {
            qDebug() << s.x() << s.y() << p.x() << sum << ygd << ::fabs(dy) << std::abs(dy);
            s.setValid(false);
          }
        }
      }
    }
  }

  bool saveCurve(const Curve<RealStatisticalPoint>& c, const QString &fileName)
  {
    TRACE;
    QFile f(fileName);
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream s(&f);
      s.setRealNumberPrecision(20);
      int n=c.count();
      for(int i=0; i<n; i++) {
        s << c.at(i).toString(20) << "\n";
      }
      return true;
    } else {
      return false;
    }
  }

  /*!
    Automatically pick all maxima with a Gaussian fit.
  */
  Curve<RealStatisticalPoint> Histogram2D::pickAll(double maxMisfit) const
  {
    int i=1;
    Histogram2D h=*this;
    Curve<RealStatisticalPoint> all;
    Curve<RealStatisticalPoint> c;
    //c=h.modeFitCurve();
    saveCurve(c, QString("/tmp/curve_%1.txt").arg(i, 4, 10, QChar('0')));
    h.save(QString("/tmp/histo_%1.txt").arg(i, 4, 10, QChar('0')));
    while(!c.isEmpty()) {
      h.filterMisfit(c, maxMisfit);
      all.append(c);
      //c=h.modeFitCurve();
      i++;
      saveCurve(c, QString("/tmp/curve_%1.txt").arg(i, 4, 10, QChar('0')));
      h.save(QString("/tmp/histo_%1.txt").arg(i, 4, 10, QChar('0')));
    }
    return all;
  }

} // namespace QGpCoreMath
