/***************************************************************************
**
**  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: 2011-11-30
**  Copyright: 2011-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#ifndef TAPERDELEGATE_H
#define TAPERDELEGATE_H

#include <QGpCoreMath.h>

#include "GeopsyCoreDLLExport.h"
#include "TimeRange.h"

namespace GeopsyCore {

  class GEOPSYCORE_EXPORT TaperDelegate
  {
  public:
    TaperDelegate(const WindowFunctionParameters * p);
    ~TaperDelegate();

    void test(int nSamples, int startIndex, int endIndex);
    double correctionFactor(double start, double end, double samplingFrequency);
    double correctionFactor(int windowFirstSample, int windowLastSample);

    template<typename sampleType>
    void apply(sampleType samples, int signalFirstSample, int signalLastSample,
               int windowFirstSample, int windowLastSample) const;

    template<typename sampleType>
    void apply(sampleType samples, double start, double end,
               double samplingFrequency, int nSamples) const;

    class Sample
    {
    public:
      Sample(double * samples) {_samples=samples;}
      ~Sample() {}
    protected:
      double * _samples;
    };

    class RealSample: public Sample
    {
    public:
      RealSample(double * samples) : Sample(samples) {}

      void setNull(int index) {_samples[index]=0;}
      void multiply(int index, double factor) {_samples[index]*=factor;}
    };

    class ComplexSample: public Sample
    {
    public:
      ComplexSample(int nSamples, double * samples)
        : Sample(samples) {_nSamples=nSamples; _nyquistIndex=_nSamples >> 1;}
      void setNull(int index)
      {
        _samples[index]=0.0;
        // If index==_nyquistIndex and even number of samples, _nSamples-index==index
        // Useless affectation to 0 but it is more effecient than condition testing for all samples.
        if(index>0) {
          _samples[_nSamples-index]=0;
        }
      }
      void multiply(int index, double factor)
      {
        _samples[index]*=factor;
        // Above shortcut cannot be done without multiplying twice
        if(index>0 && (index<_nyquistIndex || (_nSamples & 0x00000001))) {
          _samples[_nSamples-index]*=factor;
        }
      }
    protected:
      int _nSamples;
      int _nyquistIndex;
    };
  private:
    const WindowFunctionParameters * _parameters;
  };

  /*!
    Overload function provided for convenience.
  */
  template<typename sampleType>
  void TaperDelegate::apply(sampleType samples, double start, double end,
                            double samplingFrequency, int nSamples) const
  {
    TRACE;
    apply(samples, 0, nSamples-1, qRound(start*samplingFrequency),
          qRound(end*samplingFrequency)-1);
  }

  /*!
    Outside \a signalFirstSample and \a signalLastSample the signal is not touched.
    Outside \a windowFirstSample and \a windowLastSample the affected signal is set to null or untouched.
    The signal is modified between \a windowFirstSample and \a windowLastSample according
    to window function.

    \a windowFirstSample and \a windowLastSample can be outside the range \a signalFirstSample and \a signalLastSample.
  */
  template<typename sampleType>
  void TaperDelegate::apply(sampleType samples, int signalFirstSample, int signalLastSample,
                            int windowFirstSample, int windowLastSample) const
  {
    TRACE;
#ifdef COMPATIBILITY_2_5_0
    WindowFunction func(windowLastSample-windowFirstSample+2, *_parameters);
#else
    WindowFunction func(windowLastSample-windowFirstSample+1, *_parameters);
#endif
    ASSERT(windowFirstSample<=windowLastSample);
    func.setStartIndex(windowFirstSample);
    func.setSignalRange(signalFirstSample, signalLastSample);
    const QVector<WindowFunction::Action>& actions=func.actions();
    int n=actions.count();
    int index;
    for(int i=0; i<n; i++) {
      const WindowFunction::Action& action=actions.at(i);
      switch(action.type()) {
      case WindowFunction::Action::Vanish:
        for(index=action.start(); index<=action.end(); index++) {
          samples.setNull(index);
        }
        break;
      case WindowFunction::Action::Untouch:
        break;
      case WindowFunction::Action::Multiply:
        for(index=action.start(); index<=action.end(); index++) {
          samples.multiply(index, func.value(index));
        }
        break;
      }
    }
  }

} // namespace GeopsyCore

#endif // TAPERDELEGATE_H
