/***************************************************************************
**
**  This file is part of max2curve.
**
**  This file may be distributed and/or modified under the terms of the
**  GNU General Public License version 2 or 3 as published by the Free
**  Software Foundation and appearing in the file LICENSE.GPL included
**  in the packaging of this file.
**
**  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 General Public License for
**  more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program. If not, see <http://www.gnu.org/licenses/>.
**
**  See http://www.geopsy.org for more information.
**
**  Created : 2006-01-13
**  Authors:
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpGuiWave.h>
#include <SciFigs.h>

#include "HistogramWidget.h"
#include "GenericOptions.h"
#include "FKOptions.h"
#include "TFAOptions.h"
#include "SPACOptions.h"
#include "StatGridAnalyser.h"
#include "FreqVelocityFilter.h"

HistogramWidget::HistogramWidget(MaxEntryList * maxList, QWidget * parent) : QWidget(parent)
{
  TRACE;
  setupUi(this);

  QSplitter * hSplitter=new QSplitter(this);
  hSplitter->setOrientation(Qt::Horizontal);
  _curves=new CurveBrowser(hSplitter);
  AxisWindow * graph=new AxisWindow(hSplitter);
  graph->setObjectName( "HistogramWidget" );
  _gridLayer=new IrregularGrid2DPlot(graph);
  _gridLayer->setObjectName("histograms");
  ColorMap& map=_gridLayer->beginColorMapChange();
  map.generateColorScale(20, ColorPalette::McNames, true);
  _gridLayer->endColorMapChange();
  LineLayer * curveLayer=new LineLayer(graph);
  curveLayer->setObjectName("curves");

  _maxList=maxList;
  switch (_maxList->type()) {
  case MaxEntryList::Undefined: ASSERT(false);
  case MaxEntryList::FK:
    _curves->setProxy(new DispersionProxy);
    _curves->setPlotProxy(new ModalPlotProxy);
    curveLayer->setReferenceLine(new ModalLine);
    break;
  case MaxEntryList::SPAC:
    _curves->setProxy(new AutocorrProxy);
    _curves->setPlotProxy(new ModalPlotProxy);
    curveLayer->setReferenceLine(new ModalLine);
   break;
  case MaxEntryList::TFA:
    _curves->setProxy(new EllipticityProxy);
    _curves->setPlotProxy(new EllipticityPlotProxy);
    curveLayer->setReferenceLine(new ModalLine);
    break;
  case MaxEntryList::CURVE:
    _curves->setProxy(new RealStatisticalProxy);
    // TODO: create a basic plot proxy
    //_curves->setPlotProxy();
    curveLayer->setReferenceLine(new RealStatisticalLine);
    break;
  }
  _curves->initLayer(curveLayer);
  _curves->setCurrentLayer(curveLayer);
  //QAction * a=_curves->addCurveAction(tr("&Adjust"), tr("Adjust current curve to the closest maximum"), true);
  //connect(a, SIGNAL(triggered()), this, SLOT(adjustCurve()) );
  qobject_cast<QVBoxLayout *>(layout())->insertWidget(0, hSplitter);

  Settings::getRect(this, "HistogramWidget" );

  _statWidget=new StatGridAnalyser(0, Qt::Window);
  _statWidget->setGrid(_gridLayer, graph->yAxis());
  Settings::getWidget(_statWidget, "StatGridAnalyser");
  connect(_statWidget, SIGNAL(reject( double, double, double) ),
           this, SLOT(unselectValue( double, double, double) ));
  connect(_statWidget, SIGNAL(undo( double) ),
           this, SLOT(selectAll( double) ));
  connect(_statWidget, SIGNAL(newMean()), this, SLOT(addMeanCurve()) );
  connect(_statWidget, SIGNAL(newMedian()), this, SLOT(addMedianCurve()) );
  connect(_statWidget, SIGNAL(newMode()), this, SLOT(addModeCurve()) );
}

HistogramWidget::~HistogramWidget()
{
  TRACE;
  Settings::setRect(_statWidget, "StatGridAnalyser" );
  delete _statWidget;
  delete _maxList;
}

bool HistogramWidget::select(bool scaleTypeForced, SamplingOption scaleType)
{
  TRACE;
  double minValueHisto, maxValueHisto;
  int nValueClasses, ringIndex, componentIndex;
  double relFac, absFac, nppm;
  SAFE_UNINITIALIZED(nppm,0);
  SAFE_UNINITIALIZED(relFac,0);
  SAFE_UNINITIALIZED(absFac,0);
  SAFE_UNINITIALIZED(componentIndex,0);
  SAFE_UNINITIALIZED(ringIndex,0);
  SAFE_UNINITIALIZED(nValueClasses,0);
  SAFE_UNINITIALIZED(minValueHisto,0);
  SAFE_UNINITIALIZED(maxValueHisto,0);
  switch (_maxList->type()) {
  case MaxEntryList::FK: {
      FKOptions * d=new FKOptions(this);
      Settings::getWidget(d);
      if(d->exec()!=QDialog::Accepted) {
        delete d;
        return false;
      }
      minValueHisto=1.0/d->maxVelocity->text().toDouble();
      maxValueHisto=1.0/d->minVelocity->text().toDouble();
      nValueClasses=d->nVelocityClasses->value();
      relFac=d->relPowerThreshold->value() * 0.01;
      absFac=d->absPowerThreshold->value() * 0.01;
      Settings::setWidget(d);
      delete d;
    }
    setWindowTitle(windowTitle().arg("FK"));
    _statWidget->setUnits("s/m");
    if(!scaleTypeForced) scaleType=LinearScale;
    break;
  case MaxEntryList::SPAC: {
      SPACOptions * d=new SPACOptions(this);
      Settings::getWidget(d);
      if(d->exec()!=QDialog::Accepted) {
        delete d;
        return false;
      }
      minValueHisto=d->maxValue->text().toDouble();
      maxValueHisto=d->minValue->text().toDouble();
      nValueClasses=d->nValueClasses->value();
      ringIndex=d->ringIndex->value();
      componentIndex=d->componentIndex->value();
      Settings::setWidget(d);
      delete d;
      setWindowTitle(windowTitle().arg("SPAC - ring %1 - component %2").arg(ringIndex).arg(componentIndex));
    }
    _statWidget->setUnits("");
    if(!scaleTypeForced) scaleType=LinearScale;
    break;
  case MaxEntryList::TFA: {
      TFAOptions * d=new TFAOptions(this);
      Settings::getWidget(d);
      if(d->exec()!=QDialog::Accepted) {
        delete d;
        return false;
      }
      minValueHisto=d->maxValue->text().toDouble();
      maxValueHisto=d->minValue->text().toDouble();
      nValueClasses=d->nValueClasses->value();
      nppm=d->nppm->value();
      Settings::setWidget(d);
      delete d;
    }
    setWindowTitle(windowTitle().arg("TFA"));
    _statWidget->setUnits("");
    if(!scaleTypeForced) scaleType=LogScale;
    break;
  case MaxEntryList::CURVE: {
      GenericOptions * d=new GenericOptions(this);
      Settings::getWidget(d);
      if(d->exec()!=QDialog::Accepted) {
        delete d;
        return false;
      }
      //minValueHisto=d->minValue->text().toDouble();
      //maxValueHisto=d->maxValue->text().toDouble();
      //nValueClasses=d->nValueClasses->value();
      Settings::setWidget(d);
      delete d;
    }
    setWindowTitle(windowTitle().arg("CURVE"));
    _statWidget->setUnits("");
    if(!scaleTypeForced) scaleType=LinearScale;
    break;
  default:
    return false;
  }
  // Sample selection
  switch (_maxList->type()) {
  case MaxEntryList::FK:
    _maxList->rejectPower(relFac, absFac);
    break;
  case MaxEntryList::SPAC:
    _maxList->keepRing(ringIndex);
    _maxList->keepComponent(componentIndex);
    break;
  case MaxEntryList::TFA:
    _maxList->rejectNPeaksPerMinute(nppm);
    break;
  default:
    break;
  }
  initGrid(nValueClasses, minValueHisto, maxValueHisto, scaleType);
  fillGrid();
  addMedianCurve();
  setLimits();
  return true;
}

AxisWindow * HistogramWidget::graph()
{
  TRACE;
  return _curves->currentLayer()->graph();
}

bool HistogramWidget::isLogScale()
{
  TRACE;
  return graph()->yAxis()->scaleType()==Scale::Log;
}

void HistogramWidget::initGrid(int nValueClasses, double minValueHisto, double maxValueHisto, SamplingOption ySampling)
{
  TRACE;
  if(minValueHisto > maxValueHisto) {
    double tmp=minValueHisto;
    minValueHisto=maxValueHisto;
    maxValueHisto=tmp;
  }
  if((ySampling & LogScale) && (minValueHisto<=0.0 || maxValueHisto<=0.0) ) {
    App::log(tr("Null or negative values not allowed for log scales, back to linear scale\n") );
    ySampling=LinearScale;
  }
  if(ySampling==LogScale) {
    graph()->yAxis()->setScaleType(Scale::Log);
    _curves->currentLayer()->setErrorBar(LineLayer::VerticalLogBar);
  }
  _gridLayer->setGrid(_maxList->initGrid(nValueClasses, minValueHisto, maxValueHisto, ySampling) );
}

void HistogramWidget::fillGrid()
{
  TRACE;
  IrregularGrid2D grid=_gridLayer->grid();
  _maxList->fillGrid(grid);
  // Normalize histogram
  normalizeGrid(grid);
  _gridLayer->setGrid(grid);
  _statWidget->gridChanged();
  graph()->deepUpdate();
}

void HistogramWidget::setLimits()
{
  TRACE;
  const IrregularGrid2D& grid=_gridLayer->grid();
  AxisWindow * g=graph();
  if(grid.nx() > 0) {
    g->xAxis()->setRange(grid.minimumAxis(XAxis), grid.maximumAxis(XAxis) );
    g->yAxis()->setRange(grid.minimumAxis(YAxis), grid.maximumAxis(YAxis) );
  }
  _statWidget->gridChanged();
  ColorMap& map=_gridLayer->beginColorMapChange();
  map.setVLinear(0, _gridLayer->grid().maximumValue()*0.5);
 _gridLayer->endColorMapChange();
  g->deepUpdate();
}

/*!

*/
void HistogramWidget::on_gridFilter_clicked()
{
  TRACE;
  FreqVelocityFilter * d=new FreqVelocityFilter(this);
  d->setCurveList(_curves->curves());
  Settings::getWidget(d);
  d->enableWidgets();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    IrregularGrid2D grid=_gridLayer->grid();
    if(d->thresholdFilter->isChecked()) {
      gridFilterThreshold(d->thresholdValue->text().toDouble());
    } else if(d->waveNumberFilter->isChecked()) {
      gridFilterWavenumber(d->kmin->text().toDouble(), d->kmax->text().toDouble());
    } else if(d->curveFilter->isChecked()) {
      gridFilterCurve(d->curveName->currentIndex(), d->curveSigma->value());
    }
    normalizeGrid(grid);
    _gridLayer->setGrid(grid);
    _statWidget->gridChanged();
    graph()->deepUpdate();
  }
  delete d;
}

void HistogramWidget::gridFilterThreshold(double t)
{
  TRACE;
  IrregularGrid2D grid=_gridLayer->grid();
  double * val=grid.valuePointer(0, 0);
  int nCells=grid.nx()*grid.ny();
  for(int i=0; i<nCells; i++) {
    if(val[i]<t) {
      val[i]=0;
    }
  }
  setGrid(grid);
}

void HistogramWidget::gridFilterWavenumber(double kmin, double kmax)
{
  TRACE;
  if(kmin<kmax) {
    qSwap(kmin, kmax);
  }
  _maxList->unselectWavenumber(kmin, kmax);
  IrregularGrid2D grid=_gridLayer->grid();
  _maxList->fillGrid(grid);
  setGrid(grid);
}

void HistogramWidget::gridFilterCurve(int curveIndex, double sigmaFactor)
{
  TRACE;
  if(curveIndex<_curves->currentLayer()->count()) {
    ModalCurve& curve=static_cast<ModalLine *>(_curves->currentLayer()->line(curveIndex))->curve();
    IrregularGrid2D grid=_gridLayer->grid();
    for(int i=grid.nx()-1; i>=0; i--) {
      double x=grid.x(i);
      if(curve.isInsideRange(x)) {
        const RealStatisticalPoint& p=curve.at(x);
        double dy=p.stddev()*sigmaFactor;
        _maxList->unselectValue(x, p.mean()-dy, p.mean()+dy);
      }
    }
    _maxList->fillGrid(grid);
    setGrid(grid);
  }
}

void HistogramWidget::on_gridSmooth_clicked()
{
  TRACE;
  IrregularGrid2D grid=_gridLayer->grid();
  for(int i=grid.nx()-1; i>=0; i--) {
    grid.smooth(YAxis, i);
  }
  setGrid(grid);
}

void HistogramWidget::setGrid(IrregularGrid2D& grid)
{
  TRACE;
  normalizeGrid(grid);
  _gridLayer->setGrid(grid);
  _statWidget->gridChanged();
  graph()->deepUpdate();
}

void HistogramWidget::unselectValue(double frequency, double minValue, double maxValue)
{
  TRACE;
  if(minValue<maxValue) {
    _maxList->unselectValue(frequency, minValue, maxValue);
  } else {
    _maxList->unselectValue(frequency, maxValue, minValue);
  }
  IrregularGrid2D grid=_gridLayer->grid();
  _maxList->fillGrid(grid);
  setGrid(grid);
}

void HistogramWidget::selectAll(double frequency)
{
  TRACE;
  _maxList->selectAll(frequency);
  IrregularGrid2D grid=_gridLayer->grid();
  _maxList->fillGrid(grid);
  setGrid(grid);
}

void HistogramWidget::normalizeGrid(IrregularGrid2D& grid)
{
  TRACE;
  bool logScale=isLogScale();
  if(logScale) {
    grid.log10(YAxis);
  }
  for(int i=grid.nx() - 1;i >= 0;i-- ) {
    grid.normalizeArea(YAxis, i);
  }
  if(logScale) {
    grid.pow10(YAxis);
  }
}

/*!

*/
void HistogramWidget::addCurve(const Curve<RealStatisticalPoint>& c)
{
  TRACE;
  switch (_maxList->type()) {
  case MaxEntryList::Undefined: ASSERT(false);
  case MaxEntryList::FK:
  case MaxEntryList::SPAC:
  case MaxEntryList::TFA: {
      ModalLine * l=static_cast<ModalLine *>(_curves->currentLayer()->addLine());
      l->setCurve(c);
      _curves->addLine(l);
      break;
    }
  case MaxEntryList::CURVE: {
      RealStatisticalLine * l=static_cast<RealStatisticalLine *>(_curves->currentLayer()->addLine());
      l->setCurve(c);
      _curves->addLine(l);
      break;
    }
    break;
  }
}

/*!
  If \a curveIndex is negative the last curve is returned.
*/
void HistogramWidget::toStream(QTextStream& s, int curveIndex)
{
  TRACE;
  if(curveIndex<0) {
    curveIndex=_curves->currentLayer()->count()-1;
  }
  switch (_maxList->type()) {
  case MaxEntryList::Undefined: ASSERT(false);
  case MaxEntryList::FK:
  case MaxEntryList::SPAC:
  case MaxEntryList::TFA: {
      ModalLine * l=static_cast<ModalLine *>(_curves->currentLayer()->line(curveIndex));
      s << l->curve().toString();
    }
    break;
  case MaxEntryList::CURVE: {
      RealStatisticalLine * l=static_cast<RealStatisticalLine *>(_curves->currentLayer()->line(curveIndex));
      s << l->curve().toString();
    }
    break;
  }
}

/*!
  Use the grid to compute the median
*/
void HistogramWidget::addMedianCurve()
{
  TRACE;
  bool logScale=isLogScale();
  IrregularGrid2D grid=_gridLayer->grid();
  if(logScale) {
    grid.log10(YAxis);
  }
  Curve<RealStatisticalPoint> c(grid.nx());
  for(int iFreq=0;iFreq < grid.nx();iFreq++ ) {
    RealStatisticalPoint& p=c[iFreq];
    p.setX(grid.x(iFreq));
    double a=grid.median(YAxis, iFreq);
    if(logScale) {
      p.setMean(pow( 10.0, a) );
      p.setStddev(pow( 10.0, sqrt(grid.variance(YAxis, iFreq, a) )) );
    } else {
      p.setMean(a);
      p.setStddev(sqrt( grid.variance(YAxis, iFreq, a) ));
    }
    p.setWeight(_maxList->count(grid.x(iFreq) ));
  }
  c.sort();
  addCurve(c);
}

/*!
  Use the grid to compute the mode
*/
void HistogramWidget::addModeCurve()
{
  TRACE;
  bool logScale=isLogScale();
  IrregularGrid2D grid=_gridLayer->grid();
  if(logScale) {
    grid.log10(YAxis);
  }
  Curve<RealStatisticalPoint> c(grid.nx());
  for(int iFreq=0;iFreq < grid.nx();iFreq++ ) {
    RealStatisticalPoint& p=c[iFreq];
    p.setX(grid.x(iFreq) );
    double a=grid.mode(YAxis,iFreq);
    if(logScale) {
      p.setMean(pow( 10.0, a) );
      p.setStddev(pow( 10.0, sqrt(grid.variance(YAxis, iFreq, a) )) );
    } else {
      p.setMean(a);
      p.setStddev(sqrt( grid.variance(YAxis, iFreq, a) ));
    }
    p.setWeight(_maxList->count(grid.x(iFreq) ));
  }
  c.sort();
  addCurve(c);
}

/*!
  Use a direct computation of the mean from the samples themselves. More precise than from the grid (rounding errors due
  to class definition).
*/
void HistogramWidget::addMeanCurve()
{
  TRACE;
  const IrregularGrid2D& grid=_gridLayer->grid();
  SamplingOption scale=isLogScale() ? LogScale : LinearScale;
  Curve<RealStatisticalPoint> c(grid.nx());
  for(int iFreq=0;iFreq < grid.nx();iFreq++ ) {
    RealStatisticalPoint& p=c[iFreq];
    p.setX(grid.x(iFreq));
    p=_maxList->meanValue(p.x(), scale);
  }
  c.sort();
  addCurve(c);
}

void HistogramWidget::on_closeButton_clicked()
{
  TRACE;
  QApplication::quit();
}

void HistogramWidget::on_saveButton_clicked()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Saving max file"),tr("max file (*.max)"));
  if(!fileName.isEmpty()) {
    _maxList->save(fileName);
  }
}

void HistogramWidget::closeEvent(QCloseEvent *e)
{
  TRACE;

  Settings::setRect(this, "HistogramWidget" );
  QApplication::quit();
  e->accept();
}

void HistogramWidget::show()
{
  TRACE;
  QWidget::show();
  _statWidget->show();
}

void HistogramWidget::adjustCurve()
{
  TRACE;
  const IrregularGrid2D& grid=_gridLayer->grid();
  AbstractLine * line=_curves->currentLayer()->line(_curves->currentLine());
  switch (_maxList->type()) {
  case MaxEntryList::Undefined: ASSERT(false);
  case MaxEntryList::FK:
  case MaxEntryList::SPAC:
  case MaxEntryList::TFA:
    grid.followMaximumX<ModalCurve, FactoryPoint>(static_cast<ModalLine *>(line)->curve(), 0, std::numeric_limits<double>::infinity());
    break;
  case MaxEntryList::CURVE:
    grid.followMaximumX<Curve<RealStatisticalPoint>, RealStatisticalPoint>(static_cast<RealStatisticalLine *>(line)->curve(), 0, std::numeric_limits<double>::infinity());
    break;
  }
}

/*!
  Loads curves from file \a fileName.
*/
void HistogramWidget::loadCurves(const QString& fileName, const QString& parserFileName)
{
  TRACE;
  ColumnTextParser parser;
  parser.setStandardTypes(_curves->proxy()->columnFileTypes());
  if(parserFileName.isEmpty()) {
    parser.setTypes(_curves->proxy()->defaultColumnFileTypes());
  } else {
    XMLHeader hdr(&parser);
    if(hdr.xml_restoreFile(parserFileName)!=XMLClass::NoError) {
      return;
    }
  }
  QFile f(fileName);
  if(f.open(QIODevice::ReadOnly)) {
    parser.setText(new QTextStream(&f));
    parser.start();
    parser.wait();
    int n=_curves->curves().count();
    _curves->loadMultiColumns(&parser, fileName);
    if(_curves->curves().count()>n) {
      _curves->curves().last()->setCurveVisible(false);
    }
  } else {
    App::log(tr("Cannot read file %1\n").arg(fileName) );
  }
}

void HistogramWidget::exportPlot(const ExportOptions& o)
{
  TRACE;
  AxisWindow * w=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))) {
      _gridLayer->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);
    }
  }
  setLimits();
  foreach(QString m, o.makeupObjectFiles()) {
    w->restoreMakeUp(m);
  }
  if(!o.exportFile().isEmpty()) {
    w->exportFile(o.exportFile(), o.exportFormat(), o.dpi());
  }
}
