/***************************************************************************
**
**  This file is part of geopsy.
**
**  geopsy 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.
**
**  geopsy 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: 2009-03-26
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <GeopsyCore.h>
#include <GeopsyGui.h>

#include "GraphicWindow.h"
#include "WaveformConsole.h"
#include "SubtractValue.h"
#include "SubtractSignals.h"
#include "AGC.h"
#include "Taper.h"
#include "CutSignal.h"
#include "DecimateAmplitude.h"
#include "StaLta.h"
#include "RotateComponents.h"
#include "Correlations.h"
#include "Convolution.h"
#include "InstrumentalResponseWidget.h"

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

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
WaveformConsole::WaveformConsole(QWidget * parent, Qt::WindowFlags f)
    : QWidget(parent, f)
{
  TRACE;
  setupUi(this);
  // Avoid systematic warning, noticed with Qt-5.14 on Catalina
#ifdef Q_OS_MAC
  directCommand->setFontFamily("Courier");
  commandHistory->setFontFamily("Courier");
#else
  directCommand->setFontFamily("Monospace");
  commandHistory->setFontFamily("Monospace");
#endif
  _subPoolWin=0;
  stepBut->setMenu(new QMenu(this));
  runBut->setEnabled(false);
  stepBut->setEnabled(false);
  Settings::splitter(splitter, "WaveformConsole::splitter");
  Settings::getWidget(this, "WaveformConsole", false);
}

/*!
  Description of destructor still missing
*/
WaveformConsole::~WaveformConsole()
{
  TRACE;
  Settings::setSplitter(splitter, "WaveformConsole::splitter");
  commandHistory->clear();
  Settings::setWidget(this, "WaveformConsole", false);
}

void WaveformConsole::setCurrentSubPool(SubPoolWindow * w)
{
  TRACE;
  if(w && !w->tool()) {
    runBut->setEnabled(true);
    stepBut->setEnabled(true);
    if(w->signalProcess()) {
      commandHistory->setPlainText(w->signalProcess()->history());
      setSteps(w->signalProcess()->steps());
    }
  } else {
    commandHistory->clear();
    runBut->setEnabled(false);
    stepBut->setEnabled(false);
  }
  _subPoolWin=w;
}

void WaveformConsole::processUpdate()
{
  SignalProcess * p=_subPoolWin->signalProcess();
  commandHistory->setPlainText(p->history());
  QScrollBar * vBar=commandHistory->verticalScrollBar();
  vBar->setValue(vBar->maximum());
  if(stepBut->menu()->actions().count()!=p->stepCount()) {
    // Steps are never modified, just appended
    setSteps(p->steps());
  }
  _subPoolWin->subPoolUpdate();
}

void WaveformConsole::setSteps(const QStringList& steps)
{
  QMenu * m=stepBut->menu();
  m->clear();
  QAction * a;
  int n=steps.count();
  for(int i=0; i<n; i++) {
    a=m->addAction(steps.at(i), this, SLOT(restoreStep()));
    a->setData(i);
  }
}

void WaveformConsole::on_runBut_clicked()
{
  TRACE;
  if(_subPoolWin) {
    QString script=directCommand->toPlainText();
    if(script.isEmpty()) {
      return;
    }
    SignalProcessScript sps(_subPoolWin->signalProcess());
    sps.run(script);
    processUpdate();
  }
}

void WaveformConsole::on_helpBut_clicked()
{
  TRACE;
  QUrl doc("http://www.geopsy.org/wiki/index.php/Geopsy:Waveform_Console");
  QDesktopServices::openUrl(doc);
}

void WaveformConsole::restoreStep()
{
  TRACE;
  QAction * a=qobject_cast<QAction *>(sender());
  if(!a) return;
  int stepIndex=a->data().toInt();
  _subPoolWin->signalProcess()->restoreStep(stepIndex);
  processUpdate();
}

void WaveformConsole::addActions(QMenu * m)
{
  TRACE;
  QAction * a;

  a=new QAction(tr("&Subtract value"), this);
  a->setObjectName("SubtractValue");
  a->setShortcut(tr("Ctrl+Alt+S"));
  a->setStatusTip(tr("Subtraction by a constant, use for DC removal"));
  connect(a, SIGNAL(triggered()), this, SLOT(subtractValue()));
  m->addAction(a);

  a=new QAction(tr("Remove tre&nd"), this);
  a->setObjectName("RemoveTrend");
  a->setShortcut(tr("Ctrl+Alt+N"));
  a->setStatusTip(tr("Subtraction by a linear regression"));
  connect(a, SIGNAL(triggered()), this, SLOT(removeTrend()));
  m->addAction(a);

  a=new QAction(tr("Subtract signals"), this);
  a->setObjectName("S&ubtractSignals");
  a->setShortcut(tr("Ctrl+Alt+Shift+S"));
  a->setStatusTip(tr("Subtract signals by other signals"));
  connect(a, SIGNAL(triggered()), this, SLOT(subtractSignals()));
  m->addAction(a);

  a=new QAction(tr("Multipl&y"), this);
  a->setObjectName("Multiply");
  a->setShortcut(tr("Ctrl+Alt+Y"));
  a->setStatusTip(tr("Multiply amplitudes by a constant"));
  connect(a, SIGNAL(triggered()), this, SLOT(multiply()));
  m->addAction(a);

  a=new QAction(tr("&Filter"), this);
  a->setObjectName("Filter");
  a->setShortcut(tr("Ctrl+Alt+F"));
  a->setStatusTip(tr("Frequency filters"));
  connect(a, SIGNAL(triggered()), this, SLOT(filter()));
  m->addAction(a);

  a=new QAction(tr("A&GC"), this);
  a->setObjectName("AGC");
  a->setShortcut(tr("Ctrl+Alt+A"));
  a->setStatusTip(tr("Automatic gain control"));
  connect(a, SIGNAL(triggered()), this, SLOT(agc()));
  m->addAction(a);

  a=new QAction(tr("&Whiten"), this);
  a->setObjectName("Whiten");
  a->setShortcut(tr("Ctrl+Alt+W"));
  a->setStatusTip(tr("Whiten spectra"));
  connect(a, SIGNAL(triggered()), this, SLOT(whiten()));
  m->addAction(a);

  a=new QAction(tr("Cl&ip"), this);
  a->setObjectName("Clip");
  a->setShortcut(tr("Ctrl+Alt+L"));
  a->setStatusTip(tr("Clip all events with an amplitude over 3 standard deviations."));
  connect(a, SIGNAL(triggered()), this, SLOT(stddevClip()));
  m->addAction(a);

  a=new QAction(tr("&Phase shift"), this);
  a->setObjectName("Shift");
  a->setShortcut(tr("Ctrl+Alt+P"));
  a->setStatusTip(tr("Shift the signals by a fraction of the sampling period (Fourier interpolation)."));
  connect(a, SIGNAL(triggered()), this, SLOT(shift()));
  m->addAction(a);

  a=new QAction(tr("&Over sample"), this);
  a->setObjectName("OverSample");
  a->setShortcut(tr("Ctrl+Alt+O"));
  a->setStatusTip(tr("Multiply the sampling frequency by any factor greater than 1."));
  connect(a, SIGNAL(triggered()), this, SLOT(overSample()));
  m->addAction(a);

  a=new QAction(tr("&Taper"), this);
  a->setObjectName("Taper");
  a->setShortcut(tr("Ctrl+Alt+T"));
  a->setStatusTip(tr("Apply a taper to all signals of the current viewer (in time domain)"));
  connect(a, SIGNAL(triggered()), this, SLOT(taper()));
  m->addAction(a);

  a=new QAction(tr("&Cut"), this);
  a->setObjectName("Cut");
  a->setShortcut(tr("Ctrl+Alt+C"));
  a->setStatusTip(tr("Cut signals of the current viewer (in time domain)"));
  connect(a, SIGNAL(triggered()), this, SLOT(cut()));
  m->addAction(a);

  a=new QAction(tr("&Merge"), this);
  a->setObjectName("Merge");
  a->setShortcut(tr("Ctrl+Alt+M"));
  a->setStatusTip(tr("Merge signals of the current viewer (in time domain)"));
  connect(a, SIGNAL(triggered()), this, SLOT(merge()));
  m->addAction(a);

  a=new QAction(tr("Decimate amplitu&de"), this);
  a->setObjectName("DecimateAmplitude");
  a->setShortcut(tr("Ctrl+Alt+I"));
  a->setStatusTip(tr("Reduce amplitude sampling, may also binarize signals"));
  connect(a, SIGNAL(triggered()), this, SLOT(decimateAmplitude()));
  m->addAction(a);

  a=new QAction(tr("Decimate tim&e"), this);
  a->setObjectName("DecimateTime");
  a->setShortcut(tr("Ctrl+Alt+E"));
  a->setStatusTip(tr("Reduce frequency sampling (including anti-aliasing filter)"));
  connect(a, SIGNAL(triggered()), this, SLOT(decimateTime()));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("Wavelet transf&orm"), this);
  a->setObjectName("Wavelet");
  a->setStatusTip(tr("Compute the convolution of the signals with the modified Morlet wavelet."));
  connect(a, SIGNAL(triggered()), this, SLOT(waveletTransform()));
  m->addAction(a);

  a=new QAction(tr("STA/&LTA"), this);
  a->setObjectName("STA_LTA");
  a->setStatusTip(tr("Compute the Short Term Average over Long Term Average ratio  (in time domain)"));
  connect(a, SIGNAL(triggered()), this, SLOT(stalta()));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("Rot&ate components"), this);
  a->setObjectName("Rotate");
  a->setStatusTip(tr("Rotate the components of the current viewer"));
  connect(a, SIGNAL(triggered()), this, SLOT(rotate()));
  m->addAction(a);

  a=new QAction(tr("Co&rrelations"), this);
  a->setObjectName("Correlations");
  a->setStatusTip(tr("Compute all possible correlations (all pairs of signals or with one reference signal)."));
  connect(a, SIGNAL(triggered()), this, SLOT(correlations()));
  m->addAction(a);

  a=new QAction(tr("Normali&zed correlations"), this);
  a->setObjectName("NormalizedCorrelations");
  a->setStatusTip(tr("Compute all possible normalized correlations (all pairs of signals or with one reference signal)."));
  connect(a, SIGNAL(triggered()), this, SLOT(normalizedCorrelations()));
  m->addAction(a);

  a=new QAction(tr("Con&volution"), this);
  a->setObjectName("Convolution");
  a->setStatusTip(tr("Compute products of convolution (with one reference signal)."));
  connect(a, SIGNAL(triggered()), this, SLOT(convolution()));
  m->addAction(a);

  a=new QAction(tr("Remove instrumental response"), this);
  a->setObjectName("InstrumentalResponse");
  a->setStatusTip(tr("Remove the instrumental response defined by poles and zeros."));
  connect(a, SIGNAL(triggered()), this, SLOT(removeInstrumentalResponse()));
  m->addAction(a);

  m->addSeparator();

  a=new QAction(tr("Revert to original"), this);
  a->setObjectName("Revert");
  a->setStatusTip(tr("Revert to original signals. To undo one single step use 'Waveform console'."));
  connect(a, SIGNAL(triggered()), this, SLOT(revert()));
  m->addAction(a);
}

void WaveformConsole::removeTrend()
{
  TRACE;
  if(!_subPoolWin) return;
  _subPoolWin->signalProcess()->removeTrend();
  processUpdate();
}

void WaveformConsole::subtractValue()
{
  TRACE;
  if(!_subPoolWin) return;
  SubtractValue * d=new SubtractValue(this);
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    _subPoolWin->signalProcess()->subtractValue(d->meanBut->isChecked() ? 0.0 : d->valEdit->text().toDouble());
    processUpdate();
  }
  delete d;
}

void WaveformConsole::subtractSignals()
{
  TRACE;
  if(!_subPoolWin) return;
  SubtractSignals * d=new SubtractSignals(this);
  Settings::getWidget(d);
  SubSignalPool& subPool=_subPoolWin->subPool();
  d->setSubPool(&subPool);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    if(d->bySignal->isChecked()) {
      _subPoolWin->signalProcess()->subtractSignal(d->signalList->currentIndex());
    } else {
      AbstractSignalGroup * g=d->group();
      if(g) {
        _subPoolWin->signalProcess()->subtractSignals(g->pathName());
      } else {
        Message::warning(MSG_ID, tr("Subtract signals"), tr("No group was selected, nothing subtracted."));
      }
    }
    processUpdate();
  }
  delete d;
}

void WaveformConsole::multiply()
{
  TRACE;
  if(!_subPoolWin) return ;
  bool ok;
  QString text=Settings::getText(QApplication::activeWindow(),
                                 tr("Multiply signals"),
                                 tr("Multiplication factor"),
                                 QLineEdit::Normal, "1.00", &ok);
  if(!ok) return;
  _subPoolWin->signalProcess()->multiply(text.toDouble());
  processUpdate();
}

void WaveformConsole::filter()
{
  TRACE;
  if(!_subPoolWin) return;
  Dialog * d=new Dialog(this);
  d->setWindowTitle(tr("Frequency filter"));
  FilterParameterWidget * dw=new FilterParameterWidget();
  d->setMainWidget(dw);
  Settings::getWidget(d, "FilterParameterWidget");
  dw->updateAllFields();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d, "FilterParameterWidget");
    FilterParameters param;
    dw->getParameters(param);
    _subPoolWin->signalProcess()->filter(param);
    processUpdate();
  }
  delete d;
  return;
}

void WaveformConsole::agc()
{
  TRACE;
  if(!_subPoolWin) return;
  AGC * d=new AGC(this);
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    double w=d->widthEdit->text().toDouble();
    _subPoolWin->signalProcess()->agc(w);
    processUpdate();
  }
  delete d;
}

void WaveformConsole::whiten()
{
  TRACE;
  if(!_subPoolWin) return;
  _subPoolWin->signalProcess()->whiten();
  processUpdate();
}

void WaveformConsole::stddevClip()
{
  TRACE;
  if(!_subPoolWin) return;
  bool ok;
  QString text=Settings::getText(QApplication::activeWindow(),
                                 tr("Clip signals"),
                                 tr("Clip above standard deviation times"),
                                 QLineEdit::Normal, "3.00", &ok);
  double val=Number::toDouble(text, ok);
  if(!ok) return;
  _subPoolWin->signalProcess()->stddevClip(val);
  processUpdate();
}

void WaveformConsole::shift()
{
  TRACE;
  if(!_subPoolWin) return ;
  bool ok;
  QString text=Settings::getText(QApplication::activeWindow(),
                                 tr("Phase shift"),
                                 tr("Time delay (seconds), if positive, the signal is shifted in the past."),
                                 QLineEdit::Normal, "0.001", &ok);
  if(!ok) return;
  double val=Number::toDouble(text, ok);
  _subPoolWin->signalProcess()->shift(val);
  processUpdate();
}

void WaveformConsole::overSample()
{
  TRACE;
  if(!_subPoolWin) return ;
  bool ok;
  QString text=Settings::getText(QApplication::activeWindow(),
                                 tr("Over sample"),
                                 tr("Sampling frequency factor (greater than 1)"),
                                 QLineEdit::Normal, "2.00", &ok);
  if(!ok) return;
  double factor=Number::toDouble(text, ok);
  if(!ok || factor <=1) {
    Message::warning(MSG_ID, tr("Over sample"), tr("Sampling factor must be greater than 1"));
    return;
  }
  _subPoolWin->signalProcess()->overSample(factor);
  processUpdate();
}

void WaveformConsole::taper()
{
  TRACE;
  if(!_subPoolWin) return ;
  Taper * d=new Taper(this);
  Settings::getWidget(d);
  d->windowShape->updateAllFields();
  d->timeLimits->setSubPool(&_subPoolWin->subPool());
  d->timeLimits->updateAllFields();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    TimeRangeParameters tlparam;
    d->timeLimits->getParameters(tlparam);
    WindowFunctionParameters taperparam;
    d->windowShape->getParameters(taperparam);
    _subPoolWin->signalProcess()->taper(tlparam, taperparam);
    processUpdate();
  }
  delete d;
}

void WaveformConsole::cut()
{
  TRACE;
  if(!_subPoolWin) return ;
  CutSignal * d=new CutSignal(this);
  d->timeLimits->setSubPool(&_subPoolWin->subPool());
  Settings::getWidget(d);
  d->timeLimits->updateAllFields();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    TimeRangeParameters tlparam;
    d->timeLimits->getParameters(tlparam);
    _subPoolWin->signalProcess()->cut(tlparam);
    processUpdate();
  }
  delete d;
}

void WaveformConsole::merge()
{
  TRACE;
  MessageContext mc;
  if(!_subPoolWin) return ;
  switch (Message::question(MSG_ID, tr("Merging signals"),
                                 tr("Do you want to merge signals by station and component?"),
                                 Message::yes(), Message::no(),
                                 Message::cancel())) {
  case Message::Answer0:
    _subPoolWin->signalProcess()->mergeStations();
    processUpdate();
    break;
  case Message::Answer1:
    _subPoolWin->signalProcess()->merge();
    processUpdate();
    break;
  default:
    break;
  }
}

void WaveformConsole::decimateAmplitude()
{
  TRACE;
  if(!_subPoolWin) return ;
  DecimateAmplitude * d=new DecimateAmplitude(this);
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    double maxRef;
    if(d->maxType->currentIndex()==0)
      maxRef=d->maxRef->text().toDouble();
    else
      maxRef=-1;
    _subPoolWin->signalProcess()->decimateAmplitude(d->maxCount->text().toInt(), maxRef);
    processUpdate();
  }
  delete d;
}

void WaveformConsole::decimateTime()
{
  TRACE;
  if(!_subPoolWin) return ;
  bool ok;
  QString text=Settings::getText(QApplication::activeWindow(),
                                      tr("Decimate time"),
                                      tr("Decimation factor (integer greater than 1, anti-aliasing filter included)"),
                                      QLineEdit::Normal, "2", &ok);
  if(!ok) return;
  int nSamples=text.toInt(&ok);
  if(!ok || nSamples<=1) {
    Message::warning(MSG_ID, tr("Decimate time"), tr("Decimation factor must be an integer greater than 1"));
    return;
  }
  _subPoolWin->signalProcess()->decimateTime(nSamples);
  processUpdate();
}

void WaveformConsole::waveletTransform()
{
  TRACE;
  if(!_subPoolWin) return;
  Dialog * d=new Dialog(this);
  d->setWindowTitle(tr("Wavelet transform"));
  MorletParameterWidget * dw=new MorletParameterWidget;
  d->setMainWidget(dw);
  Settings::getWidget(d, "MorletParamWidget");
  dw->updateAllFields();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d, "MorletParamWidget");
    MorletParameters param;
    dw->getParameters(param);
    _subPoolWin->signalProcess()->waveletTransform(param);
    processUpdate();
  }
  delete d;
}

void WaveformConsole::stalta()
{
  TRACE;
  if(!_subPoolWin) return ;
  StaLta * d=new StaLta(this);
  Settings::getWidget(d);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    _subPoolWin->signalProcess()->stalta(d->staLength->value(), d->ltaLength->value());
    GraphicWindow * w=qobject_cast<GraphicWindow *>(_subPoolWin);
    if(w) {
      w->signalLayer()->setOffset(SignalLayer::NoOffset);
    }
    processUpdate();
  }
  delete d;
}


void WaveformConsole::rotate()
{
  TRACE;
  if(!_subPoolWin) return;
  if(!_subPoolWin->subPool().associate3Components()) return;
  _subPoolWin->subPoolUpdate();
  RotateComponents * d=new RotateComponents(this);
  Settings::getWidget(d);
  d->updateAll();
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    _subPoolWin->signalProcess()->rotateComponents(d->parameters());
    processUpdate();
  }
  delete d;
}

void WaveformConsole::correlations()
{
  TRACE;
  if(!_subPoolWin) return;
  Correlations * d=new Correlations(this);
  Settings::getWidget(d);
  SubSignalPool& subPool=_subPoolWin->subPool();
  d->setSubPool(&subPool);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    double tw=d->maximumDelay->text().toDouble();
    if(d->referenceSignal->isChecked()) {
      _subPoolWin->signalProcess()->correlations(tw, d->signalList->currentIndex());
    } else {
      _subPoolWin->signalProcess()->correlations(tw);
    }
    processUpdate();
  }
  delete d;
}

void WaveformConsole::normalizedCorrelations()
{
  TRACE;
  if(!_subPoolWin) return;
  Correlations * d=new Correlations(this);
  Settings::getWidget(d);
  SubSignalPool& subPool=_subPoolWin->subPool();
  d->setSubPool(&subPool);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    double tw=d->maximumDelay->text().toDouble();
    if(d->referenceSignal->isChecked()) {
      _subPoolWin->signalProcess()->normalizedCorrelations(tw, d->signalList->currentIndex());
    } else {
      _subPoolWin->signalProcess()->normalizedCorrelations(tw);
    }
    processUpdate();
  }
  delete d;
}

void WaveformConsole::convolution()
{
  TRACE;
  if(!_subPoolWin) return;
  Convolution * d=new Convolution(this);
  Settings::getWidget(d);
  SubSignalPool& subPool=_subPoolWin->subPool();
  d->setSubPool(&subPool);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    _subPoolWin->signalProcess()->convolution(d->signalIndex());
    processUpdate();
  }
  delete d;
}

void WaveformConsole::removeInstrumentalResponse()
{
  TRACE;
  if(!_subPoolWin) return;
  InstrumentalResponseWidget * d=new InstrumentalResponseWidget(this);
  d->setWindowModality(Qt::WindowModal);
  if(d->exec()==QDialog::Accepted) {
    Settings::setWidget(d);
    _subPoolWin->signalProcess()->removeInstrumentalResponse(d->response());
    processUpdate();
  }
  delete d;
}

void WaveformConsole::revert()
{
  TRACE;
  if(!_subPoolWin) return;
  _subPoolWin->signalProcess()->restoreStep(0);
  processUpdate();
}
