/***************************************************************************
**
**  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-04
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyGui.h>
#include <ArrayCore.h>

#include "FKToolWidget.h"
#include "FKTimeWindows.h"

#define _fkBrowser static_cast<FKTimeWindows*>(_childrenList[0])

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

  _currentTestFrequency=0.0;

  delete _tool;
  _tool=new FKTool;
  connect(tool(), SIGNAL(waveNumberGridReady()), this, SLOT(setWaveNumberGrid()));
  connect(tool(), SIGNAL(finished()), this, SLOT(finish()));

  freqSamp->setUnit(tr(" Hz"));
  freqSamp->setPrecision(2);
  freqSamp->setSingleStep(0.25);
  freqSamp->setAdmissibleRange(1e-99, std::numeric_limits<double>::infinity());
  winParam->removeFilterOption();
  winParam->setLength(WindowingParameters::FrequencyDependent, 50.0);
  maxPeakCount->setMaximum(INT_MAX);

  connect(startBut, SIGNAL(clicked()), this, SLOT(start()));
  connect(stopBut, SIGNAL(clicked()), this, SLOT(stop()));
  connect(testBut, SIGNAL(clicked()), this, SLOT(test()));
  connect(loadParam, SIGNAL(clicked()), this, SLOT(loadLogParameters()));
  connect(freqSamp, SIGNAL(parametersChanged()), this, SLOT(numFreqChanged()));

  connect(timeLimits, SIGNAL(parametersChanged()), this, SLOT(parametersChanged()));
  connect(selectMinimumDurationEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(winParam, SIGNAL(parametersChanged()), this, SLOT(parametersChanged()));

  connect(blockAveraging, SIGNAL(parametersChanged()), this, SLOT(parametersChanged()));

  connect(freqBandwidthEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(oversamplingEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(dampingFactorEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(rotateStepCountEdit, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(minDistance, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));
  connect(maxDistance, SIGNAL(valueChanged(const QString&)), this, SLOT(parametersChanged()));

  connect(gridStepEdit, SIGNAL(valueChanged(const QString& )), this, SLOT(gridStepChanged()));
  connect(gridSizeEdit, SIGNAL(valueChanged(const QString& )), this, SLOT(gridSizeChanged()));
  connect(vminEdit, SIGNAL(valueChanged(const QString& )), this, SLOT(parametersChanged()));
  connect(vmaxEdit, SIGNAL(valueChanged(const QString& )), this, SLOT(parametersChanged()));

  _outputFileChecked=false;
  _exportAllFKGrids=false;
  _gridSizeTouched=false;
  _gridStepTouched=false;

  // Temporary deactivate gradient search
  inversionMethodEdit->setCurrentIndex(1);
  inversionMethodEdit->setEnabled(false);
}

FKToolWidget::~FKToolWidget()
{
  TRACE;
}

void FKToolWidget::restoreFields()
{
  TRACE;
  AbstractToolWidget::restoreFields();
  // kmin and kmax can be computed with unselected and without parameters
  // For instance, magnetic declination is provided in parameters and
  // introduces effects only for 3C component processing.
  // kmin and kmax are set after restoring past user values
  tool()->setWaveNumberGrid();
}

void FKToolWidget::updateAllFields()
{
  TRACE;
  winParam->updateAllFields();
  timeLimits->updateAllFields();
  blockAveraging->updateAllFields();
  freqSamp->updateAllFields();
  numFreqChanged();
  on_freqScroll_valueChanged(freqScroll->value());
  on_processTypeEdit_currentIndexChanged(processTypeEdit->currentIndex());
}

/*!
  Check if enough precision to represent these numbers, at least 3 significant digits
*/
void FKToolWidget::setWaveNumberGrid()
{
  TRACE;
  double k;
  const FKParameters * param=tool()->parameters();
  if(!_gridStepTouched) {
    k=param->kmin()*FKParameters::defaultGridStepFactor(processType(processTypeEdit->currentIndex()),
                                                        inversionMethod(inversionMethodEdit->currentIndex()));
    gridStepEdit->blockSignals(true);
    if(k>0.0 && k<pow(10.0, -gridStepEdit->decimals())) {
      gridStepEdit->setDecimals(3-qFloor(log10(k)));
    }
    gridStepEdit->setValue(k);
    gridStepEdit->blockSignals(false);
  }
  if(!_gridSizeTouched) {
    k=FKParameters::gridSize(param->kmax());
    gridSizeEdit->blockSignals(true);
    if(k>0.0 && k<pow(10.0, -gridSizeEdit->decimals())) {
      gridSizeEdit->setDecimals(3-qFloor(log10(k)));
    }
    gridSizeEdit->setValue(k);
    gridSizeEdit->blockSignals(false);
    parametersChanged();
  }
}

bool FKToolWidget::checkParameters(FKParameters * param) const
{
  if(param->gridStep()>param->gridSize()) {
    Message::warning(MSG_ID,tr("FK parameters"),tr("Grid step must be less than grid size."), true);
    return false;
  }
  if(param->gridStep()<=0.0) {
    Message::warning(MSG_ID,tr("FK parameters"),tr("Grid step cannot be null or negative."), true);
    return false;
  }
  if(param->gridStep()*3600<param->gridSize()) {
    Message::warning(MSG_ID,tr("FK parameters"),tr("Grid step cannot be more than 3600 times smaller than grid size. "
                                                  "If step*3600==size, the approximate size of a FK map is 100 Mb."), true);
    return false;
  }
  return true;
}

void FKToolWidget::parametersChanged()
{
  if(_fkBrowser && testBut->isChecked()) {
    FKParameters * param=static_cast<FKParameters *>(parameters());
    if(param) {
      _fkBrowser->setParameters(tool()->array(), _currentTestFrequency, param);
      setTimeWindowLayer(&_fkBrowser->timeWindowList());
    }
  }
}

void FKToolWidget::gridStepChanged()
{
  TRACE;
  _gridStepTouched=true;
  parametersChanged();
}

void FKToolWidget::gridSizeChanged()
{
  TRACE;
  // kmin and kmax computations are triggered in restoreFields() before showing this widget
  // Hence, if isWaveNumberGridReady is true, computation is finished.
  if(!tool()->isWaveNumberGridReady()) {
    if(Message::question(MSG_ID, tr("Changing grid size"),
                         tr("The grid size is currently been computed, do you want to "
                            "stop this computation and set your own value?"),
                         Message::yes(), Message::no(), true)==Message::Answer0) {
      _gridSizeTouched=true;
    }
  } else {
    _gridSizeTouched=true;
  }
  parametersChanged();
}

bool FKToolWidget::setSubPool(SubSignalPool * subPool)
{
  TRACE;
  if(AbstractToolWidget::setSubPool(subPool)) {
    timeLimits->setSubPool(subPool);
    /*if(winParam) {
      // Set station names in apply list
      for(int i=0; i<_array.nComponents(); i++) {
        winParam->addComponent(Signal::userName(_array.component(i)));
      }
      for(ArrayStations::iterator it=_array.begin(); it!=_array.end(); ++it) {
        winParam->addStation((*it)->name());
      }
    }*/
    processTypeEdit->blockSignals(true);
    switch (tool()->array()->components()) {
    case StationSignals::AllComponent:
      //processTypeEdit->addItem(tr("Passive Direct Steering"));
      processTypeEdit->addItem(tr("Passive RTBF"));
      processTypeEdit->addItem(tr("Passive RTBF Radial"));
      processTypeEdit->addItem(tr("Passive conventional"));
      //processTypeEdit->addItem(tr("Active RTBF"));
      //processTypeEdit->addItem(tr("Active conventional"));
      break;
    case StationSignals::VerticalComponent:
      processTypeEdit->addItem(tr("Passive HR"));
      processTypeEdit->addItem(tr("Passive conventional"));
      processTypeEdit->addItem(tr("Active HR"));
      processTypeEdit->addItem(tr("Active conventional"));
      break;
    }
    processTypeEdit->blockSignals(false);
    return true;
  } else {
    return false;
  }
}

/*!
  Add all stations to time window layer
*/
void FKToolWidget::setTimeWindowLayer(TimeWindowList * winList)
{
  TRACE;
  if(timeWindowLayer() && _fkBrowser && _fkBrowser->crossSpectrum()) {
    const ArraySelection& array=_fkBrowser->crossSpectrum()->array();
    int nComp=array.array()->nComponents();
    int n=array.count();
    timeWindowLayer()->clearTimeWindows();
    for(int i=0; i<n; i++) {
      for(int iComp=0; iComp<nComp; iComp++ ) {
        const SubSignalPool subPool=array.at(i)->originals(iComp);
        for(SubSignalPool::const_iterator itSig=subPool.begin();itSig!=subPool.end(); itSig++) {
          timeWindowLayer()->addTimeWindows(*itSig, winList);
        }
      }
    }
  }
}

void FKToolWidget::test()
{
  TRACE;
  if(!_fkBrowser) {
    _childrenList[0]=new FKTimeWindows;
    _fkBrowser->setObjectName("FKBrowser");
    _fkBrowser->setTimeWindowLayer(timeWindowLayer());
    GeopsyGuiEngine::instance()->addSubWindow(this, _fkBrowser)->setUserClosable(false);
  }
  QApplication::processEvents();
  parametersChanged();
  GeopsyCoreEngine::instance()->showMessage(tool()->database(), tr("Signals are ready"));
}

void FKToolWidget::start()
{
  TRACE;
  if(subPoolLocked()) {
    return;
  }
  // Get parameters from dialog box
  FKParameters * param=static_cast<FKParameters *>(parameters());
  if(!param) {
    return;
  }
  param->setOutputBaseName(outputFileName());
  if(!param->outputBaseName().isEmpty()) {
    lockSubPool();
    if(tool()->setParameters(*param)) {
      detailedStatus->setLoop(tool()->loop());
      setRunning(true);
      tool()->start();
    } else {
      unlockSubPool();
    }
  }
  delete param;
}

void FKToolWidget::finish()
{
  unlockSubPool();
  FKParameters * param=static_cast<FKParameters *>(parameters());
  tool()->results()->save(param->outputBaseName());
  delete param;
  setRunning(false);
}

void FKToolWidget::stop()
{
  TRACE;
  tool()->stop();
}

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

void FKToolWidget::getParameters(FKParameters& param) const
{
  TRACE;
  param.setProcessType(processType(processTypeEdit->currentIndex()));
  param.setInversionMethod(inversionMethod(inversionMethodEdit->currentIndex()));
  // Store default parameters not accessible through GUI
  param.setDefaultValues(param.processType(), param.inversionMethod(), tool()->array()->mode());

  timeLimits->getParameters(param.timeLimits());
  param.setSelectDurationFactor(0.01*selectMinimumDurationEdit->value());
  winParam->getParameters(param.windowing());

  freqSamp->getParameters(param.frequencySampling());
  blockAveraging->getParameters(param.blockAveraging());

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

  param.setDamping(dampingFactorEdit->value());
  param.setRotateStepCount(rotateStepCountEdit->value());

  param.setInversionMethod(inversionMethod(inversionMethodEdit->currentIndex()));
  param.setMaximumSlowness(1.0/vminEdit->value());
  param.setMinimumSlowness(1.0/vmaxEdit->value());
  param.setGridStep(gridStepEdit->value());
  param.setGridSize(gridSizeEdit->value());
  param.setKmin(tool()->parameters()->kmin()); // Used when setting default gridStep
  param.setKmax(tool()->parameters()->kmax()); // Used when setting default gridSize

  param.setMaximumPeakCount(maxPeakCount->value());
  param.setAbsoluteThreshold(maxAbsoluteThres->value());
  param.setRelativeThreshold(maxRelativeThres->value());

  param.setMinimumDistance(minDistance->value());
  param.setMaximumDistance(maxDistance->value());

  param.setOutputBaseName(outputFileNameEdit->text());
}

void FKToolWidget::setParameters(const AbstractParameters * absparam)
{
  TRACE;
  const FKParameters& param=*static_cast<const FKParameters *>(absparam);
  timeLimits->setParameters(param.timeLimits());
  selectMinimumDurationEdit->setValue(100.0*param.selectDurationFactor());
  winParam->setParameters(param.windowing());

  freqSamp->setParameters(param.frequencySampling());
  blockAveraging->setParameters(param.blockAveraging());

  processTypeEdit->setCurrentIndex(processType(param.processType()));

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

  dampingFactorEdit->setValue(param.damping());
  rotateStepCountEdit->setValue(param.rotateStepCount());

  inversionMethodEdit->blockSignals(true);
  inversionMethodEdit->setCurrentIndex(inversionMethod(param.inversionMethod()));
  inversionMethodEdit->blockSignals(false);
  gridStepEdit->blockSignals(true);
  gridStepEdit->setValue(param.gridStep());
  gridStepEdit->blockSignals(false);
  gridSizeEdit->blockSignals(true);
  gridSizeEdit->setValue(param.gridSize());
  gridSizeEdit->blockSignals(false);
  vminEdit->setValue(1.0/param.maximumSlowness());
  vmaxEdit->setValue(1.0/param.minimumSlowness());

  maxPeakCount->setValue(param.maximumPeakCount());
  maxAbsoluteThres->setValue(param.absoluteThreshold());
  maxRelativeThres->setValue(param.relativeThreshold());

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

  outputFileNameEdit->setText(param.outputBaseName());
}

FKParameters::ProcessType FKToolWidget::processType(int t) const
{
  TRACE;
  switch (tool()->array()->components()) {
  case StationSignals::AllComponent:
    switch(t) {
    case 0:
      return FKParameters::RTBF;
    case 1:
      return FKParameters::RTBFRadial;
    case 2:
      return FKParameters::Conventional;
    //case 5:
    //  return FKParameters::ActiveDirectSteering;
    case 3:
      return FKParameters::ActiveRTBF;
    case 4:
      return FKParameters::ActiveConventional;
    default:
      break;
    }
    break;
  case StationSignals::VerticalComponent:
    switch(t) {
    case 1:
      return FKParameters::Conventional;
    case 2:
      return FKParameters::ActiveRTBF;
    case 3:
      return FKParameters::ActiveConventional;
    default:
      break;
    }
    break;
  }
  return FKParameters::RTBF;
  //return FKParameters::DirectSteering;
}

int FKToolWidget::processType(FKParameters::ProcessType t) const
{
  TRACE;
  switch (tool()->array()->components()) {
  case StationSignals::AllComponent:
    switch(t) {
    case FKParameters::DirectSteering:
    case FKParameters::DirectSteeringRefined:
    case FKParameters::Omni:
    case FKParameters::PoggiRadial:
    case FKParameters::PoggiVertical:
      break;
    case FKParameters::DirectSteeringRadial:
      return 0;
    case FKParameters::DirectSteeringVertical:
      return 0;
    case FKParameters::RTBF:
      return 0;
    case FKParameters::RTBFRadial:
      return 1;
    case FKParameters::Conventional:
      return 2;
    case FKParameters::ActiveDirectSteering:
      return 3;
    case FKParameters::ActiveRTBF:
      return 3;
    case FKParameters::ActiveConventional:
      return 4;
    }
    break;
  case StationSignals::VerticalComponent:
    switch(t) {
    case FKParameters::DirectSteering:
    case FKParameters::DirectSteeringRefined:
    case FKParameters::Omni:
    case FKParameters::PoggiRadial:
    case FKParameters::PoggiVertical:
    case FKParameters::DirectSteeringRadial:
    case FKParameters::DirectSteeringVertical:
    case FKParameters::RTBF:
    case FKParameters::RTBFRadial:
      break;
    case FKParameters::Conventional:
      return 1;
    case FKParameters::ActiveDirectSteering:
      return 2;
    case FKParameters::ActiveRTBF:
      return 2;
    case FKParameters::ActiveConventional:
      return 3;
    }
    break;
  }
  return 0;
}

FKParameters::InversionMethod FKToolWidget::inversionMethod(int m)
{
  TRACE;
  switch(m) {
  case 1:
    return FKParameters::RefinedGrid;
  default:
    break;
  }
  return FKParameters::Gradient;
}

int FKToolWidget::inversionMethod(FKParameters::InversionMethod m)
{
  TRACE;
  switch(m) {
  case FKParameters::Gradient:
    break;
  case FKParameters::RefinedGrid:
    return 1;
  }
  return 0;
}

void FKToolWidget::on_outputFileNameBrowse_clicked()
{
  TRACE;
  QString fileName=Message::getSaveFileName(tr("Output .max file"),
                                            tr("FK max file (*.max)"),
                                            outputFileNameEdit->text());
  if(!fileName.isEmpty()) {
    outputFileNameEdit->setText(fileName);
    _outputFileChecked=true;
  }
}

void FKToolWidget::on_outputFileNameEdit_textChanged(QString)
{
  TRACE;
  _outputFileChecked=false;
}

QString FKToolWidget::outputFileName()
{
  TRACE;
  if(outputFileNameEdit->text().isEmpty()) {
    on_outputFileNameBrowse_clicked();
    if(outputFileNameEdit->text().isEmpty()) {
      return QString();
    }
  }
  if(!_outputFileChecked) {
    QFileInfo fi(outputFileNameEdit->text());
    if(fi.exists()) {
      if(Message::question(MSG_ID, tr("Output .max file"),
                           tr("File '%1' already exist. Do you want to overwrite?").arg(fi.fileName()),
                           Message::no(), Message::yes())==Message::Answer0) {
        return QString();
      }
    }
  }
  return outputFileNameEdit->text();
}

void FKToolWidget::numFreqChanged()
{
  TRACE;
  SamplingParameters param;
  freqSamp->getParameters(param);
  freqScroll->setMaximum(param.count()-1);
}

void FKToolWidget::on_freqScroll_valueChanged(int index)
{
  TRACE;
  SamplingParameters param;
  freqSamp->getParameters(param);
  _currentTestFrequency=param.value(index);
  testFrequency->blockSignals(true);
  testFrequency->setValue(_currentTestFrequency);
  testFrequency->blockSignals(false);
  parametersChanged();
}

void FKToolWidget::on_testFrequency_valueChanged(const QString&)
{
  TRACE;
  _currentTestFrequency=testFrequency->value();
}

void FKToolWidget::setRunning(bool r, const QString& message)
{
  TRACE;
  startBut->setEnabled(!r);
  stopBut->setEnabled(r);
  if(r) {
    if(message.isEmpty()) {
      mainStatus->setText(tr("Running..."));
    } else {
      mainStatus->setText(message);
    }
  } else {
    mainStatus->setText(tr("Not running"));
  }
}

void FKToolWidget::on_processTypeEdit_currentIndexChanged(int index)
{
  TRACE;
  bool threeComp=tool()->array()->components()==StationSignals::AllComponent;
  FKParameters::ProcessType t=processType(index);
  switch(t) {
  case FKParameters::DirectSteering:
  case FKParameters::DirectSteeringRefined:
  case FKParameters::Omni:
  case FKParameters::DirectSteeringRadial:
  case FKParameters::DirectSteeringVertical:
    dampingFactorEdit->setEnabled(true);
    srcRecDistance->setEnabled(false);
    rotateStepCountEdit->setEnabled(false);
    break;
  case FKParameters::RTBF:
  case FKParameters::RTBFRadial:
  case FKParameters::PoggiRadial:
  case FKParameters::PoggiVertical:
    dampingFactorEdit->setEnabled(true);
    rotateStepCountEdit->setEnabled(threeComp);
    srcRecDistance->setEnabled(false);
    break;
  case FKParameters::Conventional:
    dampingFactorEdit->setEnabled(false);
    rotateStepCountEdit->setEnabled(threeComp);
    srcRecDistance->setEnabled(false);
    break;
  case FKParameters::ActiveDirectSteering:
  case FKParameters::ActiveRTBF:
    dampingFactorEdit->setEnabled(true);
    rotateStepCountEdit->setEnabled(false);
    srcRecDistance->setEnabled(true);
    break;
  case FKParameters::ActiveConventional:
    dampingFactorEdit->setEnabled(false);
    rotateStepCountEdit->setEnabled(false);
    srcRecDistance->setEnabled(true);
    break;
  }
  if(Message::question(MSG_ID, tr("Default values"),
                       tr("Do you want to reset to the default values for processing '%1'?")
                       .arg(processTypeEdit->itemText(index)),
                       Message::yes(), Message::no(), true)==Message::Answer0) {
    FKParameters param;
    getParameters(param);
    if(!_gridStepTouched) {
      param.setGridStep(0.0);
    }
    if(!_gridSizeTouched) {
      param.setGridSize(0.0);
    }
    param.setDefaultValues(param.processType(), param.inversionMethod(), tool()->array()->mode());
    processTypeEdit->blockSignals(true);
    setParameters(&param);
    processTypeEdit->blockSignals(false);
  }
  parametersChanged();
}

void FKToolWidget::on_inversionMethodEdit_currentIndexChanged(int)
{
  TRACE;
  setWaveNumberGrid();
  parametersChanged();
}
