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

#ifndef DOUBLESIGNAL_H
#define DOUBLESIGNAL_H

#include "GeopsyCoreDLLExport.h"
#include "SignalTemplate.h"
#include "ComplexSignal.h"
#include "WindowingParameters.h"
#include "InstrumentalResponse.h"
#include "FourierPlan.h"

namespace GeopsyCore {

class KeepSignal;
class MorletParameters;

class GEOPSYCORE_EXPORT DoubleSignal: public SignalTemplate<double>
{
   TRANSLATIONS("DoubleSignal")
public:
  enum SignalType {Waveform, Spectrum, Phase, UndefinedSignalType};

  DoubleSignal();
  DoubleSignal(int n);
  DoubleSignal(const DoubleSignal& o);

  void lockProperties() const {_propertiesMutex.lockForWrite();}
  void constLockProperties() const {_propertiesMutex.lockForRead();}
  void unlockProperies() const {_propertiesMutex.unlock();}

  // Properties
  double samplingPeriod() const {return _samplingPeriod;}
  virtual void setSamplingPeriod(double newval) {_samplingPeriod=newval;}
  double samplingFrequency() const {return 1.0/_samplingPeriod;}
  double duration() const {return _samplingPeriod*_nSamples;}
  inline virtual void setNSamples(int n);
  SignalType type() const {return _type;}
  void setType(SignalType t) {_type=t;}
  inline bool isReal() const;
  inline bool isComplex() const;
  inline bool isSpectrum() const;
  void copyBasicProperties(const DoubleSignal& o);
  int nyquistIndex() const {return _nyquistIndex;}

  static SignalType type(QString t, bool * ok);
  static QString typeString(SignalType st);

  inline void copySamplesFrom(const DoubleSignal * sig);
  bool copySamplesFrom(const DoubleSignal * sig, double sigStart, double thisStart, double tToCopy);
  bool copySamplesFrom(const DoubleSignal * sig, int isigStart, int ithisStart, int nToCopy);
  Curve<Point2D> spectrumAmplitudeCurve() const;
  int add(const DoubleSignal * sig, double sigStart, double thisStart,
          double tToCopy, bool checkInvalid=false, double invalidValue=std::numeric_limits<double>::infinity());
  int add(const DoubleSignal * sig);

  // Funtion to perform signal processing
  void fastFourierTransform(SignalType st);

  SignalType saveType(SignalType st);
  void restoreType(SignalType st);
  const DoubleSignal * saveType(SignalType st) const;
  void restoreType(const DoubleSignal * sig) const;

  bool subtractValue(double val=0.0);
  bool filter(const FilterParameters& param);
  bool whiten();
  bool stddevClip(double factor);
  bool agc(double width);
  bool taper(double start, double end, const WindowFunctionParameters& param);
  bool taper(const WindowFunctionParameters& param);
  bool abs();
  bool square();
  bool sqrt();
  bool decimateAmplitude(double delta);
  bool shift(double dt);
  bool unglitch(double threshold);
  bool setValue(double val);
  bool removeTrend();
  bool multiply(double value) {return SignalTemplate<double>::multiply(value);}
  bool multiply(const Complex& value);

  bool discreteFourierTransform();
  Complex discreteFourierTransform(const FourierPlan& plan) const;
  bool overSample(double factor, const DoubleSignal * sig);
  bool decimateTime(int factor, const DoubleSignal * sig);

  ComplexSignal * morletWavelet(const MorletParameters& param) const;
  ComplexSignal * positiveComplexSpectrum(bool fullSize=false) const;
  ComplexSignal * complexSpectrum() const;

  void stalta(double tsta, double tlta, DoubleSignal * ratio) const;
  void staltaToKeep(const WindowingParameters::ShortLongTermAverage& param, KeepSignal * keep, double delay) const;
  void clipMaxToKeep(double value, KeepSignal * keep, double delay) const;
  bool correlation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay);
  bool normalizedCorrelation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay);
  bool timeCorrelation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay);
  double correlation(const DoubleSignal * sig, double sigStart, double thisStart, double length) const;
  bool convolution(const DoubleSignal * s1, const DoubleSignal * s2);
  bool removeInstrumentalResponse(const InstrumentalResponse& sensor);

  double amplitudeAt(double abscissa) const;
  double interpoleAt(double abscissa) const;
  double maximumAmplitude(int itmin, int itmax) const;
  int maximumAmplitudeAt(int itmin, int itmax) const;
  int maximumAt(int itmin, int itmax) const;
  double averageAmplitude(int itmin, int itmax) const;
  double energy(double minF, double maxF) const;
  double power(double excludeBorderRatio=0.0) const;

  Complex complex(const double * samples, int i) const;
  void setComplex(double * samples, int i, const Complex& value) const;
  double amplitude2(const double * samples, int i) const;
  double amplitude(const double * samples, int i) const;
  void setAmplitude(double * samples, int i, double val) const;
  inline double phase(const double * samples, int i) const;
  inline double re(const double * samples, int i) const;
  inline double im(const double * samples, int i) const;
  inline void setRe(double * samples, int i, double value) const;
  inline void setIm(double * samples, int i, double value) const;

  void debugPrint() const;
  void debugSave(const QString& fileName) const;
protected:
  mutable ReadWriteLock _propertiesMutex;
  double _samplingPeriod;
  SignalType _type;
  int _nyquistIndex;
private:
  bool correlationCheck(const DoubleSignal * s1, const DoubleSignal * s2, double& maxDelay);
  bool correlationProcess(const DoubleSignal * s1, const DoubleSignal * s2,
                          DoubleSignal * s1t, DoubleSignal * s2t);
  bool correlationNormalization(const DoubleSignal * s, DoubleSignal * norm) const;

  static void filb3(double fc, int ak, double pas, double *a, double *b);
  static void frecur(int la, double *a, double *xa, int lb, double *b, double *yb,
                      int ndeb, int nfin, int npas, int isens, double *x, double *y);
  static void filrec(double fc, int type, int ndeb, int nfin, int npas, double *x,
                      double *y, int nfil, double pas);
  int commonSamples(double delay, int otherNSamples, int& thisStart, int& otherStart) const;
};

inline void DoubleSignal::setNSamples(int n)
{
  TRACE;
  SignalTemplate<double>::setNSamples(n);
  _nyquistIndex=_nSamples >> 1;
}

inline double DoubleSignal::re(const double * samples, int i) const
{
  TRACE;
  ASSERT(i>=0 && i<=_nyquistIndex);
  return samples[i];
}

inline double DoubleSignal::im(const double * samples, int i) const
{
  TRACE;
  if(i==0) {
    return 0.0;
  } else if(i<_nyquistIndex) {
  } else if(i==_nyquistIndex) {
    if(_nSamples & 0x00000001) {
      return samples[_nSamples-i];
    } else {
      return 0.0;
    }
  } else {
    ASSERT(false);
  }
  return samples[_nSamples-i];
}

inline void DoubleSignal::setRe(double * samples, int i, double value) const
{
  TRACE;
  ASSERT(i>=0 && i<=_nyquistIndex);
  samples[i]=value;
}

inline void DoubleSignal::setIm(double * samples, int i, double value) const
{
  TRACE;
  if((i>0 && i<_nyquistIndex) ||
     (i==_nyquistIndex && _nSamples & 0x00000001)) {
    samples[_nSamples-i]=value;
  }
  // Not allowed to change the value for all other cases
}

inline double DoubleSignal::amplitude(const double * samples, int i) const
{
  return ::sqrt(amplitude2(samples, i));
}

inline double DoubleSignal::phase(const double * samples, int i) const
{
  return complex(samples, i).phase();
}

inline bool DoubleSignal::isComplex() const
{
  TRACE;
  switch (_type) {
  case Spectrum:
  case Phase:
    return true;
  default:
    return false;
  }
}

inline bool DoubleSignal::isReal() const
{
  TRACE;
  switch (_type) {
  case Waveform:
    return true;
  default:
    return false;
  }
}

inline bool DoubleSignal::isSpectrum() const
{
  TRACE;
  switch (_type) {
  case Spectrum:
  case Phase:
    return true;
  default:
    return false;
  }
}

inline void DoubleSignal::copySamplesFrom(const DoubleSignal * sig)
{
  SignalTemplate<double>::copySamplesFrom(sig);
}

} // namespace GeopsyCore

#endif // DOUBLESIGNAL_H
