/***************************************************************************
**
**  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)
**
***************************************************************************/

#include <QGpCoreMath.h>

#include "DoubleSignal.h"
#include "MorletParameters.h"
#include "KeepSignal.h"
#include "FastFourierTransform.h"
#include "FastPartialFourierTransform.h"
#include "TaperDelegate.h"
#include "FFTWCache.h"
#include "SubSignalPool.h"

namespace GeopsyCore {

/*!
  \class DoubleSignal DoubleSignal.h
  \brief DoubleSignal is a vector of double floating points with a sampling frequency, and a a type

  DoubleSignal has only three additional public data members compared to SignalTemplate<double>:

  \li sampling period (samplingPeriod())
  \li type: Waveform,Spectrum,Phase
  \li [internal] the last spectrum index if the number of samples is even

  fft can be applied to this object as well as all other basic signal processings.
*/

/*!
  \fn double DoubleSignal::re(const double * samples, int i) const

  Retruns the real part of sample with index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  Format of inplace r2c fft:

  r0, r1, r2, ..., r(n/2), i((n+1)/2-1), ..., i2, i1

  Format of r2c fft:

  c0, c1, c2, ..., c(n/2-1), c(n/2), c(n/2-1)*, ..., c2*, c1*

  Index \a i can range from 0 to n. Positive frequencies are stored from
  0 to n/2, negative frequencies from n/2+1 (lowest) to n (highest frequency
  closest to 0 Hz).

  \sa im(), complex(), amplitude2(), amplitude(), phase()
*/

/*!
  \fn double DoubleSignal::im(const double * samples, int i) const

  Retruns the imaginary part of sample with index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  \sa re(), complex(), amplitude2(), amplitude(), phase()
*/

/*!
  \fn Complex DoubleSignal::complex(const double * samples, int i) const

  Retruns the complex sample with index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  \sa re(), im(), amplitude2(), amplitude(), phase()
*/

/*!
  \fn double DoubleSignal::amplitude2(const double * samples, int i) const

  Returns the squared amplitude for frequency index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  \sa re(), im(), complex(), amplitude(), phase()
*/

/*!
  \fn double DoubleSignal::amplitude(const double * samples, int i) const

  Returns the amplitude for frequency index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  \sa re(), im(), complex(), amplitude2(), phase()
*/

/*!
  \fn DoubleSignal::phase(const double * samples, int i) const

  Returns the phase for frequency index \a i.
  \a samples must be a pointer to signal samples locked with
  LOCK_SAMPLES or CONST_LOCK_SAMPLES.

  \sa re(), im(), complex(), amplitude2(), amplitude()
*/

/*!
  Construct a null signal (null sampling frequency, null number of samples).
*/
DoubleSignal::DoubleSignal()
{
  TRACE;
  _type=Waveform;
  _samplingPeriod=0.0;
  _nyquistIndex=0; // Set as an odd number of samples
}

/*!
  Construct a signal with \a n samples (null sampling frequency).
*/
DoubleSignal::DoubleSignal(int n) :
    SignalTemplate<double>(n)
{
  TRACE;
  _type=Waveform;
  _samplingPeriod=0.0;
  _nyquistIndex=_nSamples >> 1;
}

/*!
  Construct a a copy of signal \a o.
*/
DoubleSignal::DoubleSignal(const DoubleSignal& o) :
    SignalTemplate<double>(o)
{
  TRACE;
  _nyquistIndex=_nSamples >> 1;
  copyBasicProperties(o);
}

void DoubleSignal::copyBasicProperties(const DoubleSignal& o)
{
  TRACE;
  _samplingPeriod=o._samplingPeriod;
  _type=o._type;
}

/*!
  Subtract value \a val from all samples. If \a val is null, the average of all samples is subtracted (DC removal).
*/
bool DoubleSignal::subtractValue(double val)
{
  TRACE;
  if(val==0.0) val=average(0, _nSamples);
  bool ret=false;
  if(val!= 0.0) {
    LOCK_SAMPLES(double, thisSamples, this)
      SignalType ost=saveType(Waveform);
      for(int i=0; i < _nSamples; i++ )
        thisSamples[ i ] -= val;
      restoreType(ost);
      ret=true;
    UNLOCK_SAMPLES(this)
  }
  return ret;
}

/*!
 Butterworth filtering (internal only)
*/
void DoubleSignal::filb3(double fc, int ak, double pas, double *a, double *b)
{
  TRACE;
  double wa, wa2, wa3, dw;
  double S1, S2, S3;

  S1=2.0;
  S2=2.0;
  S3=1.0;

  wa=tan(M_PI * fc * pas);
  if( !ak)
    return ;
  if(ak < 0)
    wa=1.0/wa;
  wa2=wa * wa;
  wa3=wa2 * wa;
  dw=S3 + S2 * wa + S1 * wa2 + wa3;
  a[ 1 ]=wa3/dw;
  a[ 2 ]=3. * ak * a[ 1 ];
  a[ 3 ]=3. * a[ 1 ];
  a[ 4 ]=ak * a[ 1 ];
  b[ 1 ]=1.;
  b[ 2 ]=ak * ( -3 * S3 - S2 * wa + S1 * wa2 + 3. * wa3)/dw;
  b[ 3 ]=(3. * S3 - S2 * wa - S1 * wa2 + 3. * wa3)/dw;
  b[ 4 ]=ak * ( -S3 + S2 * wa - S1 * wa2 + wa3)/dw;
}


/*!
  Butterworth filtering (internal only)
*/
void DoubleSignal::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)
{
  TRACE;
  int ipas, ip, k, j, i;
  double sa, sb;

  ipas=npas * isens;
  if(isens >= 0)
    ip=ndeb;
  else
    ip=nfin;
  for(k=ndeb; k <= nfin; k += npas) {
    sa=0.;
    sb=0.;
    if(la!=1) {
      for(j=2; j <= la; j++ ) {
        i=la - j + 1;
        sa=sa + a[ i + 1 ] * xa[ i ];
        xa[ i + 1 ]=xa[ i ];
      }
      xa[ 1 ]=x[ ip ];
    }
    if(lb!=1) {
      for(j=2; j <= lb; j++ ) {
        i=lb - j + 1;
        sb=sb + b[ i + 1 ] * yb[ i ];
        yb[ i + 1 ]=yb[ i ];
      }
    }
    y[ ip ]=a[ 1 ] * x[ ip ] + sa - sb;
    if(lb!=1)
      yb[ 1 ]=y[ ip ];
    ip=ip + ipas;
  }
}


/*!
 Butterworth filtering (internal only)

   \li \a x=input signal (sampling step: pas)
   \li \a y=output signal (filters between samples \a ndeb and \a nfin with step \a npas)
   \li \a fc= filter frequency (high-pass or low-pass)
   \li \a type=-1. for high-pass, +1. for low-pass 
   \li \a nfil=order and type of filtering:

   \li abs(\a nfil)=order of Butterworth filtre
   \li if \a nfil is negative, the filter is done in both directions, and it is non-causal but non-outphasing
   \li if \a nfil is positive, ,the filter is done in only one direction, and it is causal but outphasing
*/
void DoubleSignal::filrec(double fc, int type, int ndeb, int nfin, int npas,
                          double *x, double *y, int nfil, double pas)
{
  TRACE;
  double a[ 5 ], b[ 5 ], xa[ 5 ], yb[ 5 ];
  int mpas, jf1, j, i, jf, mfil, k;

  if(fc < 0. )
    return ;
  mpas=npas;
  if(type > 0)
    mpas=1;
  filb3(fc, type, pas * mpas, a, b);
  if(nfil > 0)
    jf1=1;
  else
    jf1=2;
  mfil=(( nfil > 0) ? nfil : -nfil);
  for(j=1; j <= mfil; j++ ) {
    for(jf=1; jf <= jf1; jf++ ) {
      for(i=1; i <= 4; i++ ) {
        xa[ i ]=0.;
        yb[ i ]=0.;
      }
      k=j;
      if(jf==2)
        k=3;
      switch (k) {
      case 1:
        frecur(4, a, xa, 4, b, yb, ndeb, nfin, mpas, 1, x, y);
        break;
      case 2:
        frecur(4, a, xa, 4, b, yb, ndeb, nfin, mpas, 1, y, y);
        break;
      default:
        frecur(4, a, xa, 4, b, yb, ndeb, nfin, mpas, -1, y, y);
        break;
      }
    }
  }
}

/*!
  Butterworth and convolution filters

  Return false if not processed due to memory problem
*/
bool DoubleSignal::filter(const FilterParameters& param)
{
  TRACE;
  bool ret=true;
  switch(param.method()) {
  case FilterParameters::Butterworth: {
      SignalType ost=saveType(Waveform);
      LOCK_SAMPLES(double, thisSamples, this)
        switch (param.band()) {
        case FilterParameters::BandPass:
          filrec(param.maximumFrequency(), 1, 0, _nSamples-1, 1, thisSamples, thisSamples, param.order(), _samplingPeriod);
          filrec(param.minimumFrequency(), -1, 0, _nSamples-1, 1, thisSamples, thisSamples, param.order(), _samplingPeriod);
          break;
        case FilterParameters::BandReject: {
            double * s=new double [_nSamples];
            int i;
            for(i=0; i<_nSamples; i++) {
              s[i]=thisSamples[i];
            }
            filrec(param.minimumFrequency(), 1, 0, _nSamples-1, 1, thisSamples, thisSamples, param.order(), _samplingPeriod);
            filrec(param.maximumFrequency(), -1, 0, _nSamples-1, 1, s, s, param.order(), _samplingPeriod);
            for(i=0; i<_nSamples; i++) {
              thisSamples[ i ]+=s[i];
            }
            delete [] s;
          }
          break;
        case FilterParameters::LowPass:
          filrec(param.minimumFrequency(), 1, 0, _nSamples-1, 1, thisSamples, thisSamples, param.order(), _samplingPeriod);
          break;
        case FilterParameters::HighPass:
          filrec(param.maximumFrequency(), -1, 0, _nSamples-1, 1, thisSamples, thisSamples, param.order(), _samplingPeriod);
          break;
        }
      UNLOCK_SAMPLES(this) else ret=false;
      restoreType(ost);
    }
    break;
  case FilterParameters::FrequencyWindow: {
      SignalType st=saveType(Spectrum);
      double fac=_samplingPeriod*static_cast<double>(_nSamples);
      LOCK_SAMPLES(double, thisSamples, this)
        TaperDelegate::ComplexSample samples(_nSamples, thisSamples);
        TaperDelegate window(&param.frequencyWindow());
        switch (param.band()) {
        case FilterParameters::LowPass:
          window.apply(samples,
                       qRound(fac*param.minimumFrequency()*(1.0-param.width())),
                       _nyquistIndex,
                       qRound(fac*param.minimumFrequency()*(1.0-2.0*param.width())),
                       qRound(fac*param.minimumFrequency()));
          break;
        case FilterParameters::HighPass: {
            int i=qRound(fac*param.minimumFrequency()*(1.0+1.0*param.width()));
            if(i>_nyquistIndex) {
              i=_nyquistIndex;
            }
            window.apply(samples, 0, i,
                         qRound(fac*param.minimumFrequency()),
                         qRound(fac*param.minimumFrequency()*(1.0+2.0*param.width())));
          }
          break;
        case FilterParameters::BandPass:
        case FilterParameters::BandReject:
          window.apply(samples, 0, _nyquistIndex,
                       qRound(fac*param.minimumFrequency()),
                       qRound(fac*param.maximumFrequency()));
          break;
        }
      UNLOCK_SAMPLES(this) else ret=false;
      restoreType(st);
    }
    break;
  }
  return ret;
}

/*!
  Return the convolution of this signal with Morlet Wavelet in freshly allocated complex signal 

  Input:
   \li w0 factor > 5.5 to has an admissible wavelet
   \li fmin, central frequency of filter (s=w0/(2*pi*fmin))
*/
ComplexSignal * DoubleSignal::morletWavelet(const MorletParameters& param) const
{
  TRACE;
  // Number of samples that describe the spectrum for positive part
  int n=_nSamples >> 1;
  ComplexSignal * csig=positiveComplexSpectrum(true);
  LOCK_SAMPLES(Complex, csamples, csig)
    // Make convolution of this complex spectrum with the Morlet function
    // defined only for the positive frequencies.
    //
    // Morlet Wavelet:
    //      Modified Morlet wavelet=1/pow(pi,0.25) * exp (-(f*s - w0)^2*m)
    //      Common value for m is 10
    // s=w0/param.fmin
    // However for efficiency: s=param.w0/param.fmin*df (see computation of a in loop)
    double df=1.0/(_samplingPeriod *static_cast<double>(_nSamples));
    double s=6.0*df/param.fi();
    double c=pow(M_PI, -0.25);
    csamples[0]=0.0; // DC component
    for(int i=1;i<=n; i++) { // 0 frequency is already set to 0
      double a=s*i-6.0;
      a=a*a;
      if(a<700) {
        a=c*exp(-a*param.m());
        csamples[i]*= a;
      } else {
        csamples[i]=0.0;
      }
    }
    // Negative frequency: convolution gives null contribution
    for(int i=_nSamples-1;i>n; i--) {
      csamples[i]=0.0;
    }
    // Back transform to time domain
    FastFourierTransform::instance()->backward(csig->nSamples(), csamples);
  UNLOCK_SAMPLES(csig)
  return csig;
}

/*!
  Return the complex spectrum for positive frequencies.
  If \a fullSize is true, reserve a complex signal with full number of
  samples (both positive and negative frequency) but only positive
  frequencies are initialized.

  (from FFTW documentation))
  For those who like to think in terms of positive and negative frequencies, this means that the positive frequencies
  are stored in the first half of the output and the negative frequencies are stored in backwards order in the second
  half of the output. (The frequency -k/n is the same as the frequency (n-k)/n.)

  \sa re()
*/
ComplexSignal * DoubleSignal::positiveComplexSpectrum(bool fullSize) const
{
  TRACE;
  int n=_nSamples >> 1;
  // Copying to a full complex representation of the signal excluding negative frequencies
  ComplexSignal * csig=new ComplexSignal(fullSize ? _nSamples : n+1);
  LOCK_SAMPLES(Complex, csamples, csig)
    // Assign values to the complex signal spectra from the real2complex representation
    const DoubleSignal * sig=saveType(Spectrum);
    CONST_LOCK_SAMPLES(double, sigSamples, sig)
      csamples[0]=sigSamples[ 0 ];
      for(int i=1; i<n; i++) {
        csamples[i].setRe(sigSamples[i]);
        csamples[i].setIm(sigSamples[_nSamples-i]);
      }
      if(_nSamples & 0x00000001) {
        csamples[n].setRe(sigSamples[n]);
        csamples[n].setIm(sigSamples[_nSamples-n]);
      } else {
        csamples[n]=sigSamples[ n ];
      }
    UNLOCK_SAMPLES(sig)
    restoreType(sig);
  UNLOCK_SAMPLES(csig)
  return csig;
}

/*!
  Return the complex spectrum for positive and negative frequencies of a real signal.

  (from FFTW documentation))
  For those who like to think in terms of positive and negative frequencies, this means that the positive frequencies
  are stored in the first half of the output and the negative frequencies are stored in backwards order in the second
  half of the output. (The frequency -k/n is the same as the frequency (n-k)/n.)

  \sa re()
*/
ComplexSignal * DoubleSignal::complexSpectrum() const
{
  TRACE;
  int n=_nSamples >> 1;
  // Copying to a full complex representation of the signal
  ComplexSignal * csig=new ComplexSignal(_nSamples);
  LOCK_SAMPLES(Complex, csamples, csig)
    // Assign values to the complex signal spectra from the real2complex representation
    const DoubleSignal * sig=saveType(Spectrum);
    CONST_LOCK_SAMPLES(double, sigSamples, sig)
      csamples[0]=sigSamples[ 0 ];
      for(int i=1; i<n; i++) {
        csamples[i].setRe(sigSamples[i]);              // positive frequency
        csamples[i].setIm(sigSamples[_nSamples-i]);
        csamples[_nSamples-i]=conjugate(csamples[i]);  // negative frequency
      }
      if(_nSamples & 0x00000001) {
        csamples[n].setRe(sigSamples[n]);
        csamples[n].setIm(sigSamples[_nSamples-n]);
      } else {
        csamples[n]=sigSamples[n];
      }
    UNLOCK_SAMPLES(sig)
    restoreType(sig);
  UNLOCK_SAMPLES(csig)
  return csig;
}

/*!
  Shift the signal by \a dt seconds. It may be any number even not a multiple of the sampling period.
  Better if \a dt is not greater than the sampling period. If it is greater prefer a shift based on sample index.
  This shift is rather slow because it requires two fft transforms to and from frequency domain.

  A positive \a dt shifts the signal towards the left (or the past).
*/
bool DoubleSignal::shift(double dt)
{
  TRACE;
  bool ret=false;
  SignalType st=saveType(Spectrum);
  LOCK_SAMPLES(double, thisSamples, this)
    int n=_nSamples >> 1;
    double domega=2*M_PI/duration()*dt;
    for(int i=1; i<=n; i++) {
      Complex c(thisSamples[i], thisSamples[_nSamples-i]);
      Complex dphi(cos(i*domega), sin(i*domega));
      c*=dphi;
      thisSamples[i]=c.re();
      thisSamples[_nSamples-i]=c.im();
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  restoreType(st);
  return ret;
}

/*!
  Automatic Gain Control

  width is in seconds, it is the half width of the time window interval used to
  calculate the energy level.

  Return false if not processed due to memory problem
*/
bool DoubleSignal::agc(double width)
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    SignalType st=saveType(Waveform);
    int iWidth=(int) floor(width/_samplingPeriod + 0.5);
    double sum=0.0, val, nwin;
    int i;
    // Large windows (larger than signal length)
    if(2 * iWidth + 1 >= _nSamples) {
      for(i=0; i < _nSamples; ++i) {
        val=thisSamples[ i ];
        sum += val * val;
      }
      if(sum > 0) {
        sum=::sqrt(sum);
        for(i=0; i < _nSamples; ++i) {
          thisSamples[ i ] /= sum;
        }
      }
    } else {
      double * agcSig=new double [ _nSamples ];
      // compute initial window for first sample (only on the right side)
      for(i=0; i < iWidth + 1; ++i) {
        val=thisSamples[ i ];
        sum += val * val;
      }
      nwin=2.0 * iWidth + 1.0;
      agcSig[ 0 ]=(sum <= 0.0) ? 0.0 : thisSamples[ 0 ]/::sqrt(sum/nwin);

      // Next samples, increasing left side of the window
      for(i=1; i <= iWidth; ++i) {
        val=thisSamples[ i + iWidth ];
        sum += val * val;
        ++nwin;
        agcSig[ i ]=(sum <= 0.0) ? 0.0 : thisSamples[ i ]/::sqrt(sum/nwin);
      }

      // Full window available (2*width+1)
      int n=_nSamples - 1 - iWidth;
      for(i=iWidth + 1; i <= n; ++i) {
        val=thisSamples[ i + iWidth ];
        sum += val * val;
        val=thisSamples[ i - iWidth ];
        sum -= val * val;
        agcSig[ i ]=(sum <= 0.0) ? 0.0 : thisSamples[ i ]/::sqrt(sum/nwin);
      }

      // Decreasing right side of the window
      for(i=_nSamples - iWidth; i < _nSamples; ++i) {
        val=thisSamples[ i - iWidth ];
        sum -= val * val;
        --nwin;
        agcSig[ i ]=(sum <= 0.0) ? 0.0 : thisSamples[ i ]/::sqrt(sum/nwin);
      }

      // Copying back to original signal
      memcpy(thisSamples, agcSig, _nSamples * sizeof(double) );
      delete [] agcSig;
    }

    restoreType(st);
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Whiten the spectrum
*/
bool DoubleSignal::whiten()
{
  TRACE;
  bool ret=false;
  SignalType st=saveType(Spectrum); // make sure we are in frequency domain
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0; i<_nyquistIndex; i++) {
      setAmplitude(thisSamples, i, 1.0);
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  restoreType(st);
  return ret;
}

/*!
  Clip signal exceeding \a factor times standard deviation. Re-compute standard deviation and clip again until
  no more clipping is required. The DC is first removed.
*/
bool DoubleSignal::stddevClip(double factor)
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    SignalType st=saveType(Waveform); // make sure we are in time domain
    double sum=average(0, _nSamples)*_nSamples;
    double sum2=average2(0, _nSamples)*(_nSamples-1);
    double invNSamples=1.0/_nSamples;
    double factor2=factor*factor;
    double varianceFactor=factor2*(sum2-sum*invNSamples*sum)/(_nSamples-1);
    double stddevFactor=::sqrt(varianceFactor);
    double newStddevFactor;
    for(int ir=0; ir<10; ir++) {
      for(int i=0; i<_nSamples; i++) {
        if(thisSamples[i]>stddevFactor) {
          sum+=stddevFactor-thisSamples[i];
          sum2+=varianceFactor-thisSamples[i]*thisSamples[i];
          thisSamples[i]=stddevFactor;
        } else if(thisSamples[i]<-stddevFactor) {
          sum+=-stddevFactor-thisSamples[i];
          sum2+=varianceFactor-thisSamples[i]*thisSamples[i];
          thisSamples[i]=-stddevFactor;
        }
      }
      if(_nSamples<2) {
        varianceFactor=0.0;
      } else {
        varianceFactor=factor2 *(sum2-sum*invNSamples*sum)/(_nSamples-1);
        if(varianceFactor<0.0) varianceFactor=0.0;
      }
      newStddevFactor=::sqrt(varianceFactor);
      if(newStddevFactor>0.9*stddevFactor) {
        break;
      }
      stddevFactor=newStddevFactor;
    }
    if(newStddevFactor<0.9*stddevFactor) {
      App::log(tr("Convergence criterium not reached (less than 10% variantion of stddev).\n") );
    }
    restoreType(st);
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Applies a taper window to the signal over the range \a t.

  Returns false if not processed due to memory problem.
*/
bool DoubleSignal::taper(double start, double end, const WindowFunctionParameters& param)
{
  TRACE;
  bool ret=false;
  SignalType st=saveType(Waveform);
  LOCK_SAMPLES(double, thisSamples, this)
    TaperDelegate::RealSample samples(thisSamples);
    TaperDelegate window(&param);
    if(end-start>samplingPeriod()) {
      window.apply(samples, start, end, samplingFrequency(), _nSamples);
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  restoreType(st);
  return ret;
}

/*!
  Applies a taper window on the complete signal.

  Returns false if not processed due to memory problem.
*/
bool DoubleSignal::taper(const WindowFunctionParameters& param)
{
  TRACE;
  bool ret=false;
  SignalType st=saveType(Waveform);
  LOCK_SAMPLES(double, thisSamples, this)
    TaperDelegate::RealSample samples(thisSamples);
    TaperDelegate window(&param);
    window.apply(samples, 0, _nSamples-1, 0, _nSamples-1);
    ret=true;
  UNLOCK_SAMPLES(this)
  restoreType(st);
  return ret;
}

/*!
  Set \a value to all samples.
*/
bool DoubleSignal::setValue(double val)
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0; i<_nSamples; i++) {
      thisSamples[i]=val;
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Decimate along amplitude scale. \a delta is the amplitude step used to discretize amplitudes.
*/
bool DoubleSignal::decimateAmplitude(double delta)
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    SignalType st=saveType(Waveform); // make sure we are in time domain
    double invDelta=1.0/delta;
    for(int i=0; i<_nSamples; i++) {
      if(thisSamples[i]>0)
        thisSamples[i]=ceil(thisSamples[i] * invDelta) * delta;
      else
        thisSamples[i]=floor(thisSamples[i] * invDelta) * delta;
    }
    restoreType(st);
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Transform type into \a st
*/
DoubleSignal::SignalType DoubleSignal::saveType(SignalType st)
{
  TRACE;
  SignalType oldSt=_type;
  fastFourierTransform(st);
  return oldSt;
}

/*!
  Return a copy of this signal with \a st signal type or this if this signal has already type \a st.
*/
const DoubleSignal * DoubleSignal::saveType(SignalType st) const
{
  TRACE;
  if(_type==st) {
    return this;
  } else {
    DoubleSignal * sig=new DoubleSignal(*this);
    sig->copySamplesFrom(this);
    sig->fastFourierTransform(st);
    return sig;
  }
}

/*!
  Transform to initial type

  \sa saveType()
*/
void DoubleSignal::restoreType(SignalType st)
{
  TRACE;
  fastFourierTransform(st);
}

/*!
  Delete \a sig if it is not equal to this. This must be used with saveType() in the same way as for non const signals.
*/
void DoubleSignal::restoreType(const DoubleSignal * sig) const
{
  if(sig!=this) {
    delete sig;
  }
}

/*!
  A r2r kind of FFTW_R2HC (r2hc) corresponds to an r2c DFT (see One-Dimensional
  DFTs of Real Data) but with "halfcomplex" format output, and may sometimes be
  faster and/or more convenient than the latter. The inverse hc2r transform is
  of kind FFTW_HC2R. This consists of the non-redundant half of the complex
  output for a 1d real-input DFT of size n, stored as a sequence of n real
  numbers (double) in the format:

  r0, r1, r2, ..., rn/2, i(n+1)/2-1, ..., i2, i1

  For instance if n==4: r0, r1, r2, i1
                  n==5: r0, r1, r2, i1, i2

  Here, rk is the real part of the kth output, and ik is the imaginary part.
  (Division by 2 is rounded down.) For a halfcomplex array hc[n], the kth
  component thus has its real part in hc[k] and its imaginary part in hc[n-k],
  with the exception of k==0 or n/2 (the latter only if n is even)--in these
  two cases, the imaginary part is zero due to symmetries of the real-input DFT,
  and is not stored. Thus, the r2hc transform of n real values is a halfcomplex
  array of length n, and vice versa for hc2r.

  Normalization factor: see McNamara 2004
*/
void DoubleSignal::fastFourierTransform(SignalType st)
{
  TRACE;
  if(isComplex()) {
    switch (st) {
    case Waveform: {
        LOCK_SAMPLES(double, thisSamples, this)
          const FFTWCache * cache=FFTWCache::begin(_nSamples, FFTWKey::HC2R);
          cache->execute(thisSamples);
          FFTWCache::end(cache);
          //FastFourierTransform::instance()->backward(_nSamples, thisSamples);
          // Factor according to McNamara
          // Stable for various sampling frequencis
          // Stable for various signal durations
#ifdef COMPATIBILITY_2_5_0
          double rn=_samplingPeriod;
#else
          double rn=1.0/::sqrt(2.0*_samplingPeriod*static_cast<double>(_nSamples));
#endif
          for(int i=0; i<_nSamples; i++) {
            thisSamples[i]*=rn;
          }
        UNLOCK_SAMPLES(this)
        _type=st;
      }
      break;
    case Spectrum:
      if(_type==Phase) {
        _type=Spectrum;
      }
      break;
    case Phase:
      if(_type==Spectrum) {
        _type=Phase;
      }
      break;
    case UndefinedSignalType:
      break;
    }
  } else if(isReal()) {
    switch (st) {
    case Waveform:
    case UndefinedSignalType:
      break;
    case Spectrum:
    case Phase: {
        LOCK_SAMPLES(double, thisSamples, this)
          const FFTWCache * cache=FFTWCache::begin(_nSamples, FFTWKey::R2HC);
          cache->execute(thisSamples);
          FFTWCache::end(cache);
          // Factor according to McNamara
          // Stable for various sampling frequencis
          // Stable for various signal durations
#ifdef COMPATIBILITY_2_5_0
          double rn=1.0/(static_cast<double>(_nSamples)*_samplingPeriod);
#else
          double rn=::sqrt(2*_samplingPeriod/static_cast<double>(_nSamples));
#endif
          for(int i=0; i<_nSamples; i++) {
            thisSamples[i]*=rn;
          }
        UNLOCK_SAMPLES(this)
        _type=st;
        break;
      }
    }
  }
}

Complex DoubleSignal::discreteFourierTransform(const FourierPlan& plan) const
{
  ASSERT(plan.count()==nSamples());
  Complex c;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    c=plan.dtft(thisSamples);
  UNLOCK_SAMPLES(this)
  return c;
}

/*
   Discrete fourier transform calculed with the explicit technique
   Slow but correct. Mainly used to validate other faster techniques.
   It assumes signal is a waveform (real)
*/
bool DoubleSignal::discreteFourierTransform()
{
  long i,k;
  double arg;
  double cosarg,sinarg;

  int n=nSamples();
  double * x2=new double[n];
  double * y2=new double[n];

  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)

  for(i=0;i<n;i++) {
    x2[i]=0;
    y2[i]=0;
    arg=- 2.0 * M_PI * (double)i/(double)n;
    for(k=0;k<n;k++) {
      cosarg=cos(k * arg);
      sinarg=sin(k * arg);
      x2[i] += (thisSamples[k] * cosarg);
      y2[i] += (thisSamples[k] * sinarg);
    }
  }

  // Compute spectrum amplitude
  // From 0 to n/2 : positive frequencies
  // From n/2 to n-1: negative frequencies in reverse order (antisymetric for real input signal
  int n2=n >> 1;
  double rn=1.0/(_nSamples*_samplingPeriod);
  for(i=0;i<=n2;i++) {
    thisSamples[i]=x2[i]*rn;
  }
  for(i=n2+1;i<n;i++) {
    thisSamples[i]=y2[n-i]*rn;
  }
  ret=true;
  UNLOCK_SAMPLES(this);
  setType(Spectrum);

  delete [] x2;
  delete [] y2;
  return ret;
}

/*!
  Divide the sampling period by \a factor, equivalent to zero padding on the complex spectrum.
  \a sig is the reference signal. The results are stored in this signal. This signal must not be allocated.
  This object can be created with any constructor.

  Organisation of samples:

  r0, r1, r2, ..., rn/2, i(n+1)/2-1, ..., i2, i1

  Definition of evenNyquistIndex:

  if( !(n & 0x00000001) )
    _evenNyquistIndex=_nSamples >> 1;
  else
    _evenNyquistIndex=0; // Set as an odd number of samples

  Its use to get the complex spectrum:

  if(i==0 || i==_evenNyquistIndex) return samples[ i ];
  else return Complex(samples[ i ], samples[ _nSamples - i ] );
*/
bool DoubleSignal::overSample(double factor, const DoubleSignal * sig)
{
  TRACE;
  if(factor<=1.0) {
    App::log(tr("DoubleSignal::overSample: factor cannot be less than 1, aborting.\n") );
    return false;
  }
  if(isAllocated()) {
    App::log(tr("DoubleSignal::overSample: samples of destination signal cannot be allocated, aborting.\n") );
    return false;
  }
  int nOriginalSpectrum=sig->_nSamples >> 1;
  int nResampled=qRound(sig->_nSamples*factor);
  setNSamples(nResampled);
  setSamplingPeriod(sig->samplingPeriod()/factor);

  bool ret=false;
  const DoubleSignal * sigfft=sig->saveType(Spectrum);
  CONST_LOCK_SAMPLES(double, sigSamples, sigfft)
    LOCK_SAMPLES(double, thisSamples, this)
      thisSamples[0]=sigSamples[0];
      for(int i=1; i<nOriginalSpectrum; i++) {
        thisSamples[i]=sigSamples[i];
        thisSamples[_nSamples-i]=sigSamples[sig->_nSamples -i];
      }
      if(_nSamples & 0x00000001) {
        thisSamples[nOriginalSpectrum]=0.0;
      } else {
        thisSamples[nOriginalSpectrum]=sigSamples[sig->_nyquistIndex];
      }
      int n=_nSamples-nOriginalSpectrum;
      for(int i=nOriginalSpectrum+1; i<=n; i++) {
        thisSamples[i]=0.0;
      }
      setType(Spectrum);
      fastFourierTransform(sig->type());
      ret=true;
    UNLOCK_SAMPLES(this);
  UNLOCK_SAMPLES(sigfft)
  sig->restoreType(sigfft);
  return ret;
}

/*!
  Decimate \a sig along time scale. The result is set to this. An adequat anti-aliasing filter is used
  before any processing. One sample over \a factor is kept.

  \a sig is the reference signal. The results are stored in this signal. This signal must not be allocated.
*/
bool DoubleSignal::decimateTime(int factor, const DoubleSignal * sig)
{
  TRACE;
  if(factor<=1) {
    App::log(tr("DoubleSignal::decimateTime: factor cannot be less than 1, aborting.\n") );
    return false;
  }
  if(isAllocated()) {
    App::log(tr("DoubleSignal::decimateTime: samples of destination signal cannot be allocated, aborting.\n") );
    return false;
  }
  int nResampled=(int) floor(sig->nSamples()/(double)factor);
  setNSamples(nResampled);
  setSamplingPeriod(factor*samplingPeriod());

  // Anti aliasing filter, need to make a copy signal
  DoubleSignal * filtsig=new DoubleSignal(*sig);
  filtsig->copySamplesFrom(sig);
  // Use an anti-aliasing filter: Low Pass with cut-off frequency set to 0.8*Nyquist frequency
  FilterParameters param;
  param.setFrequencyRange(0.5/_samplingPeriod);
  param.setWidth(0.2);
  param.setBand(FilterParameters::LowPass);
  param.setMethod(FilterParameters::FrequencyWindow);
  if( !filtsig->filter(param) ) {
    delete filtsig;
    return false;
  }
  filtsig->fastFourierTransform(Waveform);
  bool ret=false;
  CacheProcess cp;
  cp << filtsig << this;
  LOCK_SAMPLES(double, sigSamples, filtsig)
    LOCK_SAMPLES(double, thisSamples, this)
      for(int i=0; i<_nSamples; i++) {
        thisSamples[i]=sigSamples[i*factor];
      }
      ret=true;
    UNLOCK_SAMPLES (this)
  UNLOCK_SAMPLES(filtsig)
  delete filtsig;
  return ret;
}

bool DoubleSignal::correlationCheck(const DoubleSignal * s1, const DoubleSignal * s2, double& maxDelay)
{
  TRACE;
  if(s1->samplingPeriod()<=0) {
    App::log(tr("DoubleSignal::correlation: null samplingPeriod, aborting.\n") );
    return false;
  }
  if(s1->samplingPeriod()!=s2->samplingPeriod()) {
    App::log(tr("DoubleSignal::correlation: samplingPeriod must be equal, aborting.\n") );
    return false;
  }
  if(maxDelay<=0) {
    App::log(tr("DoubleSignal::correlation: negative delay, aborted\n") );
    return false;
  }
  if(maxDelay>=s1->duration()) {
    double newMaxDelay=s1->duration()-s1->samplingPeriod();
    App::log(tr("correlation: maximum delay set to %1 s instead of %2\n").arg(newMaxDelay).arg(maxDelay) );
    maxDelay=newMaxDelay;
  }
  if(maxDelay>=s2->duration()) {
    double newMaxDelay=s2->duration()-s2->samplingPeriod();
    App::log(tr("correlation: maximum delay set to %1 s instead of %2\n").arg(newMaxDelay).arg(maxDelay) );
    maxDelay=newMaxDelay;
  }
  return true;
}

/*!
  Signals \a s1t and \a s2t must be allocated with a copy constructor from \a s1 and \a s2.
  The sampling frequencies must be the same for \a s1 and \a s2 (checked by
  correlationCheck().
*/
bool DoubleSignal::correlationProcess(const DoubleSignal * s1, const DoubleSignal * s2,
                                      DoubleSignal * s1t, DoubleSignal * s2t)
{
  TRACE;
  int n1=s1->nSamples();
  int n2=s2->nSamples();
  CacheProcess cp;
  cp << s1t << s2t;
  // Removes offset from both signals
  WindowFunctionParameters param;
  param.setShape(WindowFunctionParameters::Tukey);
  param.setAlpha(0.02);

  s1t->setNSamples(n1);
  s1t->copySamplesFrom(s1, 0, 0, n1);
  s1t->subtractValue();
  s1t->taper(param);

  s2t->setNSamples(n2);
  s2t->copySamplesFrom(s2, 0, 0, n2);
  s2t->subtractValue();
  s2t->taper(param);

  // Calculate optimum number of samples for correlation (power of 2)
  int n=Number::nextPowerOfTwo(n1+n2-1);

  // 0 padding for first signal
  DoubleSignal spec1(*s1t);
  cp << &spec1;
  spec1.setNSamples(n);
  spec1.copySamplesFrom(s1t, 0, 0, n1);
  spec1.initValues(0.0, n1, n-n1);

  // 0 padding for second signal
  DoubleSignal spec2(*s2t);
  cp << &spec2;
  spec2.setNSamples(n);
  int i=n1-1;
  spec2.initValues(0.0, 0, i);
  spec2.copySamplesFrom(s2t, 0, i, n2);
  i+=n2;
  spec2.initValues(0.0, i, n-i);

  cp.next();
  cp.next();

  DoubleSignal conv(n);
  conv.setType(Spectrum);
  conv.setSamplingPeriod(s1->samplingPeriod());
  cp << &conv << this;

  bool ret=false;
  LOCK_SAMPLES(double, convSamples, (&conv))
    LOCK_SAMPLES(double, s1Samples, (&spec1))
      LOCK_SAMPLES(double, s2Samples, (&spec2))
        const FFTWCache * cache=FFTWCache::begin(n, FFTWKey::R2HC);
        cache->execute(s1Samples);
        cache->execute(s2Samples);
        FFTWCache::end(cache);
        //FastFourierTransform::instance()->forward(n, s1Samples);
        //FastFourierTransform::instance()->forward(n, s2Samples);
        convSamples[0]=s1Samples[0]*s2Samples[0];
        Complex c1, c2;
        for(int i=1; i<conv._nyquistIndex; i++) {
          c1.set(s1Samples[i], -s1Samples[n-i]);
          c2.set(s2Samples[i], s2Samples[n-i]);
          c1*=c2;
          convSamples[i]=c1.re();
          convSamples[n-i]=c1.im();
        }
        if(!(conv._nSamples & 0x00000001)) {
          convSamples[conv._nyquistIndex]=s1Samples[conv._nyquistIndex]*s2Samples[conv._nyquistIndex];
        }
        ret=true;
      UNLOCK_SAMPLES((&spec2))
    UNLOCK_SAMPLES((&spec1))
    if(ret) {
      ret=false;
      const FFTWCache * cache=FFTWCache::begin(n, FFTWKey::HC2R);
      cache->execute(convSamples);
      FFTWCache::end(cache);
      //FastFourierTransform::instance()->backward(n, convSamples);
      LOCK_SAMPLES(double, thisSamples, this)
        int n0=n1-1-_nyquistIndex;
        double norm=1.0/static_cast<double>(n);
        for(int i=0; i<_nSamples; i++) {
          thisSamples[i]=convSamples[n0+i]*norm;
        }
        ret=true;
      UNLOCK_SAMPLES(this)
    }
  UNLOCK_SAMPLES((&conv))
  return ret;
}

/*!
  Computes normalization factors for normalized correlation. They take into account the amount
  of samples available for all delays.
*/
bool DoubleSignal::correlationNormalization(const DoubleSignal * s, DoubleSignal * norm) const
{
  TRACE;
  int n=s->nSamples();
  DoubleSignal sum(n);
  CacheProcess cp;
  cp << s << &sum << norm;
  bool ret=false;
  CONST_LOCK_SAMPLES(double, sSamples, s)
    LOCK_SAMPLES(double, sumSamples, (&sum))
      double v=sSamples[0];
      sumSamples[0]=v*v;
      for(int i=1; i<n; i++) {
        v=sSamples[i];
        sumSamples[i]=sumSamples[i-1]+v*v;
      }
      LOCK_SAMPLES(double, normSamples, norm)
        for(int i=0; i<_nSamples; i++) {
          int i1=_nyquistIndex-i-1;
          int i2=i1+n;
          double sum1, sum2;
          if(i1<0) {
            sum1=0.0;
          } else if(i1>=n) {
            sum1=sumSamples[n-1];
          } else {
            sum1=sumSamples[i1];
          }
          if(i2<0) {
            sum2=0.0;
          } else if(i2>=n) {
            sum2=sumSamples[n-1];
          } else {
            sum2=sumSamples[i2];
          }
          normSamples[i]=sum2-sum1;
        }
      ret=true;
      UNLOCK_SAMPLES(norm)
    UNLOCK_SAMPLES((&sum))
  UNLOCK_SAMPLES(s)
  return ret;
}

/*!
  Calculates the unormalized time correlation between signal \a s1 and \a s2 and stores the result in this signal.

  This is the fast method based on Fourier's transform.
*/
bool DoubleSignal::correlation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay)
{
  TRACE;
  if(!correlationCheck(s1, s2, maxDelay)) {
    return false;
  }
  setNSamples(2*qRound(maxDelay/s1->samplingPeriod())+1);
  setSamplingPeriod(s1->samplingPeriod());

  DoubleSignal * s1t=new DoubleSignal(*s1);
  DoubleSignal * s2t=new DoubleSignal(*s2);
  bool ret=correlationProcess(s1, s2, s1t, s2t);
  delete s1t;
  delete s2t;
  return ret;
}

/*!
  Calculates the normalized time correlation between signal \a s1 and \a s2 and stores the result in this signal.

  This is a fast alternative based on Fourier's transform.

  This implementation is based on "Fast Normalized Cross-Correlation" by J. P. Lewis, downgraded to 1D signals (instead of 2D images).
  (http://scribblethink.org/Work/nvisionInterface/nip.html)
*/
bool DoubleSignal::normalizedCorrelation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay)
{
  TRACE;
  if(!correlationCheck(s1, s2, maxDelay)) {
    return false;
  }
  setNSamples(2*qRound(maxDelay/s1->samplingPeriod())+1);
  setSamplingPeriod(s1->samplingPeriod());

  DoubleSignal * s1t=new DoubleSignal(*s1);
  DoubleSignal * s2t=new DoubleSignal(*s2);
  if(!correlationProcess(s1, s2, s1t, s2t)) {
    delete s1t;
    delete s2t;
    return false;
  }

  // Computation of normalization factors
  DoubleSignal * s1Norm=new DoubleSignal(_nSamples);
  DoubleSignal * s2Norm=new DoubleSignal(_nSamples);
  CacheProcess cp;
  cp << this << s1Norm << s2Norm;
  correlationNormalization(s1t, s1Norm);
  correlationNormalization(s2t, s2Norm);
  delete s1t;
  delete s2t;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    CONST_LOCK_SAMPLES(double, s1Samples, s1Norm)
      CONST_LOCK_SAMPLES(double, s2Samples, s2Norm)
        for(int i=0; i<_nSamples; i++) {
          double en1=s1Samples[i];
          double en2=s2Samples[_nSamples-i-1];
          if(en1>0.0 && en2>0.0) {
            thisSamples[i]/=::sqrt(en1*en2);
          }
        }
        ret=true;
      UNLOCK_SAMPLES(s2Norm)
    UNLOCK_SAMPLES(s1Norm)
  UNLOCK_SAMPLES(this)
  delete s1Norm;
  delete s2Norm;
  return ret;
}

/*!
  Calculates the convolution between signal \a s1 and \a s2 and stores the result in this signal.

  This is the fast alternative based on Fourier's transform.
*/
bool DoubleSignal::convolution(const DoubleSignal * s1, const DoubleSignal * s2)
{
  TRACE;
  // Samples of this cannot be allocated
  if(isAllocated()) {
    App::log(tr("DoubleSignal::convolution: samples of destination signal cannot be allocated, aborting.\n") );
    return false;
  }
  if(s1->samplingPeriod()<=0) {
    App::log(tr("DoubleSignal::convolution: null samplingPeriod, aborting.\n") );
    return false;
  }
  if(s1->samplingPeriod()!=s2->samplingPeriod()) {
    App::log(tr("DoubleSignal::convolution: samplingPeriod must be equal (%1!=%2), aborting.\n")
                  .arg(s1->samplingPeriod()).arg(s2->samplingPeriod()));
    return false;
  }
  int n=s1->nSamples();
  if(n!=s2->nSamples()) {
    App::log(tr("DoubleSignal::convolution: number of samples must be equal (%1!=%2), aborting.\n")
                  .arg(s1->nSamples()).arg(s2->nSamples()));
    return false;
  }

  ComplexSignal * cspec1=s1->complexSpectrum();
  /*LOCK_SAMPLES(Complex, csamp, cspec1)
  for(int i=0; i<n; i++) {
    csamp[i].setExp(1, -M_PI);
  }
  UNLOCK_SAMPLES(cspec1)*/
  ComplexSignal * cspec2=s2->complexSpectrum();
  ComplexSignal * cconv=new ComplexSignal(n);
  setSamplingPeriod(s1->samplingPeriod());
  setNSamples(n);

  bool ret=false;
  LOCK_SAMPLES(Complex, convSamples, cconv)
    CONST_LOCK_SAMPLES(Complex, s1Samples, cspec1)
      CONST_LOCK_SAMPLES(Complex, s2Samples, cspec2)
        for(int i=0; i<n; i++) {
          convSamples[i]=s1Samples[i]*s2Samples[i];
        }
        ret=true;
      UNLOCK_SAMPLES(cspec2)
    UNLOCK_SAMPLES(cspec1)
    delete cspec1;
    delete cspec2;
    FastFourierTransform::instance()->backward(n, convSamples);
    LOCK_SAMPLES(double, thisSamples, this)
      for(int i=0; i<n; i++) {
        thisSamples[i]=convSamples[i].re(); // Im is null
        // TODO: if it is correct to skip ComplexSignal
        //       study positive and negative lag in the code below
        //printf("%i %lg %lg %lg\n", i, convSamples[n0+i].re(), convSamples[n0+i].im(), convSamples[n0+i].im()/convSamples[n0+i].re());
      }
      // Negative lags
      /*int n0=n-_nyquistIndex;
      for(int i=0; i< _nyquistIndex; i++) {
        thisSamples[i]=0.5*convSamples[n0+i].re(); // Im is null
        printf("%i %lg %lg %lg\n", i, convSamples[n0+i].re(), convSamples[n0+i].im(), convSamples[n0+i].im()/convSamples[n0+i].re());
      }
      // Positive lags
      n0=-_nyquistIndex;
      for(int i=_nyquistIndex; i<_nSamples; i++) {
        thisSamples[i]=0.5*convSamples[n0+i].re(); // Im is null
        printf("%i %lg %lg %lg\n", i, convSamples[n0+i].re(), convSamples[n0+i].im(), convSamples[n0+i].im()/convSamples[n0+i].re());
      }*/
    UNLOCK_SAMPLES(this)
  UNLOCK_SAMPLES(cconv)
  delete cconv;
  return ret;
}

/*!
  Calculates the correlation between signal \a s1 and \a s2 in time domain and stores the result in this signal.
  The correlation is computed with a maximum time delay of \a maxDelay seconds, assuming that signals
  are already shifted by \a dt seconds. s1(t1+dt) is at the same time as s2(t2).
  The number of samples for the resulting signal is 2*(\a maxDelay /detlaT())+1. Sampling frequencies of two signals
  must be equal.

  The time window used for correlation is only the overlapping window decreased by the double of \a maxDelay (md).

  \code
          S1      --------------------------------------
                 | dt | md |     overlap           | md |
          S2           ------------------------------------------------------
                  -----------> time scale

          S1      ----------0000000000000000000000000000
                 | dt | md |     overlap           | md |
          S2           0000000000000000000000000000--------------------------

          S1      -----0000000000000000000000000000-----
                 | dt | md |     overlap           | md |
          S2           -----0000000000000000000000000000---------------------
  \endcode

  If this signal has the correct number of samples and the correct sampling frequency, the value of this signal
  are not cleaned and the results are stacked on existing values. If not, the signal is initialized with 0.

  This is the slow alternative not based on Fourier's transform. Prefer correlation() for all applications.
*/
bool DoubleSignal::timeCorrelation(const DoubleSignal * s1, const DoubleSignal * s2, double maxDelay)
{
  TRACE;
  if(!correlationCheck(s1, s2, maxDelay)) {
    return false;
  }

  double sampFreq=s1->samplingFrequency();
  int nmd=(int)round(maxDelay*sampFreq);     // number of samples for maxDelay
  int n1=s1->nSamples();
  int n2=s2->nSamples();

  // Init the result signal if necessary, else stack
  if(nSamples()!=2*nmd+1 || samplingPeriod()!=s1->samplingPeriod()) {
    if(isAllocated()) {
      return false;
    }
    setNSamples(2*nmd+1);
    setSamplingPeriod(s1->samplingPeriod());
    initValues (0.0, 0, 2*nmd+1);
  }

  bool ret=false;
  CONST_LOCK_SAMPLES(double, s1Samples, s1)
    CONST_LOCK_SAMPLES(double, s2Samples, s2)
      LOCK_SAMPLES(double, thisSamples, this)
        thisSamples+=nmd;
        double corr, en1, en2, v1, v2;
        for(int delay=-nmd; delay<=0; delay++) {
          corr=0.0;
          en1=0.0;
          en2=0.0;
          int n=n1;
          if(n>n2-delay) {
            n=n2-delay;
          }
          for(int i=-delay; i<n; i++) {
            v1=s1Samples[i];
            v2=s2Samples[i+delay];
            corr+=v1*v2;
            en1+=v1*v1;
            en2+=v2*v2;
          }
          if(en1>0.0 && en2>0.0) {
            thisSamples[delay]+=corr/::sqrt(en1*en2);
          }
        }
        for(int delay=1; delay<=nmd; delay++) {
          corr=0.0;
          en1=0.0;
          en2=0.0;
          int n=n2;
          if(n>n1+delay) {
            n=n1+delay;
          }
          for(int i=delay; i<n; i++) {
            v1=s1Samples[i-delay];
            v2=s2Samples[i];
            corr+=v1*v2;
            en1+=v1*v1;
            en2+=v2*v2;
          }
          if(en1>0.0 && en2>0.0) {
            thisSamples[delay]+=corr/::sqrt(en1*en2);
          }
        }
        ret=true;
      UNLOCK_SAMPLES(this)
    UNLOCK_SAMPLES(s2)
  UNLOCK_SAMPLES(s1)
  return ret;
}

bool DoubleSignal::copySamplesFrom(const DoubleSignal * sig, double sigStart, double thisStart, double tToCopy)
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::copySamplesFrom: null samplingPeriod, aborting.\n") );
    return false;
  }
  if(_samplingPeriod!=sig->samplingPeriod()) {
    App::log(tr("DoubleSignal::copySamplesFrom: samplingPeriod must be equal, aborting.\n") );
    return false;
  }
  double fSamp=1.0/_samplingPeriod;
  return copySamplesFrom(sig, qRound(sigStart*fSamp), qRound(thisStart*fSamp), qRound(tToCopy*fSamp));
}

bool DoubleSignal::copySamplesFrom(const DoubleSignal * sig, int isigStart, int ithisStart, int nToCopy)
{
  TRACE;
  const DoubleSignal * wavSig=sig->saveType(Waveform);
  SignalType st=saveType(Waveform);
  bool ret=SignalTemplate<double>::copySamplesFrom(wavSig, isigStart, ithisStart, nToCopy);
  restoreType(st);
  sig->restoreType(wavSig);
  return ret;
}

/*!
  Return  all amplitudes as a curve.
*/
Curve<Point2D> DoubleSignal::spectrumAmplitudeCurve() const
{
  TRACE;
  const DoubleSignal * sigfft=saveType(Spectrum);
  double df=1.0/duration();
  int nSamples2=nSamples() >> 1;
  Curve<Point2D> c(nSamples2+1);
  CONST_LOCK_SAMPLES(double, sigSamples, sigfft)
    for(int i=0; i<=nSamples2; i++) {
      Point2D& p=c[i];
      p.setX(static_cast<double>(i)*df);
      p.setY(sigfft->amplitude(sigSamples, i));
    }
  UNLOCK_SAMPLES(sigfft)
  restoreType(sigfft);
  c.sort();
  return c;
}

int DoubleSignal::add(const DoubleSignal * sig, double sigStart, double thisStart,
                      double tToCopy, bool checkInvalid, double invalidValue)
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::add: null samplingPeriod, aborting.\n") );
    return false;
  }
  if(_samplingPeriod!=sig->samplingPeriod()) {
    App::log(tr("DoubleSignal::add: samplingPeriod must be equal, aborting.\n") );
    return 0;
  }
  double fSamp=1.0/_samplingPeriod;
  SignalType st=saveType(Waveform);
  int nCommon=SignalTemplate<double>::add (sig, (int) round(sigStart * fSamp),
                                                 (int) round(thisStart * fSamp),
                                                 (int) round(tToCopy * fSamp),
                                                 checkInvalid, invalidValue);
  restoreType(st);
  return nCommon;
}

int DoubleSignal::add(const DoubleSignal * sig)
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::add: null samplingPeriod, aborting.\n") );
    return 0;
  }
  if(_samplingPeriod!= sig->samplingPeriod()) {
    App::log(tr("DoubleSignal::add: samplingPeriod must be equal, aborting.\n") );
    return 0;
  }
  SignalType st=saveType(Waveform);
  int nCommon=SignalTemplate<double>::add (sig);
  restoreType(st);
  return nCommon;
}

/*!
  Returns the amplitude of the signal at time or frequency \a abscissa (depends upon signal type).
  The index is rounded to the lowest sample. No interpolation is perfomed.
*/
double DoubleSignal::amplitudeAt(double abscissa) const
{
  TRACE;
  double val;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    switch (_type) {
    case Waveform: {
        int i=qFloor(abscissa/_samplingPeriod);
        if(i<0 || i>=_nSamples)
          val=0.0;
        else
          val=thisSamples[i];
      }
      break;
    case Spectrum: {
        int i=qFloor(abscissa*_samplingPeriod *static_cast<double>(_nSamples));
        if(i < 0 || i > (_nSamples >> 1) )
          val=0.0;
        else
          val=amplitude(thisSamples, i);
      }
      break;
    case Phase: {
      int i=qFloor(abscissa*_samplingPeriod *static_cast<double>(_nSamples));
        if(i < 0 || i > (_nSamples >> 1) )
          val=0.0;
        else
          val=phase(thisSamples, i);
      }
      break;
    default:
      val=0.0;
      break;
    }
  UNLOCK_SAMPLES(this)
  else {
    val=0.0;
  }
  return val;
}

/*!
  Returns the amplitude of the signal at time or frequency \a abscissa (depends upon signal type).
  The returned amplitude is interpolated.
*/
double DoubleSignal::interpoleAt(double abscissa) const
{
  TRACE;
  double val;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    switch (_type) {
    case Waveform: {
        double di=abscissa/_samplingPeriod;
        int i=(int) ceil(di);
        if(i < 0 || i >= _nSamples) val=0.0;
        else {
          if(i==0) i=1;
          val=thisSamples[ i ] + (thisSamples[ i ] - thisSamples[ i - 1 ] ) * (di - (double) i);
        }
      }
      break;
    default:
      val=0.0;
      break;
    }
  UNLOCK_SAMPLES(this)
  else {
    App::log(3, tr("DoubleSignal::interpoleAt() cannot access to data vector\n"));
    val=0.0;
  }
  return val;
}

bool DoubleSignal::unglitch(double threshold)
{
  TRACE;
  // TODO: not checked for spectrum amplitude (odd or even)
  // would be better to use inline functions
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    double mul=threshold/100.;
    if(_type==Waveform) {
      double vm1, v, vp1;
      v=thisSamples[ 0 ];
      vp1=thisSamples[ 1 ];
      if(v > vp1 * mul) {
        thisSamples[ 0 ]=vp1;
      }
      int i;
      for(i=1; i < _nSamples - 1; i++ ) {
        vm1=v;
        v=vp1;
        vp1=thisSamples[ i + 1 ];
        if(( v > vm1 * mul) && (v > vp1 * mul) ) {
          thisSamples[ i ]=(vm1 + vp1)/2.;
        }
      }
      vm1=v;
      v=vp1;
      if(v > vm1 * mul) {
        thisSamples[ i ]=vm1;
      }
    } else if(isSpectrum()) {
      double vm1, v, vp1;
      mul *= mul;
      v=thisSamples[ 0 ] * thisSamples[ 0 ];
      vp1=thisSamples[ 1 ] * thisSamples[ 1 ] + thisSamples[ _nSamples - 1 ] * thisSamples[ _nSamples - 1 ];
      if(v > vp1 * mul) {
        thisSamples[ 0 ]=::sqrt(vp1);
      }
      int i;
      for(i=1; i < _nSamples/2; i++ ) {
        vm1=v;
        v=vp1;
        vp1=thisSamples[ i + 1 ] * thisSamples[ i + 1 ] + thisSamples[ _nSamples - i - 1 ] * thisSamples[ _nSamples - i - 1 ];
        if(( v > vm1 * mul) && (v > vp1 * mul) ) {
          double corr=( ::sqrt(vm1) + ::sqrt(vp1) )/2./::sqrt(v);
          thisSamples[ i ] *= corr;
          thisSamples[ _nSamples - i ] *= corr;
        }
      }
      vm1=v;
      v=vp1;
      if(v > vm1 * mul) {
        thisSamples[ i ]=::sqrt(vm1);
      }
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Convert all samples to positive numbers
*/
bool DoubleSignal::abs()
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0;i < _nSamples;i++ )
      thisSamples[ i ]=fabs(thisSamples[ i ] );
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Take the square root of all samples
*/
bool DoubleSignal::sqrt()
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0; i<_nSamples; i++) {
      thisSamples[i]=::sqrt(thisSamples[i]);
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Take the square of all samples
*/
bool DoubleSignal::square()
{
  TRACE;
  bool ret=false;
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0; i<_nSamples; i++) {
      thisSamples[i]*=thisSamples[i];
    }
    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Removes any linear variation of the base line
*/
bool DoubleSignal::removeTrend()
{
  TRACE;
  bool ret=false;
  LinearRegression reg;
  reg.reserve(nSamples());
  LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0; i<_nSamples; i++) {
      reg.add(i, thisSamples[i]);
    }
    reg.calculate();
    // No need to take x0 into account, always 0 here
    App::log(1, tr("removeTrend: s(t)=%1*t+%2\n")
                      .arg(reg.a()*samplingFrequency())
                      .arg(reg.b()));
    for(int i=0; i<_nSamples; i++) {
      thisSamples[i]-=reg.at(i);
    }

    ret=true;
  UNLOCK_SAMPLES(this)
  return ret;
}

/*!
  Calculates the sta/lta ratio and stores it in ratio.
  while i<nsta (or lsta), sta (or lsta) is set to the average value of the signal.

  The sta and lta are always computed over the past samples.

  Averages are calculated with arithmetic sums. Use abs() or square() before calling
  this function.
*/
void DoubleSignal::stalta(double tsta, double tlta, DoubleSignal * ratio) const
{
  TRACE;
  CacheProcess cp;
  cp << this << ratio;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    LOCK_SAMPLES(double, ratioSamples, ratio)
      double fSamp=1.0/_samplingPeriod;
      int nsta=(int) (tsta * fSamp);
      int nlta=(int) (tlta * fSamp);
      if(nsta > _nSamples) nsta=_nSamples;
      if(nlta > _nSamples) nlta=_nSamples;
      double moy=average(0, _nSamples);
      double sta_sum=moy * (double) nsta, lta_sum=moy * (double) nlta;
      double r=tlta/tsta;
      int i;
      // lta_sum=sta_sum => slta=nlta/nsta;
      //printf("i\tsig\tstasum\tltasum\n");
      for(i=0;i < nsta;i++ ) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        sta_sum += thisSamples[ i ] - moy;
        lta_sum += thisSamples[ i ] - moy;
        ratioSamples[ i ]=r * sta_sum/lta_sum;
      }
      for( ;i < nlta;i++ ) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        sta_sum += thisSamples[ i ] - thisSamples[ i - nsta ];
        lta_sum += thisSamples[ i ] - moy;
        ratioSamples[ i ]=r * sta_sum/lta_sum;
      }
      for( ;i < _nSamples;i++ ) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        sta_sum += thisSamples[ i ] - thisSamples[ i - nsta ];
        lta_sum += thisSamples[ i ] - thisSamples[ i - nlta ];
        ratioSamples[ i ]=r * sta_sum/lta_sum;
      }
    UNLOCK_SAMPLES(ratio)
  UNLOCK_SAMPLES(this)
}

/*!
  Check if intervals are consistent. \a delay is the delay of this signal relative to the beginning of the other signal
*/
int DoubleSignal::commonSamples(double delay, int otherNSamples, int& thisStart, int& otherStart) const
{
  TRACE;
  otherStart=(int) floor(delay/_samplingPeriod + 0.5);
  if(otherStart >= otherNSamples) return 0;
  thisStart=0;
  if(otherStart < 0) {
    thisStart=-otherStart;
    otherStart=0;
  }
  int nSamples=_nSamples - thisStart;
  if(otherStart+nSamples > otherNSamples)
    nSamples=otherNSamples - otherStart;
  return nSamples;
}

/*!
  Calculates the sta/lta ratio and test if it is between min and max, set keep accordingly.
  \a delay is the delay of this sig relative to the beginning of \a keep signal.
  \a keep and this signals must have the same samplingPeriod().

  while i<nsta (or lsta), sta (or lsta) is set to the average value of the signal.

  The sta and lta are always computed over the past samples.

  Averages are calculated with arithmetic sums. Use abs() or square() before calling
  this function.
*/
void DoubleSignal::staltaToKeep(const WindowingParameters::ShortLongTermAverage& param,
                                KeepSignal * keep, double delay) const
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::staltaToKeep: null samplingPeriod, aborting.\n") );
    return;
  }
  if(_samplingPeriod!=keep->samplingPeriod()) {
    App::log(tr("DoubleSignal::staltaToKeep: samplingPeriod must be equal, aborting.\n") );
    return;
  }
  int thisStart, keepStart;
  int nSamples=commonSamples(delay, keep->nSamples(), thisStart, keepStart);
  // OK, calculate keep from keepStart to keepStart+nSamp
  CacheProcess cp;
  cp << this << keep;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    LOCK_SAMPLES(int, keepSamples, keep)
      int nsta=(int) (param.shortLength/_samplingPeriod);
      int nlta=(int) (param.longLength/_samplingPeriod);
      double aver=average(thisStart, nSamples);
      //printf("nsta %i nlta %i moy %lf\n",nsta, nlta, moy);
      double sta_sum=aver*(double)nsta;
      double lta_sum=aver*(double)nlta;
      double corr=param.shortLength/param.longLength;
      double ratioCorr;
      double minCorr=corr * param.minimumThreshold;
      double maxCorr=corr*param.maximumThreshold;
      int i;
      // lta_sum=sta_sum => slta=nlta/nsta;
      //printf("i\tsig\tstasum\tltasum\n");
      double val;
      int ithis;
      int n=nsta;
      if(nSamples < n)
        n=nSamples;
      for(i=0;i < n;i++ ) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        val=thisSamples[i+thisStart]-aver;
        sta_sum+=val;
        lta_sum+=val;
        ratioCorr=sta_sum/lta_sum;
        if(ratioCorr < minCorr || ratioCorr > maxCorr)
          keepSamples[ i + keepStart ]=0;
      }
      n=nlta;
      if(nSamples < n)
        n=nSamples;
      for( ;i < n;i++ ) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        ithis=i+thisStart;
        val=thisSamples[ithis];
        sta_sum+=val-thisSamples[ithis-nsta];
        lta_sum+=val-aver;
        ratioCorr=sta_sum/lta_sum;
        if(ratioCorr<minCorr || ratioCorr>maxCorr)
          keepSamples[i+keepStart]=0;
      }
      for(; i<nSamples; i++) {
        //printf("%i\t%lf\t%lf\t%lf\n",i,_samples[i],sta_sum,lta_sum);
        ithis=i+thisStart;
        val=thisSamples[ithis];
        sta_sum+=val-thisSamples[ithis-nsta];
        lta_sum+=val-thisSamples[ithis-nlta];
        ratioCorr=sta_sum/lta_sum;
        if(ratioCorr<minCorr || ratioCorr>maxCorr)
          keepSamples[i+keepStart]=0;
      }
    UNLOCK_SAMPLES(keep)
  UNLOCK_SAMPLES(this)
}

/*!
  Set keep signal according to the amplitude of signal (if above \a value).
  \a delay is the delay of this sig relative to the beginning of \a keep signal.
  \a keep and this signals must have the same samplingPeriod().

  The mean is removed before comparing samples.
*/
void DoubleSignal::clipMaxToKeep(double value, KeepSignal * keep, double delay) const
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::clipRelativeMaxToKeep: null samplingPeriod, aborting.\n") );
    return;
  }
  if(_samplingPeriod!=keep->samplingPeriod()) {
    App::log(tr("DoubleSignal::clipRelativeMaxToKeep: samplingPeriod must be equal, aborting.\n") );
    return;
  }
  int thisStart, keepStart;
  int nSamples=commonSamples(delay, keep->nSamples(), thisStart, keepStart);
  double m=average(0, _nSamples);
  CacheProcess cp;
  cp << this << keep;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    LOCK_SAMPLES(int, keepSamples, keep)
      for(int i=0; i<nSamples; i++) {
        if(fabs(thisSamples[ i+thisStart]-m)>=value)
          keepSamples[i+keepStart]=0;
      }
    UNLOCK_SAMPLES(keep)
  UNLOCK_SAMPLES(this)
}

/*!
  Calculate the zero delay time correlation between this signal and \a sig. \a thisStart and \a sigStart are the time
  from the beginning of each signal where to start correlation computation. \a length is in seconds and is the length
  of time used for the correlation computation.

  This signal and \a sig must have the same sampling frequency. The resulting correlation is normalized by the number
  of samples included in \a length.
*/
double DoubleSignal::correlation(const DoubleSignal * sig, double sigStart, double thisStart, double length) const
{
  TRACE;
  if(_samplingPeriod<=0) {
    App::log(tr("DoubleSignal::correlation: null samplingPeriod, aborting.\n") );
    return 0;
  }
  if(_samplingPeriod!=sig->samplingPeriod()) {
    App::log(tr("DoubleSignal::correlation: samplingPeriod must be equal, aborting.\n") );
    return 0;
  }
  double invdt=1.0/samplingPeriod();
  int iSigStart=(int) floor(sigStart * invdt +0.5);
  int iThisStart=(int) floor(thisStart * invdt +0.5);
  int iLength=(int) floor(length * invdt +0.5);
  adjustLimits(sig, iSigStart, iThisStart, iLength);
  int diStart=iSigStart-iThisStart;
  int iEnd=iThisStart+iLength;

  double corr=0.0;
  CacheProcess cp;
  cp << sig << this;
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    CONST_LOCK_SAMPLES(double, thisSamples, this)
      for(int is=iThisStart; is<iEnd; is++) {
        corr+=thisSamples[is]*sigSamples[is+diStart];
      }
    UNLOCK_SAMPLES(this)
  UNLOCK_SAMPLES(sig)
  return corr/(double)iLength;
}

/*!
  Return signal type as string.
*/
QString DoubleSignal::typeString(SignalType st)
{
  TRACE;
  switch (st) {
  case Spectrum: return "Spectrum";
  case Phase: return "Phase";
  case Waveform: return "Waveform";
  case UndefinedSignalType: break;
  }
  return "Waveform";
}

/*!
  Set signal type from a string
*/
DoubleSignal::SignalType DoubleSignal::type(QString st, bool * ok)
{
  TRACE;
  if(st.isEmpty()) {
    if(ok) {
      *ok=false;
    }
    return UndefinedSignalType;
  }
  st=st.toLower();
  switch(st[0].unicode()) {
  case 'c':                         // Kept for compatibility
    if(st=="cphasewaveform") {
      return Waveform;
    } else if(st=="campwaveform"){
      return Waveform;
    }
    break;
  case 'p':
    if(st=="phase") return Phase;
    break;
  case 'r':                         // Kept for compatibility
    if(st=="realspectrum") return Waveform;
    break;
  case 's':
    if(st=="spectrum") return Spectrum;
    break;
  case 'w':
    if(st=="waveform") return Waveform;
    break;
  default:
    break;
  }
  if(ok) {
    *ok=false;
  }
  return UndefinedSignalType;

}

/*!
  Returns the maximum amplitude of the signal between \a itmin and \a itmax
*/
double DoubleSignal::maximumAmplitude(int itmin, int itmax) const
{
  TRACE;
  if(itmin<0) itmin=0;
  if(itmax>=_nSamples) itmax=_nSamples-1;

  double vmax=0.0;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    double v;
    switch (_type) {
    case Waveform:
      for(int i=itmin; i<=itmax; i++) {
        v=fabs(thisSamples[i]);
        if(v>vmax) vmax=v;
      }
      break;
    case Spectrum:
    case Phase: {
        int nSamples2=_nSamples >> 1;
        int it2max;
        if(itmax>nSamples2) it2max=nSamples2; else it2max=itmax;
        for(int i=itmin; i<=it2max; i++) {
          v=amplitude2(thisSamples, i);
          if(v>vmax) vmax=v;
        }
        vmax=::sqrt(vmax);
      }
      break;
    default:
      vmax=1.0;
      break;
    }
  UNLOCK_SAMPLES(this)
  return vmax;
}

/*!
  Returns the index of the maximum amplitude of the signal between \a itmin and \a itmax
*/
int DoubleSignal::maximumAmplitudeAt(int itmin, int itmax) const
{
  TRACE;
  if(itmin<0) itmin=0;
  if(itmax>=_nSamples) itmax=_nSamples-1;

  double vmax=0.0;
  int maxIndex=itmin;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    double v;
    switch (_type) {
    case Waveform:
      for(int i=itmin; i<=itmax; i++) {
        v=fabs(thisSamples[i]);
        if(v>vmax) {
          vmax=v;
          maxIndex=i;
        }
      }
      break;
    case Spectrum:
    case Phase: {
        int nSamples2=_nSamples >> 1;
        int it2max;
        if(itmax>nSamples2) it2max=nSamples2; else it2max=itmax;
        for(int i=itmin; i<=it2max; i++) {
          v=amplitude2(thisSamples, i);
          if(v>vmax) {
            vmax=v;
            maxIndex=i;
          }
        }
        vmax=::sqrt(vmax);
      }
      break;
    default:
      break;
    }
  UNLOCK_SAMPLES(this)
  return maxIndex;
}


/*!
  Returns the index of the maximum of the signal between \a itmin and \a itmax
*/
int DoubleSignal::maximumAt(int itmin, int itmax) const
{
  TRACE;
  if(itmin<0) itmin=0;
  if(itmax>=_nSamples) itmax=_nSamples-1;

  double vmax=0.0;
  int maxIndex=itmin;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    double v;
    switch (_type) {
    case Waveform:
      for(int i=itmin; i<=itmax; i++) {
        v=thisSamples[i];
        if(v>vmax) {
          vmax=v;
          maxIndex=i;
        }
      }
      break;
    case Spectrum:
    case Phase: {
        int nSamples2=_nSamples >> 1;
        int it2max;
        if(itmax>nSamples2) it2max=nSamples2; else it2max=itmax;
        for(int i=itmin; i<=it2max; i++) {
          v=amplitude2(thisSamples, i);
          if(v>vmax) {
            vmax=v;
            maxIndex=i;
          }
        }
        vmax=::sqrt(vmax);
      }
      break;
    default:
      break;
    }
  UNLOCK_SAMPLES(this)
  return maxIndex;
}

/*!
  Returns the average amplitude of the signal between \a itmin and \a itmax
*/
double DoubleSignal::averageAmplitude(int itmin, int itmax) const
{
  TRACE;
  if(itmin<0) itmin=0;
  if(itmax>=_nSamples) itmax=_nSamples-1;
  double nSamples=(double) (itmax-itmin+1);

  double sum=0.0;
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    switch (_type) {
    case Waveform:
      for(int i=itmin; i<=itmax; i++) {
        sum+=thisSamples[i];
      }
      break;
    case Spectrum:
    case Phase: {
        int nSamples2=_nSamples >> 1;
        int it2max;
        if(itmax > nSamples2) it2max=nSamples2; else it2max=itmax;
        for(int i=itmin; i<=it2max; i++) {
          sum+=amplitude2(thisSamples, i);
        }
      }
      break;
    default:
      sum=1.0;
      break;
    }
  UNLOCK_SAMPLES(this)
  return sum/nSamples;
}

/*!
  Returns the energy between frequency \a minF and \a maxF.
*/
double DoubleSignal::energy(double minF, double maxF) const
{
  TRACE;
  double en=0.0;
  const DoubleSignal * sig=saveType(Spectrum); // make sure we are in frequency domain
  CONST_LOCK_SAMPLES(double, sigSamples, sig)
    double fac=_samplingPeriod * (double) _nSamples;
    int minI=(int) floor(minF * fac);
    if(minI<0) minI=0;
    int maxI=(int) ceil(maxF * fac);
    if(maxI>=_nSamples) maxI=_nSamples-1;
    if(minI>=maxI) return 0.0;
    for(int i=minI; i<maxI; i++ ) {
      en+=sig->amplitude2(sigSamples, i);
    }
  UNLOCK_SAMPLES(sig);
  restoreType(sig);
  return en;
}

/*!
  Time domain computation of total power. \a excludeBorderRatio can for instance 0.1 to exclude
  10% of signal on both ends.
*/
double DoubleSignal::power(double excludeBorderRatio) const
{
  TRACE;
  ASSERT(excludeBorderRatio<1.0 && excludeBorderRatio>=0.0);
  double p=0.0;
  CONST_LOCK_SAMPLES(double, samples, this)
    int n=qRound(_nSamples*(1.0-excludeBorderRatio));
    for(int i=qRound(_nSamples*excludeBorderRatio); i<n; i++) {
      p+=samples[i]*samples[i];
    }
  UNLOCK_SAMPLES(this);
  return p;
}

void DoubleSignal::debugPrint() const
{
  TRACE;
  printf("Debug samples for signal %s\n", debugName().toLatin1().data());
  CONST_LOCK_SAMPLES(double, thisSamples, this)
    for(int i=0;i < _nSamples;i++ ) printf( "%i\t%lg\n", i, thisSamples[ i ] );
  UNLOCK_SAMPLES(this)
}

void DoubleSignal::debugSave(const QString& fileName) const
{
  TRACE;
  Signal * tmp=new Signal(GeopsyCoreEngine::instance()->defaultDatabase());
  tmp->setNSamples(nSamples());
  tmp->setSamplingPeriod(samplingPeriod());
  tmp->DoubleSignal::copySamplesFrom(this);
  tmp->multiply(1e3);
  SubSignalPool pool;
  pool.addSignal(tmp);
  pool.save(fileName, false, SignalFileFormat::MiniSeed);
  App::log(tr("Signal %1 saved in %2\n").arg(debugName()).arg(fileName) );
}

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

void DoubleSignal::setComplex(double * samples, int i, const Complex& value) const
{
  TRACE;
  if(i==0) {
    samples[i]=value.re();
  } else if(i<_nyquistIndex) {
    samples[i]=value.re();
    samples[_nSamples-i]=value.im();
  } else if(i==_nyquistIndex) {
    samples[i]=value.re();
    if(_nSamples & 0x00000001) {
      samples[_nSamples-i]=value.im();
    }
  } else {
    ASSERT(false);
  }
}

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

void DoubleSignal::setAmplitude(double * samples, int i, double val) const
{
  if(i==0) {
      samples[i]=val;
  } else if(i<_nyquistIndex) {
    double ampl=::sqrt(samples[i]*samples[i]+
                       samples[_nSamples-i]*samples[_nSamples-i]);
    if(ampl==0.0) return;
    double factor=val/ampl;
    samples[i]*=factor;
    samples[_nSamples-i]*=factor;
  } else if(i==_nyquistIndex) {
    if(_nSamples & 0x00000001) {
      double ampl=::sqrt(samples[i]*samples[i]+
                         samples[_nSamples-i]*samples[_nSamples-i]);
      if(ampl==0.0) return;
      double factor=val/ampl;
      samples[i]*=factor;
      samples[_nSamples-i]*=factor;
    } else {
      samples[i]=val;
    }
  } else {
    ASSERT(false);
  }
}

/*!
  Deconvolution by the instrumental response
*/
bool DoubleSignal::removeInstrumentalResponse(const InstrumentalResponse& sensor)
{
  TRACE;
  int n=nSamples();
  ComplexSignal * cspec=complexSpectrum();
  ComplexSignal * cconv=new ComplexSignal(n);
  double df=1.0/duration();
  bool ok=false;
  LOCK_SAMPLES(Complex, convSamples, cconv)
    CONST_LOCK_SAMPLES(Complex, sSamples, cspec)
      for(int i=0; i<n; i++) {
        if(i<_nyquistIndex) {
          convSamples[i]=sensor.deconvolution(i*df, sSamples[i]);
        } else {
          convSamples[i]=sensor.deconvolution(-i*df, sSamples[i]);
        }
      }
      ok=true;
    UNLOCK_SAMPLES(cspec)
    delete cspec;
    FastFourierTransform::instance()->backward(n, convSamples);
    double rn=1.0/::sqrt(2*_samplingPeriod*_nSamples);
    LOCK_SAMPLES(double, thisSamples, this)
      for(int i=0; i<_nSamples; i++) {
        thisSamples[i]=convSamples[i].re()*rn;
      }
    UNLOCK_SAMPLES(this)
  UNLOCK_SAMPLES(cconv)
  delete cconv;
  return ok;
}

bool DoubleSignal::multiply(const Complex& value)
{
  TRACE;

  bool ok=false;
  LOCK_SAMPLES(double, samples, this)
    SignalType ost=saveType(Spectrum);
    int n=_nSamples >> 1;
    for(int i=0; i<n; i++) {
      Complex z(re(samples, i), im(samples, i));
      z*=value;
      setRe(samples, i, z.re());
      setIm(samples, i, z.im());
    }
    restoreType(ost);
    ok=true;
  UNLOCK_SAMPLES(this)
  return ok;
}

} // namespace GeopsyCore
