/***************************************************************************
**
**  This file is part of gphistogram.
**
**  gphistogram 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.
**
**  gphistogram 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: 2008-12-18
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <SciFigs.h>

#include "HistogramWidget.h"
#include "GridFilter.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
HistogramWidget::HistogramWidget(QWidget * parent)
    : QWidget(parent)
{
  TRACE;
  setupUi(this);
  setAttribute(Qt::WA_DeleteOnClose, false);
  setFocusPolicy(Qt::ClickFocus);
  _hist=nullptr;
  _normalize=true;
  _lastX=0;
  _gaussianMixtureCurve=nullptr;
  _gaussianMixtureOutput.setHistogramDensities(&_gaussianMixtureDensities);
  connect(&_gaussianMixtureDensities, SIGNAL(histogramFinished(int)),
          this, SLOT(addGaussianMixture(int)));

  AxisWindow * w;
  QAction * a;

  w=new AxisWindow(this);
  w->setObjectName("Histogram");
  sheet->sheet()->addObject(w);
  w->setPrintLeft(0.5);
  w->setPrintTop(0.5);
  w->setPrintWidth(16.0);
  w->setPrintHeight(8.0);
  w->updateInternalGeometry();
  _histogramLayer=new IrregularGrid2DPlot(w);
  _histogramLayer->setSmooth(false); // Currently a shift is observed for smoothed data
                                     // TODO: check the smooth display algorithm
  ColorMap map;
  map.generateColorScale(20, ColorPalette::McNamesClip, true);
  map.setWhiteTransparent(true);
  _histogramLayer->setColorMap(map);
  LineLayer * curveLayer=new LineLayer(w);
  curveLayer->setReferenceLine(new RealStatisticalLine);
  curves->setProxy(new RealStatisticalProxy);
  curves->setPlotProxy(new RealStatisticalPlotProxy);
  curves->initLayer(curveLayer);
  curves->setCurrentLayer(curveLayer);
  //a=curves->addCurveAction(tr("&Adjust"), tr("Adjust current curve to the closest maximum"), true);
  //connect(a, SIGNAL(triggered()), this, SLOT(adjustCurve()) );
  sheet->sheet()->showObject(w);

  new QShortcut(Qt::Key_Up, this, SLOT(nextX()));
  new QShortcut(Qt::Key_Up+Qt::SHIFT, this, SLOT(nextXReject()));
  new QShortcut(Qt::Key_Down, this, SLOT(previousX()));
  new QShortcut(Qt::Key_Down+Qt::SHIFT, this, SLOT(previousXReject()));
  new QShortcut(Qt::Key_Left, this, SLOT(decreaseLowLimit()));
  new QShortcut(Qt::Key_Left+Qt::SHIFT, this, SLOT(decreaseHighLimit()));
  new QShortcut(Qt::Key_Right, this, SLOT(increaseLowLimit()));
  new QShortcut(Qt::Key_Right+Qt::SHIFT, this, SLOT(increaseHighLimit()));

  // Init the curve menu
  QMenu * m=new QMenu(this);
  m->setTitle(tr( "Curve"));
  m->setTearOffEnabled (true);
  curveBut->setMenu(m);

  a=new QAction(tr("&Mean"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SLOT(addMeanCurve()) );
  a=new QAction(tr("Medi&an"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SLOT(addMedianCurve()) );
  a=new QAction(tr("Mo&de"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SLOT(addModeCurve()) );
  a=new QAction(tr("&Gaussian mixture point"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SLOT(addGaussianMixturePoint()) );
  a=new QAction(tr("&Gaussian mixture curve"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SLOT(addGaussianMixtureCurve()) );

  w=new AxisWindow(this);
  sheet->sheet()->addObject(w);
  w->setPrintLeft(0.5);
  w->setPrintTop(8.5);
  w->setPrintWidth(8.0);
  w->setPrintHeight(6.0);
  w->updateInternalGeometry();
  _densityBandLayer=new ParallelBands(w);
  _densityBandLayer->setObjectName("DensitySelection");
  _densityBandLayer->addTrackingAction(tr("Pick bands"), 0, tr("Define the X band to reject."));
  _densityBandLayer->setOpacity(0.6);
  _densityBandLayer->addBand(0, 0, Brush(QColor( 205, 242, 255)));
  connect(_densityBandLayer, SIGNAL(bandPicked(double, double)), this, SLOT(setBand( double, double)));
  _densityCurveLayer=new LineLayer(w);
  _densityCurveLayer->setObjectName( "Density" );
  _densityCurveLayer->setReferenceLine(new PlotLine2D);
  _densityCurveLayer->setReferencePen(Pen( Qt::blue));
  _densityCurveLayer->addLine(Pen(Qt::blue), Symbol());
  _densityCurveLayer->addLine(Pen(Qt::red), Symbol());
  _densityCurveLayer->addLine(Pen(Qt::green), Symbol());
  w->yAxis()->setNumberType('e');
  w->yAxis()->setNumberPrecision(1);
  w->yAxis()->setTitle("Probability density");
  sheet->sheet()->showObject(w);

  w=new AxisWindow(this);
  sheet->sheet()->addObject(w);
  w->setPrintLeft(8.5);
  w->setPrintTop(8.5);
  w->setPrintWidth(8.0);
  w->setPrintHeight(6.0);
  w->updateInternalGeometry();
  _cumulativeBandLayer=new ParallelBands(w);
  _cumulativeBandLayer->setObjectName("CumulativeSelection");
  _cumulativeBandLayer->addTrackingAction(tr("Pick bands"), 0, tr("Define the X band to reject."));
  _cumulativeBandLayer->setOpacity(0.6);
  _cumulativeBandLayer->addBand(0, 0, Brush(QColor( 205, 242, 255)));
  connect(_cumulativeBandLayer, SIGNAL(bandPicked(double, double)), this, SLOT(setBand( double, double)));
  _cumulativeCurveLayer=new LineLayer(w);
  _cumulativeCurveLayer->setObjectName("Cumulative");
  _cumulativeCurveLayer->setReferenceLine(new PlotLine2D);
  _cumulativeCurveLayer->setReferencePen(Pen( Qt::blue));
  _cumulativeCurveLayer->addLine(Pen(Qt::blue), Symbol());
  _cumulativeCurveLayer->addLine(Pen(Qt::red), Symbol());
  _cumulativeCurveLayer->addLine(Pen(Qt::green), Symbol());
  w->yAxis()->setRange(0.0, 1.0);
  w->yAxis()->setNumberType('f');
  w->yAxis()->setNumberPrecision(1);
  w->yAxis()->setTitle("Cumulative probability");
  sheet->sheet()->showObject(w);

  Settings::getWidget(this, "Histogram");
}

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

void HistogramWidget::closeEvent(QCloseEvent * e)
{
  TRACE;
  Settings::setWidget(this, "Histogram");
  e->accept();
}

void HistogramWidget::setHistogramComments(QString cmt)
{
  TRACE;
  _histogramLayer->setComments(cmt);
}

void HistogramWidget::setHistogram(Histogram2D * hist)
{
  TRACE;
  _hist=hist;
  if(_normalize) {
    _hist->normalizeArea(YAxis);
  }
  _histogramLayer->setGrid(*_hist);

  if(!_hist->xTitle().isNull()) {
    _histogramLayer->graph()->xAxis()->setTitle(_hist->xTitle());
    _histogramLayer->graph()->xAxis()->setTitleInversedScale(_hist->xTitleInversedScale());
  }
  if(!_hist->yTitle().isNull()) {
    _histogramLayer->graph()->yAxis()->setTitle(_hist->yTitle());
    _histogramLayer->graph()->yAxis()->setTitleInversedScale(_hist->yTitleInversedScale());
    _densityCurveLayer->graph()->xAxis()->setTitle(_hist->yTitle());
    _densityCurveLayer->graph()->xAxis()->setTitleInversedScale(_hist->yTitleInversedScale());
    _cumulativeCurveLayer->graph()->xAxis()->setTitle(_hist->yTitle());
    _cumulativeCurveLayer->graph()->xAxis()->setTitleInversedScale(_hist->yTitleInversedScale());
  }

  _histogramLayer->graph()->xAxis()->setScaleType(hist->xScaleType());
  switch(hist->yScaleType()) {
  case Scale::Linear:
  case Scale::Inversed:
  case Scale::AbsoluteTime:
  case Scale::RelativeTime:
    break;
  case Scale::Log:
  case Scale::InversedLog:
    curves->currentLayer()->setErrorBar(LineLayer::VerticalLogBar);
    break;
  }
  _histogramLayer->graph()->yAxis()->setScaleType(hist->yScaleType());
  _densityCurveLayer->graph()->xAxis()->setScaleType(hist->yScaleType());
  _cumulativeCurveLayer->graph()->xAxis()->setScaleType(hist->yScaleType());

  _gaussianMixtureDensities.setHistogram(_hist);

  xScroll->setRange(0, hist->nx()-1);
  on_xScroll_valueChanged();
  _histogramLayer->graph()->deepUpdate();
}

void HistogramWidget::setLimits()
{
  TRACE;

  GraphContent * gc=_histogramLayer->graphContent();
  AxisWindow * w=gc->graph();

  Rect r=_histogramLayer->boundingRect();
  r.enlarge(0.01, gc->scaleX().sampling(), gc->scaleY().sampling());
  w->xAxis()->setRange(r.x1(), r.x2());
  w->yAxis()->setRange(r.y1(), r.y2());

  ColorMap pal=_histogramLayer->colorMap();
  double m=_histogramLayer->grid().maximumValue();
  if(_normalize) {
    pal.setVLog(0.0001*m, m);
  } else {
    pal.generateColorScale(qRound(m+1.0), ColorPalette::McNamesClip, true);
    pal.setVLinear(0.5, m-0.5);
  }
  _histogramLayer->setColorMap(pal);
}

void HistogramWidget::on_xScroll_valueChanged()
{
  TRACE;
  int ix=xScroll->value();
  if(_xUnit.isEmpty()) {
    xEdit->setText(QString::number(_hist->x(ix)));
  } else {
    xEdit->setText(QString("%1 %2").arg(_hist->x(ix)).arg(_xUnit));
  }

  PlotLine2D * gridDensityLine=static_cast<PlotLine2D *>(_densityCurveLayer->line(0));
  PlotLine2D * gaussDensityLine=static_cast<PlotLine2D *>(_densityCurveLayer->line(1));
  PlotLine2D * gaussMixDensityLine=static_cast<PlotLine2D *>(_densityCurveLayer->line(2));
  PlotLine2D * gridCumulativeLine=static_cast<PlotLine2D *>(_cumulativeCurveLayer->line(0));
  PlotLine2D * gaussCumulativeLine=static_cast<PlotLine2D *>(_cumulativeCurveLayer->line(1));
  PlotLine2D * gaussMixCumulativeLine=static_cast<PlotLine2D *>(_cumulativeCurveLayer->line(2));

  _densityCurveLayer->lockDelayPainting();
  _cumulativeCurveLayer->lockDelayPainting();

  IrregularGrid2D grid=*_hist;
  int n=grid.y().count();
  // If option -no-normalize is used make sure grid is normalized
  grid.normalizeArea(YAxis);
  // Keep a copy of the real world y axis cell widths (necessary to convert to cumulative function)
  QVector<double> binWidth(n);
  for(int i=0; i<n; i++) {
    binWidth[i]=grid.height(i);
  }
  // Median computed on the real world values
  double med=grid.median(YAxis, ix);
  if(_hist->ySampling() & LogScale) {
    grid.log10(YAxis);
    med=log10(med);
  } else if(_hist->ySampling() & InversedScale) {
    grid.inverse(YAxis);
    med=1.0/med;
  }

  Curve<Point2D> curve=grid.crossSection(YAxis, ix);
  if(_hist->ySampling() & LogScale) {
    curve.xExp10();
  } else if(_hist->ySampling() & InversedScale) {
    curve.xInverse();
  }

  gridDensityLine->setCurve(curve);
  gaussDensityLine->setCurve(curve);
  gaussMixDensityLine->setCurve(curve);
  gridCumulativeLine->setCurve(curve);
  gaussCumulativeLine->setCurve(curve);
  gaussMixCumulativeLine->setCurve(curve);
  ASSERT(n==gridDensityLine->curve().count());

  double mean=grid.mean(YAxis, ix);
  double stddev=sqrt(grid.variance(YAxis, ix));
  double meddev=sqrt(grid.variance(YAxis, ix, med));
  double mod=grid.mode(YAxis, ix);
  double moddev=sqrt(grid.variance(YAxis, ix, mod));
  double modFitdev=grid.varianceFit(YAxis, ix, mod);

  double sum=0;
  for(int i=0; i<n; i++) {
    Point2D& p=gridCumulativeLine->curve()[i];
    sum+=p.y()*binWidth.at(i);
    p.setY(sum);
  }

  GaussDistribution gd(med, meddev);
  for(int i=0; i<n; i++) {
    Point2D& p=gaussDensityLine->curve()[i];
    if(_hist->ySampling() & LogScale) {
      // 1/p, source: https://en.wikipedia.org/wiki/Log-normal_distribution
      p.setY(gd.rho(log10(p.x()))/p.x());
    } else if(_hist->ySampling() & InversedScale) {
      p.setY(gd.rho(1.0/p.x()));
    } else {
      p.setY(gd.rho(p.x()));
    }
  }
  for(int i=0; i<n; i++) {
    Point2D& p=gaussCumulativeLine->curve()[i];
    if(_hist->ySampling() & LogScale) {
      p.setY(gd.part(log10(p.x())));
    } else if(_hist->ySampling() & InversedScale) {
      p.setY(gd.part(1.0/p.x()));
    } else {
      p.setY(gd.part(p.x()));
    }
  }

  Densities d=_gaussianMixtureDensities.densities(ix);
  for(int i=0; i<n; i++) {
    Point2D& p=gaussMixDensityLine->curve()[i];
    if(_hist->ySampling() & LogScale) {
      p.setY(d.density(log10(p.x())));
    } else if(_hist->ySampling() & InversedScale) {
      p.setY(d.density(1.0/p.x()));
    } else {
      p.setY(d.density(p.x()));
    }
  }
  for(int i=0; i<n; i++) {
    Point2D& p=gaussMixCumulativeLine->curve()[i];
    if(_hist->ySampling() & LogScale) {
      p.setY(d.cumulativeDensity(log10(p.x())));
    } else if(_hist->ySampling() & InversedScale) {
      p.setY(d.cumulativeDensity(1.0/p.x()));
    } else {
      p.setY(d.cumulativeDensity(p.x()));
    }
  }


  const ParallelBand& b=_densityBandLayer->band(0);
  saveBand(_lastX, b.minimum(), b.maximum());
  Point2D p=restoreBand(ix);
  if(std::isnormal(p.x()) && std::isnormal(p.y())) {
    setBand(p.x(), p.y());
  } else {
    if(_hist->ySampling() & LogScale) {
      setBand(log10(med-meddev), log10(med+meddev));
    } else if(_hist->ySampling() & InversedScale) {
      setBand(1.0/(med-meddev), 1.0/(med+meddev));
    } else {
      setBand(med-meddev, med+meddev);
    }
  }
  _lastX=ix;

  _densityCurveLayer->unlock();
  _cumulativeCurveLayer->unlock();

  if(_hist->ySampling() & LogScale) {
    mean=pow(10.0, mean);
    stddev=pow(10.0, stddev);
    med=pow(10.0, med);
    meddev=pow(10.0, meddev);
    mod=pow(10.0, mod);
    moddev=pow(10.0, moddev);
    modFitdev=pow(10.0, modFitdev);
  } else if(_hist->ySampling() & InversedScale) {
    mean=1.0/mean;
    stddev=1.0/stddev;
    med=1.0/med;
    meddev=1.0/meddev;
    mod=1.0/mod;
    moddev=1.0/moddev;
    modFitdev=1.0/modFitdev;
  }

  statValuesLabel->setText(tr("Mean %2%1 - Stddev %3%1\n"
                              "Median %4%1 - Meddev %5%1\n"
                              "Mode %6%1 - Moddev %7%1\n"
                              "Cumulative median misfit %10")
                            .arg(_yUnit.isEmpty() ? _yUnit : " " + _yUnit)
                            .arg(mean, 0, 'g', 3).arg(stddev, 0, 'g', 3)
                            .arg(med, 0, 'g', 3).arg(meddev, 0, 'g', 3)
                            .arg(mod, 0, 'g', 3).arg(moddev, 0, 'g', 3)
                            .arg(_hist->misfit(_hist->x(ix), gd)));

  Rect r;
  GraphContent * gc;
  AxisWindow * w;

  gc=_densityBandLayer->graphContent();
  w=gc->graph();
  r=gc->boundingRect();
  w->xAxis()->setRange(r.x1(), r.x2());
  w->yAxis()->setRange(r.y1(), r.y2());
  w->deepUpdate();

  gc=_cumulativeBandLayer->graphContent();
  w=gc->graph();
  r=gc->boundingRect();
  w->xAxis()->setRange(r.x1(), r.x2());
  w->deepUpdate();
}

void HistogramWidget::saveBand(int ix, double min, double max)
{
  TRACE;
  double x=_hist->x(ix);
  CurveProperties * lowProp=curves->find("Low limit");
  if(lowProp) {
    lowProp->beginCurveChange();
  } else if(std::isnormal(min)) {
    lowProp=curves->addLine(curves->currentLayer()->addLine());
    lowProp->beginCurveChange();
    lowProp->proxy()->setName("Low limit");
  }
  CurveProperties * highProp=curves->find("High limit");
  if(highProp) {
    highProp->beginCurveChange();
  } else if(std::isnormal(max)) {
    highProp=curves->addLine(curves->currentLayer()->addLine());
    highProp->beginCurveChange();
    highProp->proxy()->setName("High limit");
  }

  if(lowProp && highProp) {
    Curve<RealStatisticalPoint>& lowCurve=static_cast<RealStatisticalProxy *>(lowProp->proxy())->curve();
    Curve<RealStatisticalPoint>& highCurve=static_cast<RealStatisticalProxy *>(highProp->proxy())->curve();

    // TODO RELEASE 3.4: remove these work around
    if(lowCurve.isEmpty()) {
      lowCurve.append(RealStatisticalPoint(x, min));
      lowCurve.sort();
    } else {
      lowCurve.insert(RealStatisticalPoint(x, min));
    }
    if(highCurve.isEmpty()) {
      highCurve.append(RealStatisticalPoint(x, min));
      highCurve.sort();
    } else {
      highCurve.insert(RealStatisticalPoint(x, max));
    }

    lowProp->endCurveChange();
    highProp->endCurveChange();
  }
}

Point2D HistogramWidget::restoreBand(int ix)
{
  TRACE;
  double x=_hist->x(ix);
  CurveProperties * lowProp=curves->find("Low limit");
  CurveProperties * highProp=curves->find("High limit");
  if(!lowProp || ! highProp) {
    return Point2D(NAN, NAN);
  }
  Curve<RealStatisticalPoint>& lowCurve=static_cast<RealStatisticalProxy *>(lowProp->proxy())->curve();
  Curve<RealStatisticalPoint>& highCurve=static_cast<RealStatisticalProxy *>(highProp->proxy())->curve();
  return Point2D(lowCurve.at(x).y(), highCurve.at(x).y());
}

void HistogramWidget::setBand(double min, double max)
{
  TRACE;
  ParallelBand& b1=_densityBandLayer->band(0);
  b1.setMinimum(min);
  b1.setMaximum(max);
  ParallelBand& b2=_cumulativeBandLayer->band(0);
  b2.setMinimum(min);
  b2.setMaximum(max);
  _densityBandLayer->graphContent()->deepUpdate();
  _cumulativeBandLayer->graphContent()->deepUpdate();
}

void HistogramWidget::on_saveBut_clicked()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Save filtered samples"),
                                            tr("Max file (*.max)"));
  if(!fileName.isEmpty()) {
    _hist->save(fileName, _headerLine, true);
  }
}

/*!
  Reject all samples outside the band. If SHIFT key is pressed, the current x is incremented.
  If the band for next x does not exist, a new one is created from the current one plus a translation
  calculated on the median difference before the rejection at the current frequency.
*/
void HistogramWidget::on_rejectBut_clicked()
{
  TRACE;
  int ix=xScroll->value();
  const ParallelBand& band=_densityBandLayer->band(0);
  Rect r(_hist->left(ix), -std::numeric_limits<double>::infinity(), _hist->right(ix), band.minimum());
  _hist->setValid(r, false);
  r.setY1(band.maximum());
  r.setY2(std::numeric_limits<double>::infinity());
  _hist->setValid(r, false);
  _hist->countSamples();
  saveBand(ix, band.minimum(), band.maximum());
  setHistogram(_hist);
}

void HistogramWidget::on_undoBut_clicked()
{
  TRACE;
  int ix=xScroll->value();
  Rect r(_hist->left(ix), -std::numeric_limits<double>::infinity(), _hist->right(ix), std::numeric_limits<double>::infinity());
  _hist->setValid(r, true);
  _hist->countSamples();
  setHistogram(_hist);
}

void HistogramWidget::on_rejectAllBut_clicked()
{
  TRACE;
  Rect r;
  for(int i=_hist->nx()-1; i>=0; i-- ) {
    Point2D band=restoreBand(i);
    r.setLimits(_hist->left(i), -std::numeric_limits<double>::infinity(), _hist->right(i), band.x());
    _hist->setValid(r, false);
    r.setY1(band.y());
    r.setY2(std::numeric_limits<double>::infinity());
    _hist->setValid(r, false);
  }
  _hist->countSamples();
  setHistogram(_hist);
}

void HistogramWidget::on_undoAllBut_clicked()
{
  TRACE;
  Rect r( -std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity(), std::numeric_limits<double>::infinity());
  _hist->setValid(r, true);
  _hist->countSamples();
  setHistogram(_hist);
}

void HistogramWidget::shortcutEvents(Shortcuts s)
{
  TRACE;
  GraphContent * gc=_densityCurveLayer->graphContent();
  const ParallelBand& b=_densityBandLayer->band(0);
  int ix=xScroll->value();

  double dx;
  SAFE_UNINITIALIZED(dx,0);
  switch (s) {
  case NextX:
  case NextXReject:
  case PreviousX:
  case PreviousXReject:
    break;
  case DecreaseLowLimit:
  case IncreaseLowLimit:
    dx=fabs(b.minimum()-gc->options().xs2r(gc->options().xr2s(b.minimum())+5)); // estimation of 5 pixel width
    break;
  case DecreaseHighLimit:
  case IncreaseHighLimit:
    dx=fabs(b.maximum()-gc->options().xs2r(gc->options().xr2s(b.maximum())+5)); // estimation of 5 pixel width
    break;
  }

  switch (s) {
  case NextX:
    xScroll->setValue(ix+1);
    break;
  case NextXReject:
    on_rejectBut_clicked();
    xScroll->setValue(ix+1);
    break;
  case PreviousX:
    xScroll->setValue(ix-1);
    break;
  case PreviousXReject:
    on_rejectBut_clicked();
    xScroll->setValue(ix-1);
    break;
  case DecreaseLowLimit:
    setBand(b.minimum()-dx, b.maximum());
    break;
  case DecreaseHighLimit:
    setBand(b.minimum(), b.maximum()-dx);
    break;
  case IncreaseLowLimit:
    setBand(b.minimum()+dx, b.maximum());
    break;
  case IncreaseHighLimit:
    setBand(b.minimum(), b.maximum()+dx);
    break;
  }
}

void HistogramWidget::adjustCurve()
{
  TRACE;
  AbstractLine * line=curves->currentLayer()->line(curves->currentLine());
  _hist->followMaximumX<Curve<RealStatisticalPoint>, RealStatisticalPoint>(static_cast<RealStatisticalLine *>(line)->curve(), 0, std::numeric_limits<double>::infinity());
}

void HistogramWidget::addCurve(const Curve<RealStatisticalPoint>& c)
{
  TRACE;
  RealStatisticalLine * l=static_cast<RealStatisticalLine *>(curves->currentLayer()->addLine());
  l->setCurve(c);
  curves->addLine(l);
}

void HistogramWidget::addMeanCurve()
{
  TRACE;
  Curve<RealStatisticalPoint> c=_hist->meanCurve();
  addCurve(c);
}

void HistogramWidget::addMedianCurve()
{
  TRACE;
  Curve<RealStatisticalPoint> c=_hist->medianCurve();
  addCurve(c);
}

void HistogramWidget::addModeCurve()
{
  TRACE;
  Curve<RealStatisticalPoint> c=_hist->modeCurve();
  addCurve(c);
}

void HistogramWidget::setGaussianMixtureParameters(const GaussianMixtureParameters& p)
{
  TRACE;
  _gaussianMixtureDensities.setParameters(p);
}

void HistogramWidget::addGaussianMixturePoint()
{
  TRACE;
  addCurve(Curve<RealStatisticalPoint>());
  _gaussianMixtureCurve=curves->curves().last();
  _gaussianMixtureCurve->setEditable(false);
  _gaussianMixtureCurve->line()->setPen(Pen(Qt::NoPen));
  _gaussianMixtureCurve->line()->setSymbol(Symbol(Symbol::Circle, 1.2, Qt::SolidLine, Qt::SolidPattern));
  _gaussianMixtureDensities.start(xScroll->value());
}

void HistogramWidget::addGaussianMixtureCurve()
{
  TRACE;
  addCurve(Curve<RealStatisticalPoint>());
  _gaussianMixtureCurve=curves->curves().last();
  _gaussianMixtureCurve->setEditable(false);
  _gaussianMixtureCurve->line()->setPen(Pen(Qt::NoPen));
  _gaussianMixtureCurve->line()->setSymbol(Symbol(Symbol::Circle, 1.2, Qt::SolidLine, Qt::SolidPattern));
  _gaussianMixtureDensities.start();
}

void HistogramWidget::addGaussianMixture(int index)
{
  TRACE;
  if(!_gaussianMixtureCurve) {
    return;
  }
  Densities d=_gaussianMixtureOutput.filter(index);
  double x=_hist->x(index);
  RealStatisticalPoint p;
  _gaussianMixtureCurve->beginCurveChange();
  Curve<RealStatisticalPoint>& c=static_cast<RealStatisticalLine *>(_gaussianMixtureCurve->line())->curve();
  for(int i=0; i<d.count(); i++) {
    p.setX(x);
    p.setMean(d.density(i).mean());
    p.setStddev(d.density(i).stddev());
    p.setWeight(d.weight(i));
    c.append(p);
  }
  _gaussianMixtureCurve->endCurveChange();
  _histogramLayer->graph()->deepUpdate();
  on_xScroll_valueChanged();
}

bool HistogramWidget::loadCurves(const QStringList& fileNames, const QString& formatFileName)
{
  TRACE;
  ColumnTextParser parser;
  parser.setStandardTypes(curves->proxy()->columnFileTypes());
  if(formatFileName.isEmpty()) {
    parser.setTypes(curves->proxy()->defaultColumnFileTypes());
  } else {
    XMLHeader hdr(&parser);
    if(hdr.xml_restoreFile(formatFileName)!=XMLClass::NoError) {
      return false;
    }
  }
  ColumnTextParser * pparser=&parser;
  QStringList expandedFileNames=File::expand(fileNames);
  for(QStringList::const_iterator it=expandedFileNames.begin(); it!=expandedFileNames.end(); it++) {
    if(!curves->loadMultiColumns(*it, false, pparser)) {
      return false;
    }
  }
  return true;
}

void HistogramWidget::exportPlot(const ExportOptions& o)
{
  TRACE;
  AxisWindow * w=_histogramLayer->graph();
  if(!o.legendFile().isEmpty()) {
    Legend l;
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load legend"));
    xmler.setFileName(o.legendFile());
    XMLSciFigs s;
    if(xmler.exec(s.restoreFile(o.legendFile(), &l, XMLSciFigs::Data))) {
      curves->setLegend(l);
    }
  }
  if(!o.colorMapFile().isEmpty()) {
    ColorMap pal;
    XMLErrorReport xmler(XMLErrorReport::Read | XMLErrorReport::NoMessageBox);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(o.colorMapFile());
    XMLSciFigs s;
    if(xmler.exec(s.restoreFile(o.colorMapFile(), &pal, XMLSciFigs::Data))) {
      _histogramLayer->setColorMap(pal);
    }
  }
  if(!o.appendLayerFiles().isEmpty()) {
    for(QStringList::const_iterator it=o.appendLayerFiles().begin(); it!=o.appendLayerFiles().end(); it++) {
      w->graphContent()->appendLayers(*it);
    }
  }
  if(!o.prependLayerFiles().isEmpty()) {
    for(QStringList::const_iterator it=o.prependLayerFiles().begin(); it!=o.prependLayerFiles().end(); it++) {
      w->graphContent()->prependLayers(*it);
    }
  }
  foreach(QString m, o.makeupObjectFiles()) {
    w->restoreMakeUp(m);
  }
  if(!o.preserveScales()) {
    setLimits();
  }
  if(!o.exportFile().isEmpty()) {
    w->exportFile(o.exportFile(), o.exportFormat(), o.dpi());
  }
}

/*!

*/
void HistogramWidget::on_gridFilter_clicked()
{
  TRACE;
  GridFilter * d=new GridFilter(this);
  d->setCurveList(curves->curves());
  Settings::getWidget(d);
  d->enableWidgets();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    if(d->absoluteThresholdFilter->isChecked()) {
      _hist->filterThreshold(d->absoluteThresholdValue->value());
    } else if(d->relativeThresholdFilter->isChecked()) {
      _hist->normalizeAmplitude(YAxis);  // filter calls countSamples(), hence histogram value are automatically reset
      _hist->filterThreshold(d->relativeThresholdValue->value()*0.01);
    } else if(d->curveRangeFilter->isChecked()) {
      int index=d->curve1Name->currentIndex();
      Curve<RealStatisticalPoint>& curve=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index))->curve();
      curve.sort();
      switch(d->curveRangeType->currentIndex()) {
      default:
        _hist->filterCurveInsideAbsoluteRange(curve, d->curveRangeFactor->value());
        break;
      case 1:
        _hist->filterCurveInsideRelativeRange(curve, 0.01*d->curveRangeFactor->value());
        break;
      case 2:
        _hist->filterCurveInsideSigmaRange(curve, d->curveRangeFactor->value());
        break;
      case 3:
        _hist->filterCurveOutsideAbsoluteRange(curve, d->curveRangeFactor->value());
        break;
      case 4:
        _hist->filterCurveOutsideRelativeRange(curve, 0.01*d->curveRangeFactor->value());
        break;
      case 5:
        _hist->filterCurveOutsideSigmaRange(curve, d->curveRangeFactor->value());
        break;
      }
    } else if(d->aboveCurveFilter->isChecked()) {
      int index=d->curve1Name->currentIndex();
      Curve<RealStatisticalPoint>& curve=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index))->curve();
      curve.sort();
      _hist->filterAboveCurve(curve);
    } else if(d->belowCurveFilter->isChecked()) {
      int index=d->curve1Name->currentIndex();
      Curve<RealStatisticalPoint>& curve=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index))->curve();
      curve.sort();
      _hist->filterBelowCurve(curve);
    } else if(d->insideCurvesFilter->isChecked()) {
      int index1=d->curve1Name->currentIndex();
      int index2=d->curve2Name->currentIndex();
      Curve<RealStatisticalPoint>& curve1=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index1))->curve();
      Curve<RealStatisticalPoint>& curve2=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index2))->curve();
      curve1.sort();
      curve2.sort();
      _hist->filterInsideCurves(curve1, curve2);
    } else if(d->outsideCurvesFilter->isChecked()) {
      int index1=d->curve1Name->currentIndex();
      int index2=d->curve2Name->currentIndex();
      Curve<RealStatisticalPoint>& curve1=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index1))->curve();
      Curve<RealStatisticalPoint>& curve2=static_cast<RealStatisticalLine *>(curves->currentLayer()->line(index2))->curve();
      curve1.sort();
      curve2.sort();
      _hist->filterOutsideCurves(curve1, curve2);
    }
    setHistogram(_hist);
  }
  delete d;
}

