/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore 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.
**
**  GeopsyCore 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 <QJSEngine>

#include "SignalProcess.h"
#include "SignalDatabase.h"
#include "TimePick.h"
#include "Comments.h"

namespace GeopsyCore {

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

    Full description of class still missing
  */

  SignalProcess::SignalProcess(QObject * parent)
    : QObject(parent)
  {
  }

  SignalProcess::~SignalProcess()
  {
  }

  void SignalProcess::setCurrentSubPool(SubSignalPool * subPool)
  {
    TRACE;
    _steps.clear();
    _current=subPool;
  }

  int SignalProcess::indexOfStep(const QString& tag) const
  {
    TRACE;
    int n=_steps.count();
    for(int i=0; i<n; i++ ) {
      if(_steps.at(i).name()==tag) {
        return i;
      }
    }
    return -1;
  }

  QStringList SignalProcess::steps() const
  {
    TRACE;
    QStringList l;
    int n=_steps.count();
    for(int i=0; i<n; i++ ) {
      l.append(_steps.at(i).name());
    }
    return l;
  }

  void SignalProcess::saveStep(const QString& tag)
  {
    TRACE;
    _steps.append(*_current);
    _steps.last().setName(tag);
  }

  void SignalProcess::newStep()
  {
    TRACE;
    if(_steps.isEmpty()) {
      saveStep(tr("original"));
    }
    *_current=_current->copy();
  }

  void SignalProcess::restoreStep(int index)
  {
    TRACE;
    if(index < 0 || index >= _steps.count()) return;
    restoreStepCore(index);
    addToHistory(QString("restoreStep(\"%1\");\n").arg(_steps.at(index).name()) );
  }

  void SignalProcess::clear()
  {
    TRACE;
    _history.clear();
    _steps.clear();
  }

  void SignalProcess::addToHistory(const QString& s)
  {
    _history+=s;
    // Save history to comments
    for(SubSignalPool::iterator it=_current->begin(); it!=_current->end(); it++) {
      Signal * sig=*it;
      Comments& c=sig->beginModifyMetaData<Comments>();
      c+=s;
      sig->endModifyMetaData(c);
    }
  }

  void SignalProcess::fastFourierTransform(DoubleSignal::SignalType st)
  {
    TRACE;
    fastFourierTransformCore(st);
    addToHistory(QString("fastFourierTransform(\"%1\");\n")
                .arg(DoubleSignal::typeString(st) ));
  }

  bool SignalProcess::exportFile(const QString& filePath, bool useOriginalBaseName,
                                 const SignalFileFormat& format, int maximumSignalsPerFile,
                                 const QString& pickName)
  {
    TRACE;
    bool ret=exportFileCore(filePath, useOriginalBaseName, format, maximumSignalsPerFile, pickName);
    addToHistory(QString("export(\"%1\", %2, \"%3\", %4);\n")
                .arg(filePath)
                .arg(useOriginalBaseName ? "true" : "false")
                .arg(format.name())
                .arg(pickName));
    return ret;
  }

  void SignalProcess::setHeader(int signalIndex, const MetaDataIndex& dataIndex, const QVariant& value)
  {
    TRACE;
    setHeaderCore(signalIndex, dataIndex, value);
    addToHistory(QString("setHeader(%1, \"%2\", %3);\n")
                .arg(signalIndex)
                .arg(MetaDataFactory::instance()->name(dataIndex))
                .arg(value.toString()));
  }

  void SignalProcess::removeTrend()
  {
    TRACE;
    newStep();
    removeTrendCore();
    static const QString fun("removeTrend()");
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::subtractValue(double val)
  {
    TRACE;
    newStep();
    subtractValueCore(val);
    static const QString funStruct("subtractValue(%1)");
    QString fun=funStruct.arg(val);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::subtractSignal(int index)
  {
    TRACE;
    newStep();
    subtractSignalCore(index);
    static const QString funStruct("subtractSignal(%1)");
    QString fun=funStruct.arg(index);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::subtractSignals(const QString& groupName)
  {
    TRACE;
    newStep();
    subtractSignalsCore(groupName);
    static const QString funStruct("subtractSignals(\"%1\")");
    QString fun=funStruct.arg(groupName);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::multiply(double val)
  {
    TRACE;
    newStep();
    multiplyCore(val);
    static const QString funStruct("multiply(%1)");
    QString fun=funStruct.arg(val);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  /*!
    \todo Add arguments for convolution window
  */
  void SignalProcess::filter(const FilterParameters& param)
  {
    TRACE;
    newStep();
    filterCore(param);
    static const QString funStruct("filter(\"%1\", \"%2\", %3, %4, %5)");
    QString fun=funStruct.arg(param.methodString())
                         .arg(param.bandString())
                         .arg(param.minimumFrequency())
                         .arg(param.maximumFrequency());
    switch(param.method()) {
    case FilterParameters::Butterworth:
      fun=fun.arg(param.order());
      break;
    case FilterParameters::FrequencyWindow:
      fun=fun.arg(param.width());
      break;
    }
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::whiten()
  {
    TRACE;
    newStep();
    whitenCore();
    static const QString fun("whiten()");
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::agc(double width)
  {
    TRACE;
    newStep();
    agcCore(width);
    static const QString funStruct("agc(%1)");
    QString fun=funStruct.arg(width);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::stddevClip(double factor)
  {
    TRACE;
    newStep();
    stddevClipCore(factor);
    static const QString funStruct("stddevClip(%1)");
    QString fun=funStruct.arg(factor);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::shift(double dt)
  {
    TRACE;
    newStep();
    shiftCore(dt);
    static const QString funStruct("shift(%1)");
    QString fun=funStruct.arg(dt);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::overSample(double factor)
  {
    TRACE;
    newStep();
    overSampleCore(factor);
    static const QString funStruct("overSample(%1)");
    QString fun=funStruct.arg(factor);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  /*!

  */
  void SignalProcess::taper(TimeRangeParameters& range, const WindowFunctionParameters& param)
  {
    TRACE;
    newStep();
    taperCore(range, param);
    static const QString funStruct("taper(%1)");
    QString arg="\""+param.toShortString()+"\"";
    switch(param.shape()) {
    case WindowFunctionParameters::Tukey:
    case WindowFunctionParameters::Blackman:
    case WindowFunctionParameters::Gaussian:
      arg+=", "+QString::number(param.alpha());
      break;
    default:
      break;
    }
    arg+=", \""+range.toShortString()+"\"";
    QString fun=funStruct.arg(arg);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::cut(TimeRangeParameters& param)
  {
    TRACE;
    newStep();
    cutCore(param);
    static const QString funStruct("cut(%1)");
    QString fun=funStruct.arg("\""+param.toShortString()+"\"");
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  bool SignalProcess::merge()
  {
    TRACE;
    newStep();
    bool ret=mergeCore();
    static const QString fun("merge()");
    saveStep(fun);
    addToHistory(fun+";\n");
    return ret;
  }

  bool SignalProcess::mergeStations()
  {
    TRACE;
    newStep();
    bool ret=mergeStationsCore();
    static const QString fun("mergeStations()");
    saveStep(fun);
    addToHistory(fun+";\n");
    return ret;
  }

  void SignalProcess::decimateAmplitude(int maxCount, double maxRef)
  {
    TRACE;
    newStep();
    decimateAmplitudeCore(maxCount, maxRef);
    static const QString funStruct("decimateAmplitude(%1, %2)");
    QString fun=funStruct.arg(maxCount)
                           .arg(maxRef);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::decimateTime(int factor)
  {
    TRACE;
    newStep();
    decimateTimeCore(factor);
    static const QString funStruct("decimateTime(%1)");
    QString fun=funStruct.arg(factor);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::waveletTransform(const MorletParameters& param)
  {
    TRACE;
    newStep();
    waveletTransformCore(param);
    static const QString funStruct("waveletTransform(%1, %2)");
    QString fun=funStruct.arg(param.m())
                           .arg(param.fi());
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::stalta(double tsta, double tlta)
  {
    TRACE;
    newStep();
    staltaCore(tsta, tlta);
    static const QString funStruct("stalta(%1, %2)");
    QString fun=funStruct.arg(tsta)
                           .arg(tlta);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::discreteFourierTransform()
  {
    TRACE;
    newStep();
    discreteFourierTransformCore();
    static const QString fun("discreteFourierTransform()");
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::rotateComponents(const RotateParameters& param)
  {
    TRACE;
    newStep();
    rotateComponentsCore(param);
    static const QString funStruct("rotateComponents(%1, %2, %3, %4, %5, %6)");
    QString fun=funStruct.arg(param.axes() & RotateParameters::Vertical ? "true" : "false" )
                           .arg(param.angleAroundVertical())
                           .arg(param.axes() & RotateParameters::North ? "true" : "false" )
                           .arg(param.angleAroundNorth())
                           .arg(param.axes() & RotateParameters::East ? "true" : "false" )
                           .arg(param.angleAroundEast());
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::correlations(double maxDelay, int referenceIndex)
  {
    TRACE;
    newStep();
    correlationsCore(maxDelay, referenceIndex);
    static const QString funStruct("correlations(%1, %2)");
    QString fun=funStruct.arg(maxDelay).arg(referenceIndex);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::normalizedCorrelations(double maxDelay, int referenceIndex)
  {
    TRACE;
    newStep();
    normalizedCorrelationsCore(maxDelay, referenceIndex);
    static const QString funStruct("normalizedCorrelations(%1, %2)");
    QString fun=funStruct.arg(maxDelay).arg(referenceIndex);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::convolution(int referenceIndex)
  {
    TRACE;
    newStep();
    convolutionCore(referenceIndex);
    static const QString funStruct("convolution(%1)");
    QString fun=funStruct.arg(referenceIndex);
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::removeInstrumentalResponse(const InstrumentalResponse& sensor)
  {
    newStep();
    removeInstrumentalResponseCore(sensor);
    static const QString funStruct("removeInstrumentalResponse(%1)");
    QString fun=funStruct.arg(sensor.arguments());
    saveStep(fun);
    addToHistory(fun+";\n");
  }

  void SignalProcess::restoreStepCore(int index)
  {
    if(index < 0 || index >= _steps.count()) return;
    QString name=_current->name();
    *_current=_steps.at(index).copy();
    _current->setName(name);
  }

  void SignalProcess::fastFourierTransformCore(DoubleSignal::SignalType st)
  {
    _current->fastFourierTransform(st);
  }

  bool SignalProcess::exportFileCore(const QString& filePath, bool useOriginalBaseName,
                                     const SignalFileFormat& format, int maximumSignalsPerFile,
                                     const QString& pickName)
  {
    return _current->save(filePath, useOriginalBaseName, format, maximumSignalsPerFile, pickName);
  }

  QVariant SignalProcess::header(int signalIndex, const MetaDataIndex& dataIndex)
  {
    if(signalIndex<_current->count()) {
      if(signalIndex<0) {
        return _current->first()->header(dataIndex);
      } else {
        return _current->at(signalIndex)->header(dataIndex);
      }
    } else {
      return QVariant();
    }
  }

  void SignalProcess::setHeaderCore(int signalIndex, const MetaDataIndex& dataIndex, const QVariant& value)
  {
    if(signalIndex<0) {
      for(SubSignalPool::iterator it=_current->begin(); it!=_current->end(); it++) {
        (*it)->setHeader(dataIndex, value);
      }
    } else if(signalIndex<_current->count()) {
      _current->at(signalIndex)->setHeader(dataIndex, value);
    }
  }

  int SignalProcess::signalCount()
  {
    return _current->count();
  }

  void SignalProcess::selectSignals(int startIndex, int endIndex, QString tag)
  {
    int n=_current->count();
    if(startIndex<0 || startIndex>=n) {
      startIndex=0;
    }
    if(endIndex<0 || endIndex>=n) {
      endIndex=n-1;
    }
    if(startIndex>0 || endIndex<n-1) {
      if(tag.isEmpty()) {
        tag="selected from "+QString::number(startIndex)+" to "+QString::number(endIndex);
      }
      SubSignalPool newCurrent;
      for(int i=startIndex; i<=endIndex; i++) {
        newCurrent.addSignal(_current->at(i));
      }
      _steps.append(newCurrent);
      _steps.last().setName(tag);
      *_current=newCurrent;
    }
  }

  void SignalProcess::removeTrendCore()
  {
    _current->removeTrend();
  }

  void SignalProcess::subtractValueCore(double val)
  {
    _current->subtractValue(val);
  }

  void SignalProcess::subtractSignalCore(int index)
  {
    _current->subtractSignal(_current->at(index));
  }

  void SignalProcess::subtractSignalsCore(const QString& groupName)
  {
    // _current is never an empty SubSignalPool, hence db is always non null
    SignalDatabase * db=_current->database();
    const AbstractSignalGroup * g=db->findGroup(groupName);
    if(!g) {
      App::log(tr("Unknown group %1\n").arg(groupName) );
      return;
    }
    SubSignalPool bySubPool;
    bySubPool.addGroup(g);
    _current->subtractSignals(&bySubPool);
  }

  void SignalProcess::multiplyCore(double val)
  {
    _current->multiply(val);
  }

  void SignalProcess::filterCore(const FilterParameters& param)
  {
    _current->filter(param);
  }

  void SignalProcess::agcCore(double width)
  {
    _current->agc(width);
  }

  void SignalProcess::whitenCore()
  {
    _current->whiten();
  }

  void SignalProcess::stddevClipCore(double factor)
  {
    _current->stddevClip(factor);
  }

  void SignalProcess::shiftCore(double dt)
  {
    _current->shift(dt);
  }

  void SignalProcess::overSampleCore(double factor)
  {
    _current->overSample(factor);
  }

  void SignalProcess::taperCore(TimeRangeParameters& range, const WindowFunctionParameters& param)
  {
    _current->taper(range, param);
  }

  void SignalProcess::cutCore(TimeRangeParameters& param)
  {
    _current->cut(param);
  }

  bool SignalProcess::mergeCore()
  {
    return _current->merge();
  }

  bool SignalProcess::mergeStationsCore()
  {
    return _current->mergeStations();
  }

  void SignalProcess::decimateAmplitudeCore(int maxCount, double maxRef)
  {
    _current->decimateAmplitude(maxCount, maxRef);
  }

  void SignalProcess::decimateTimeCore(int factor)
  {
    _current->decimateTime(factor);
  }

  void SignalProcess::waveletTransformCore(const MorletParameters& param)
  {
    _current->waveletTransform(param);
  }

  void SignalProcess::staltaCore(double tsta, double tlta)
  {
    _current->stalta(tsta, tlta);
  }

  void SignalProcess::discreteFourierTransformCore()
  {
    _current->discreteFourierTransform();
  }

  void SignalProcess::rotateComponentsCore(const RotateParameters& param)
  {
    Matrix3x3 rotMatrix=param.matrix();

    App::log(tr("Rotation matrix:\n")+rotMatrix.toUserString());
    _current->rotateComponents(rotMatrix);
  }

  void SignalProcess::correlationsCore(double maxDelay, int referenceIndex)
  {
    if(referenceIndex>-1) {
      _current->correlations(maxDelay, _current->at(referenceIndex));
    } else {
      _current->correlations(maxDelay);
    }
  }

  void SignalProcess::normalizedCorrelationsCore(double maxDelay, int referenceIndex)
  {
    if(referenceIndex>-1) {
      _current->normalizedCorrelations(maxDelay, _current->at(referenceIndex));
    } else {
      _current->normalizedCorrelations(maxDelay);
    }
  }

  void SignalProcess::convolutionCore(int referenceIndex)
  {
    _current->convolution(_current->at(referenceIndex));
  }

  void SignalProcess::removeInstrumentalResponseCore(const InstrumentalResponse& sensor)
  {
    _current->removeInstrumentalResponse(sensor);
  }

} // namespace GeopsyCore
