/***************************************************************************
**
**  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 : 2005-10-27
**  Authors:
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QShortcut>
#include <QMenu>

#include <QGpCoreTools.h>
#include <QGpGuiTools.h>
#include <SciFigs.h>
#include "StatGridAnalyser.h"

/*
 *  Constructs a StatGridAnalyser as a child of 'parent', with the
 *  name 'name' and widget flags set to 'f'.
 */
StatGridAnalyser::StatGridAnalyser(QWidget *parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
  TRACE;
  setupUi(this);

  setFocusPolicy(Qt::ClickFocus);
  _grid=0;
  _lastFreq=0;

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

  QAction * a;
  a=new QAction(tr("&Mean"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SIGNAL(newMean()) );
  a=new QAction(tr("Medi&an"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SIGNAL(newMedian()) );
  a=new QAction(tr("Mo&de"), this);
  curveBut->menu()->addAction(a);
  connect(a, SIGNAL(triggered()), this, SIGNAL(newMode()) );

  new QShortcut(Qt::Key_Up, this, SLOT(nextFrequency()) );
  new QShortcut(Qt::Key_Up+Qt::SHIFT, this, SLOT(nextFrequencyReject()) );
  new QShortcut(Qt::Key_Down, this, SLOT(previousFrequency()) );
  new QShortcut(Qt::Key_Down+Qt::SHIFT, this, SLOT(previousFrequencyReject()) );
  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()) );

  _partitionBandLayer=0;
  _partitionCurveLayer=0;
  _densityBandLayer=0;
  _densityCurveLayer=0;
}

/*
 *  Destroys the object and frees any allocated resources
 */
StatGridAnalyser::~StatGridAnalyser()
{
  TRACE;
}

void StatGridAnalyser::shortcutEvents(Shortcuts s)
{
  TRACE;
  GraphContent * gc=probaDensity->graphContent();
  const ParallelBand& b=_densityBandLayer->band(0);
  int iFreq=freqScroll->value();

  double dx;
  SAFE_UNINITIALIZED(dx,0);
  switch (s) {
  case NextFrequency:
  case NextFrequencyReject:
  case PreviousFrequency:
  case PreviousFrequencyReject:
    break;
  case DecreaseLowLimit:
  case IncreaseLowLimit:
    dx=fabs(b.minimum()-gc->options().xs2r(gc->options().xr2s(b.minimum())+1)); // estimation of 1 pixel width
    break;
  case DecreaseHighLimit:
  case IncreaseHighLimit:
    dx=fabs(b.maximum()-gc->options().xs2r(gc->options().xr2s(b.maximum())+1)); // estimation of 1 pixel width
    break;
  }

  switch (s) {
  case NextFrequency:
    freqScroll->setValue(iFreq+1);
    break;
  case NextFrequencyReject:
    setRejectBand(iFreq+1);
    on_rejectBut_clicked();
    freqScroll->setValue(iFreq+1);
    break;
  case PreviousFrequency:
    freqScroll->setValue(iFreq-1);
    break;
  case PreviousFrequencyReject:
    setRejectBand(iFreq-1);
    on_rejectBut_clicked();
    freqScroll->setValue(iFreq-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 StatGridAnalyser::setRejectBand(int nextFreq)
{
  TRACE;
  if(nextFreq>=_bands.count()) return;
  if(nextFreq<0) return;
  double delta=_grid->grid().median(YAxis, nextFreq) -
                 _grid->grid().median(YAxis, freqScroll->value());
  const ParallelBand& b=_densityBandLayer->band(0);
  _bands[nextFreq].first=b.minimum()+delta;
  _bands[nextFreq].second=b.maximum()+delta;
}

void StatGridAnalyser::setGrid(IrregularGrid2DPlot * grid, Axis * valueAxis)
{
  TRACE;
  _grid=grid;
  _valueAxis=valueAxis;

  _densityBandLayer=new ParallelBands(probaDensity);
  _densityBandLayer->setObjectName( "FKStatSlowLimits" );
  _densityBandLayer->addTrackingAction(tr("Pick bands"), 0, tr("Define the frequency band to rejet."));
  _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(probaDensity);
  _densityCurveLayer->setObjectName( "FKStatProba" );
  _densityCurveLayer->setReferenceLine(new PlotLine2D);
  _densityCurveLayer->setReferencePen(Pen( Qt::blue) );
  probaDensity->yAxis()->setNumberType('e');
  probaDensity->yAxis()->setNumberPrecision(1);
  probaDensity->yAxis()->setTitle( "Probability density" );
  _densityCurveLayer->addLine(Pen( Qt::blue), Symbol());
  _densityCurveLayer->addLine(Pen( Qt::red), Symbol());

  _partitionBandLayer=new ParallelBands(probaPartition);
  _partitionBandLayer->setObjectName( "FKStatSlowLimits" );
  _partitionBandLayer->addTrackingAction(tr("Pick bands"), 0, tr("Define the frequency band to rejet."));
  _partitionBandLayer->setOpacity(0.6);
  _partitionBandLayer->addBand(0, 0, Brush(QColor( 205, 242, 255) ));
  connect(_partitionBandLayer, SIGNAL(bandPicked(double, double)), this, SLOT(setBand( double, double) ));
  _partitionCurveLayer=new LineLayer(probaPartition);
  _partitionCurveLayer->setObjectName( "FKStatPartF" );
  _partitionCurveLayer->setReferenceLine(new PlotLine2D);
  _partitionCurveLayer->setReferencePen(Pen( Qt::blue) );
  probaPartition->yAxis()->setRange(0.0, 1.0);
  probaPartition->yAxis()->setNumberType('f');
  probaPartition->yAxis()->setNumberPrecision(1);
  probaPartition->yAxis()->setTitle( "Partition Function" );
  _partitionCurveLayer->addLine(Pen( Qt::blue), Symbol());
  _partitionCurveLayer->addLine(Pen( Qt::red), Symbol());
}

void StatGridAnalyser::gridChanged()
{
  TRACE;
  const IrregularGrid2D& g=_grid->grid();
  if(g.nx()==0) {
    freqScroll->setMaximum(0);
  } else {
    freqScroll->setMaximum(g.nx() - 1);
    *probaDensity->xAxis()=*_valueAxis;
    static bool initialized=false;
    if(!initialized) probaDensity->yAxis()->setRange(0.0, g.maximumValue());
    initialized=true;
    *probaPartition->xAxis()=*_valueAxis;
    on_freqScroll_valueChanged(0);
  }
  _bands.resize(g.nx());
}

void StatGridAnalyser::on_freqScroll_valueChanged(int)
{
  TRACE;
  IrregularGrid2D grid=_grid->grid();
  if(grid.nx()==0) return ;
  bool logScale=_valueAxis->scaleType()==Scale::Log;

  int iFreq=freqScroll->value();
  freqEdit->setText(QString( "%1 Hz" ).arg(grid.x(iFreq) ));

  PlotLine2D * gridDensityLine=static_cast<PlotLine2D *>(_densityCurveLayer->line(0));
  PlotLine2D * gaussDensityLine=static_cast<PlotLine2D *>(_densityCurveLayer->line(1));
  PlotLine2D * gridPartitionLine=static_cast<PlotLine2D *>(_partitionCurveLayer->line(0));
  PlotLine2D * gaussPartitionLine=static_cast<PlotLine2D *>(_partitionCurveLayer->line(1));

  _densityCurveLayer->lockDelayPainting();
  _partitionCurveLayer->lockDelayPainting();

  gridDensityLine->setCurve(grid.crossSection(YAxis, iFreq) );
  gaussDensityLine->setCurve(gridDensityLine->curve());
  gridPartitionLine->setCurve(gridDensityLine->curve());
  gaussPartitionLine->setCurve(gridDensityLine->curve());

  if(logScale) {
    grid.log10(YAxis);
  }

  double mean=grid.mean(YAxis, iFreq);
  double stddev=sqrt(grid.variance(YAxis, iFreq) );
  double med=grid.median(YAxis, iFreq);
  double meddev=sqrt(grid.variance(YAxis, iFreq, med) );
  double mod=grid.mode(YAxis, iFreq);
  double moddev=sqrt(grid.variance(YAxis, iFreq, mod) );

  int n=gridDensityLine->curve().count();
  double sum=0;
  for(int i=0;i < n;i++ ) {
    Point2D& p=gridPartitionLine->curve()[i];
    sum += p.y();
    p.setY(sum * grid.height(i) );
  }

  GaussDistribution gd(mean, stddev);
  for(int i=0;i < n;i++ ) {
    Point2D& p=gaussDensityLine->curve()[i];
    if(logScale)
      p.setY(gd.rho(log10( p.x()) ));
    else
      p.setY(gd.rho(p.x()) );
  }
  for(int i=0;i < n;i++ ) {
    Point2D& p=gaussPartitionLine->curve()[i];
    if(logScale)
      p.setY(gd.part(log10( p.x()) ));
    else
      p.setY(gd.part(p.x()) );
  }

  if(_lastFreq<_bands.count()) {
    const ParallelBand& b=_densityBandLayer->band(0);
    _bands[_lastFreq].first=b.minimum();
    _bands[_lastFreq].second=b.maximum();
  }
  if(iFreq<_bands.count()) {
    if(_bands[iFreq].first!=0.0 || _bands[iFreq].second!=0.0) {
      setBand(_bands[iFreq].first, _bands[iFreq].second);
    }
    _lastFreq=iFreq;
  }

  _densityCurveLayer->unlock();
  _partitionCurveLayer->unlock();

  if(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);
  }
  statValuesLabel->setText(tr( "Average %1 %7 - Stddev %2 %7\n"
                                "Median %3 %7 - Meddev %4 %7\n"
                                "Mode %5 %7 - Moddev %6 %7" ).
                            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(_units));

  probaDensity->graphContent() ->deepUpdate();
  probaPartition->graphContent() ->deepUpdate();
}

/*!
  Reject all samples outside the band. If SHIFT key is pressed, the current frequency is incremented.
  If the band for next frequency 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 StatGridAnalyser::on_rejectBut_clicked()
{
  TRACE;
  const ParallelBand& b=_densityBandLayer->band(0);
  emit reject(_grid->grid().x(freqScroll->value()), b.minimum(), b.maximum());
}

void StatGridAnalyser::on_undoBut_clicked()
{
  TRACE;
  emit undo(_grid->grid().x(freqScroll->value()) );
}

void StatGridAnalyser::on_rejectAllBut_clicked()
{
  TRACE;
  const IrregularGrid2D& g=_grid->grid();
  for(int i=g.nx()-1; i>=0; i-- ) {
    emit reject(g.x(i), _bands[i].first, _bands[i].second);
  }
}

void StatGridAnalyser::on_undoAllBut_clicked()
{
  TRACE;
  const IrregularGrid2D& g=_grid->grid();
  for(int i=g.nx()-1; i>=0; i-- ) {
    emit undo(g.x(i) );
  }
}

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