/***************************************************************************
**
**  This file is part of geopsyarray.
**
**  geopsyarray 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.
**
**  geopsyarray 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: 2004-09-02
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <QGpGuiWave.h>

#include "ActiveFKToolWidget.h"
#include "ActiveFKResults.h"

#define _activeFKResults static_cast<ActiveFKResults *>(_childrenList[0])

ActiveFKToolWidget::ActiveFKToolWidget(QWidget * parent) :
    AbstractToolWidget(parent, 1)
{
  TRACE;
  setupUi(this);

  delete _tool;
  _tool=new ActiveFKToolSet;

  delayEdit->setMinimum(-std::numeric_limits<double>::infinity());
  delayEdit->setMaximum(std::numeric_limits<double>::infinity());
  durationEdit->setMaximum(std::numeric_limits<double>::infinity());

  connect(startBut, SIGNAL(clicked()), this, SLOT(start()));
  connect(currentSource, SIGNAL(currentIndexChanged(int)),
           this, SLOT(on_currentSource_currentIndexChanged(int)));
  connect(minDistance, SIGNAL(valueChanged(double)), this, SLOT(timeWindowChanged()));
  connect(maxDistance, SIGNAL(valueChanged(double)), this, SLOT(timeWindowChanged()));
  connect(delayEdit, SIGNAL(valueChanged(double)), this, SLOT(timeWindowChanged()));
  connect(durationEdit, SIGNAL(valueChanged(double)), this, SLOT(timeWindowChanged()));
  connect(xSampling, SIGNAL(parametersChanged()), this, SLOT(setResultXAxis()));
  connect(ySampling, SIGNAL(parametersChanged()), this, SLOT(setResultYAxis()));
  connect(loadParam, SIGNAL(clicked()), this, SLOT(loadLogParameters()));
  connect(saveParam, SIGNAL(clicked()), this, SLOT(saveLogParameters()));
  connect(wavelengthLimit, SIGNAL(valueChanged(double)), this, SLOT(setWavelengthLimit(double)));
  connect(currentDisplaySource, SIGNAL(activated(int)), this, SLOT(setCurrentDisplaySource()));

  QAction * a=curves->addCurveAction(tr("&Adjust"), tr("Adjust current curve to the closest maximum"), true);
  connect(a, SIGNAL(triggered()), this, SLOT(adjustCurve()));

  // setup results graphs
  _childrenList[0]=new ActiveFKResults;
  _childrenList[0]->setObjectName("ActiveFKResults");
  GeopsyGuiEngine::instance()->addSubWindow(this, _activeFKResults)->setUserClosable(false);

  ActiveFKParameters param;

  xSampling->setUnit(tr(" Hz"));
  xSampling->setPrecision(2);
  xSampling->setSingleStep(0.25);
  xSampling->setAdmissibleRange(1e-99, std::numeric_limits<double>::infinity());
  xSampling->setParameters(param.frequencySampling());

  slowType->setCurrentIndex(0);
  setSlownessType();
  ySampling->setPrecision(5);    // Avoid rounding errors when reloading parameters
  ySampling->setParameters(param.ySampling());

  curves->setProxy(new DispersionProxy);
  curves->setPlotProxy(new ModalPlotProxy);

  processTypeEdit->blockSignals(true);
  processTypeEdit->addItem(tr("Conventional"));
  processTypeEdit->addItem(tr("High resolution"));
  processTypeEdit->blockSignals(false);
}

ActiveFKToolWidget::~ActiveFKToolWidget()
{
  TRACE;
}

void ActiveFKToolWidget::updateAllFields()
{
  TRACE;
  xSampling->updateAllFields();
  ySampling->updateAllFields();
  ySampling->setInversed(slowType->currentIndex()==1, false);
  on_processTypeEdit_currentIndexChanged(processTypeEdit->currentIndex());
  setSlownessType();
  setResultXAxis();
  timeWindowChanged();
  currentDisplaySource->setCurrentIndex(0);
  currentSource->setCurrentIndex(0);
}

void ActiveFKToolWidget::setResultXAxis()
{
  TRACE;
  SamplingParameters s;
  xSampling->getParameters(s);
  _activeFKResults->setXAxis(s);
}

void ActiveFKToolWidget::setResultYAxis()
{
  TRACE;
  SamplingParameters param;
  ySampling->getParameters(param);
  if(param.isInversed()) {
    if(param.type()==SamplingParameters::Log) {
      _activeFKResults->setYAxis(Scale::InversedLog, 1.0/param.maximum(), 1.0/param.minimum());
    } else {
      _activeFKResults->setYAxis(Scale::Inversed, 1.0/param.maximum(), 1.0/param.minimum());
    }
  } else {
    if(param.type()==SamplingParameters::Log) {
      _activeFKResults->setYAxis(Scale::Log, param.minimum(), param.maximum());
    } else {
      _activeFKResults->setYAxis(Scale::Linear, param.minimum(), param.maximum());
    }
  }
}

bool ActiveFKToolWidget::setSubPool(SubSignalPool * subPool)
{
  TRACE;
  if(AbstractToolWidget::setSubPool(subPool)) {
    _activeFKResults->createObjects(tool());
    _activeFKResults->setWindowTitle(tr("Active fk results (%1 sources)")
                                     .arg(tool()->sources().count()));
    connect(_activeFKResults, SIGNAL(newPlot(LineLayer *, QString)),
             this, SLOT(addCurvePlot(LineLayer *, QString)));
    connect(_activeFKResults, SIGNAL(newCurve(int, ModalLine *)), this, SLOT(newLine(int, ModalLine *)));
    int n=tool()->sources().count();
    for(int i=0; i<n; i++) {
      QString name=tool()->sources().at(i)->source()->name();
      currentSource->addItem(name);
      currentDisplaySource->addItem(name);
      curves->initLayer(_activeFKResults->curveLayer(i));
      connect(_activeFKResults->sheet(), SIGNAL(activeSelectionChanged(GraphicObject *)),
               this, SLOT(graphSelected(GraphicObject *)));
    }
    return true;
  } else {
    return false;
  }
}

void ActiveFKToolWidget::addCurvePlot(LineLayer * curveLayer, QString caption)
{
  TRACE;
  currentSource->addItem(caption);
  curves->initLayer(curveLayer);
}

void ActiveFKToolWidget::newLine(int index, ModalLine * line)
{
  TRACE;
  if(currentSource->currentIndex()==index) {
    curves->addLine(line);
  }
}

void ActiveFKToolWidget::on_currentSource_currentIndexChanged(int index)
{
  TRACE;
  curves->setCurrentLayer(_activeFKResults->curveLayer(index));
}

void ActiveFKToolWidget::timeWindowChanged()
{
  TRACE;
  if(!timeWindowLayer()) return;
  timeWindowLayer()->setWindowOwnership(true);
  ActiveFKParameters  * param=static_cast<ActiveFKParameters *>(parameters());
  if(!param) {
    return;
  }
  LayerLocker ll(timeWindowLayer());
  timeWindowLayer()->clearTimeWindows();
  int nArrays=tool()->sources().count();
  for(int iArray=0; iArray<nArrays; iArray++) {
    ActiveFKTool * t=tool()->sources().at(iArray);
    ArraySelection a(t->array());
    a.select(t->source()->position(), param->minimumDistance(), param->maximumDistance());
    FKTaskManager manager(t->array());
    manager.setParameters(param);
    ArrayTimeWindows timeWindows(&a, manager.keep(), param);
    timeWindows.setFrequencyIndex(0);
    timeWindows.setWindows();
    for(int i=a.count()-1; i>=0; i--) {
      const StationSignals * s=a.at(i);
      const SubSignalPool subPool=s->originals(0);
      for(SubSignalPool::const_iterator itSig=subPool.begin(); itSig!=subPool.end(); itSig++) {
        timeWindowLayer()->addTimeWindows(*itSig, new TimeWindowList(timeWindows.list()));
      }
    }
  }
  delete param;
  timeWindowLayer()->deepUpdate();
}

void ActiveFKToolWidget::start()
{
  TRACE;
  if(subPoolLocked()) {
    return;
  }
  // Get parameters from dialog box
  ActiveFKParameters  * param=static_cast<ActiveFKParameters *>(parameters());
  if(!param) {
    return;
  }
  lockSubPool();
  if(tool()->setParameters(*param)) {
    // Loop on all arrays
    int nArrays=tool()->sources().count();
    SignalDatabase * db=tool()->database();
    if(nArrays>1) GeopsyCoreEngine::instance()->setProgressMaximum(db, nArrays);
    IrregularGrid2DPlot * plot;
    for(int iArray=0; iArray<nArrays; iArray++) {
      if(nArrays>1) GeopsyCoreEngine::instance()->setProgressValue(db, iArray+1);
      plot=_activeFKResults->gridLayer(iArray);
      plot->setGrid(tool()->grid(iArray));
      plot->setLinearColorMap(0);
      _activeFKResults->gridLayer(iArray)->graph()->deepUpdate();
    }
  }
  delete param;
  unlockSubPool();
}

void ActiveFKToolWidget::adjustCurve()
{
  TRACE;
  int index=curves->currentLine();
  if(index>=0) {
    CurveProperties * properties=curves->curves().at(index);
    properties->beginCurveChange();
    CurveProxy * proxy=properties->proxy();
    if(proxy->sampleCount()==0) return;
    CurveBrowserCut * d=new CurveBrowserCut(this);
    d->setCurveLimits(proxy->minimumX(), proxy->maximumX() );
    d->setAxisNames(proxy->xName(), proxy->xInversedName());
    d->setWindowTitle(tr("Adjust"));
    Settings::getWidget(d, "ActiveFKToolWidget::adjustCurve");
    if(d->exec()==QDialog::Accepted) {
      Settings::setWidget(d, "ActiveFKToolWidget::adjustCurve");
      double minimum=d->minimumEdit->text().toDouble();
      double maximum=d->maximumEdit->text().toDouble();
      QString unit;
      SamplingOptions options=LinearScale;
      if(d->linearBut->isChecked()) {
        unit=proxy->xUnit();
      } else {
        unit=proxy->xInversedUnit();
        options|=InversedScale;
      }
      _activeFKResults->adjust(currentSource->currentIndex(),
                               static_cast<ModalProxy*>(proxy)->curve(),
                               minimum, maximum, options);
      proxy->addLog(tr("Adjust from %1 %2 to %3 %4\n").arg(minimum).arg(unit).arg(maximum).arg(unit) );
      properties->endCurveChange();
    }
    delete d;
  }
}

/*!
  Activate the shot corresponding to the selected graph
*/
void ActiveFKToolWidget::graphSelected(GraphicObject * obj)
{
  TRACE;
  AxisWindow * w=qobject_cast<AxisWindow *>(obj);
  if(w) {
    int n=currentSource->count();
    for(int i=0;i<n;i++) {
      if(_activeFKResults->curveLayer(i)->graph()==w) {
        currentSource->setCurrentIndex(i);
      }
    }
  }
}

void ActiveFKToolWidget::setWavelengthLimit(double waveLength)
{
  _activeFKResults->setWaveLengthLimit(currentSource->currentIndex(), waveLength);
}

void ActiveFKToolWidget::on_slowType_activated(int index)
{
  TRACE;
  ySampling->setPrecision(5);                   // Avoid rounding errors
  ySampling->setAdmissibleRange(1e-99, std::numeric_limits<double>::infinity());
  ySampling->setInversed(index==1);
  setSlownessType();
}

void ActiveFKToolWidget::setSlownessType()
{
  TRACE;
  ySampling->blockSignals(true);
  if(slowType->currentIndex()==0) {  // Slowness
    ySampling->setUnit(tr(" s/m"));
    ySampling->setPrecision(5);
    ySampling->setSingleStep(0.001);
    ySampling->setAdmissibleRange(1e-99, std::numeric_limits<double>::infinity());
  } else {         // Velocity
    ySampling->setUnit(tr(" m/s"));
    ySampling->setPrecision(0);
    ySampling->setSingleStep(10.0);
    ySampling->setAdmissibleRange(1.0, std::numeric_limits<double>::infinity());
  }
  ySampling->blockSignals(false);
  setResultYAxis();
}

AbstractParameters * ActiveFKToolWidget::parameters(AbstractParameters * param) const
{
  TRACE;
  if(!param) {
    param=new ActiveFKParameters;
  }
  getParameters(*static_cast<ActiveFKParameters *>(param));
  if(!checkParameters(static_cast<ActiveFKParameters *>(param))) {
    delete param;
    return nullptr;
  } else {
    return param;
  }
}

void ActiveFKToolWidget::getParameters(ActiveFKParameters& param) const
{
  TRACE;
  param.windowing().setSeismicEventDelay(delayEdit->value());
  param.windowing().setLength(durationEdit->value());

  xSampling->getParameters(param.frequencySampling());
  ySampling->getParameters(param.ySampling());
  // Required to get admissible delay between source and start of signal block
  if(param.ySampling().isInversed()) {
    param.setMaximumSlowness(1.0/param.ySampling().minimum());
  } else {
    param.setMaximumSlowness(param.ySampling().maximum());
  }

  param.setOversamplingFactor(oversamplingEdit->value());
  param.setfrequencyBandwidth(freqBandwidthEdit->value());
  param.setDamping(dampingFactorEdit->value());

  param.setMinimumDistance(minDistance->value());
  param.setMaximumDistance(maxDistance->value());
  if(rawBeamPowerNorm->isChecked()) {
    param.setBeamPowerNormalization(ActiveFKParameters::Raw);
  } else if(spectrumBeamPowerNorm->isChecked()) {
    param.setBeamPowerNormalization(ActiveFKParameters::Spectrum);
  } else {
    param.setBeamPowerNormalization(ActiveFKParameters::Maximum);
  }
  param.setProcessType(processType(processTypeEdit->currentIndex()));
}

void ActiveFKToolWidget::setParameters(const AbstractParameters * absparam)
{
  TRACE;
  const ActiveFKParameters& param=*static_cast<const ActiveFKParameters *>(absparam);

  delayEdit->setValue(param.windowing().seismicEventDelay());
  durationEdit->setValue(param.windowing().minimumLength(0.0));

  SamplingParameters y=param.ySampling();
  slowType->setCurrentIndex(y.isInversed() ? 1 : 0);
  xSampling->setParameters(param.frequencySampling());
  ySampling->setParameters(y);

  oversamplingEdit->setValue(param.oversamplingFactor());
  freqBandwidthEdit->setValue(param.frequencyBandwidth());
  dampingFactorEdit->setValue(param.damping());

  minDistance->setValue(param.minimumDistance());
  maxDistance->setValue(param.maximumDistance());

  switch(param.beamPowerNormalization()) {
  case ActiveFKParameters::Raw:
    rawBeamPowerNorm->setChecked(true);
    break;
  case ActiveFKParameters::Spectrum:
    spectrumBeamPowerNorm->setChecked(true);
    break;
  case ActiveFKParameters::Maximum:
    maxBeamPowerNorm->setChecked(true);
    break;
  }
  processTypeEdit->setCurrentIndex(processType(param.processType()));
}

bool ActiveFKToolWidget::checkParameters(ActiveFKParameters * param) const
{
  TRACE;
  if (param->windowing().minimumLength(0.0)<=0) {
    Message::warning(MSG_ID, tr("Active FK"), tr("Null time window length"));
    return false;
  }
  return true;
}

FKParameters::ProcessType ActiveFKToolWidget::processType(int t) const
{
  TRACE;
  switch(t) {
  case 1:
    return FKParameters::ActiveRTBF;
  default:
    break;
  }
  return FKParameters::ActiveConventional;
}

int ActiveFKToolWidget::processType(FKParameters::ProcessType t) const
{
  TRACE;
  switch(t) {
  case FKParameters::DirectSteering:
  case FKParameters::DirectSteeringRefined:
  case FKParameters::Omni:
  case FKParameters::PoggiRadial:
  case FKParameters::PoggiVertical:
  case FKParameters::ActiveDirectSteering:
  case FKParameters::ActiveRTBF:
  case FKParameters::DirectSteeringRadial:
  case FKParameters::DirectSteeringVertical:
  case FKParameters::RTBF:
  case FKParameters::RTBFRadial:
    return 1;
  case FKParameters::Conventional:
  case FKParameters::ActiveConventional:
    break;
  }
  return 0;
}

void ActiveFKToolWidget::on_processTypeEdit_currentIndexChanged(int index)
{
  dampingFactorEdit->setEnabled(index==1);
}

void ActiveFKToolWidget::setCurrentDisplaySource()
{
  TRACE;
  int stat=currentDisplaySource->currentIndex()-1;
  if(signalLayer()) {
    if(stat==-1) {
      signalLayer()->graph()->yAxis()->setRange(0.5, _tool->subPool()->count()+0.5);
      TimeRange r=subPool()->timeRange();
      const DateTime& t=signalLayer()->graph()->xAxis()->timeReference();
      signalLayer()->graph()->xAxis()->setRange(t.secondsTo(r.start()),
                                                t.secondsTo(r.end()));
    } else {
      ActiveFKTool * s=tool()->sources().at(stat);
      // Find min and max index of s in _subpool
      int n=tool()->subPool()->count();
      int i=0;
      while(i<n && !s->subPool()->contains(tool()->subPool()->at(i))) {
        i++;
      }
      int minIndex=i;
      i++;
      while(i<n && s->subPool()->contains(tool()->subPool()->at(i))) {
        i++;
      }
      int maxIndex=i;
      signalLayer()->graph()->yAxis()->setRange(minIndex+0.5, maxIndex+0.5);
      TimeRange r=s->subPool()->timeRange();
      const DateTime& t=signalLayer()->graph()->xAxis()->timeReference();
      signalLayer()->graph()->xAxis()->setRange(t.secondsTo(r.start()),
                                                t.secondsTo(r.end()));
    }
    signalLayer()->graph()->deepUpdate();
  }
}
