/***************************************************************************
**
**  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: 2007-04-30
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "DynamicSignal.h"

namespace GeopsyCore {

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

    Full description of class still missing
  */

  DynamicSignal::~DynamicSignal()
  {
    TRACE;
    // Upon deletion of object, virtual function are not working anymore
    // We remove the shift before removing samples the normal way.
    setSamples(samples()-_startTimeShift);
    _startTimeShift=0;
  }

  template <class sampleType> inline void DynamicSignal::set(const DateTime& t, const sampleType * samples, int nSamples)
  {
    TRACE;
    APP_LOG(1, tr("Received %1 samples from %2 for signal %3\n").arg(nSamples).arg(t.toString(DateTime::defaultUserFormat)).arg(nameComponent()));

    DateTime epsilonStartTime(startTime());
    epsilonStartTime.addFewSeconds(-1e-10);
    if(t<epsilonStartTime) {
      App::log(tr("Cannot set signal before start time (%1), t=%2 for signal %3\n")
               .arg(startTime().toString()).arg(t.toString()).arg(nameComponent()) );
      return;
    }
    // Keep track of gaps
    _timeRange.add(TimeRange(t, nSamples*samplingPeriod()));

    double indexD=startTime().secondsTo(t)/samplingPeriod();
    int index=qRound(indexD);
    if(fabs(indexD-static_cast<double>(index))>1e-3) {
      App::log(tr("time drift %1 % %2 %3\n")
          .arg(100*(indexD-index)).arg(startTime().toString()).arg(t.toString()));
    }
    double * newSamples;
    // Index including all time shifts
    int globalIndex=index+nSamples+_startTimeShift;
    if(_nSamples>0) {
      /*
          To decrease the complexity of locking, it is better not to lock amplitude and data at the same time.
          So we keep of copy of average and maximum for updating them smoothly.
      */
      double averageAmplitude, maxAmplitude;
      _amplitudeMutex.lock();
      averageAmplitude=_averageAmplitude;
      maxAmplitude=_maxAmplitude;
      _amplitudeMutex.unlock();

      LOCK_SAMPLES(double, thisSamples, this)
        if(globalIndex>=_maxNSamples) {
          if(_startTimeShift>_nSamples && nSamples<_nSamples) {  /* We reached a buffer that's at least twice the true signal length
                                                                    Do not waste memory resources anymore */
            newSamples=thisSamples-_startTimeShift;
            memmove(newSamples, thisSamples, sizeof(double)*static_cast<size_t>(_nSamples));
            memset(newSamples+_nSamples, 0, sizeof(double)*static_cast<size_t>(_startTimeShift));
          } else {
            int oldNSamples=_maxNSamples;
            int newNSamples=oldNSamples;
            while(globalIndex>=newNSamples) newNSamples=newNSamples << 1;
            if(GeopsyCoreEngine::instance()->cache()->enlarge(this, newNSamples*sizeof(double)) ) {
              newSamples=new double [newNSamples];
              memcpy(newSamples, thisSamples, sizeof(double)*static_cast<size_t>(_nSamples));
              memset(newSamples+_nSamples, 0, static_cast<size_t>(newNSamples-_nSamples)*sizeof(double));
              delete [] (thisSamples-_startTimeShift);
              _maxNSamples=newNSamples;
            } else {
              App::log(tr("Cannot enlarge signal's buffer\n") );
              unlockSamples();
              return;
            }
          }
          // In all cases the shift to first element of the vector is null (either with rotate or new buffer allocation)
          _startTimeShift=0;
        } else {
          newSamples=thisSamples;
        }
        setSamples(nullptr);                    // To avoid ASSERT in SignalTemplate
        if(index==_nSamples) {                  // Adding samples just at the end, incremental computation of average
          averageAmplitude*=static_cast<double>(_nSamples);
        } else {                                // Force recomputation of average
          averageAmplitude=std::numeric_limits<double>::infinity();
        }
        int endIndex=index+nSamples;
        if(endIndex>_nSamples) setNSamples(endIndex);
        setSamples(newSamples);
        for(int i=index; i<endIndex; i++) {
          newSamples[i]=samples[i-index];
        }
        // Update max amplitude incrementally
        if(maxAmplitude!=std::numeric_limits<double>::infinity()) {
          double v;
          for(int i=endIndex-1; i>=index; i--) {
            v=fabs(newSamples[i]);
            if(v>maxAmplitude) maxAmplitude=v;
          }
        }
        // Update average amplitude incrementally
        if(averageAmplitude!=std::numeric_limits<double>::infinity()) {
          for(int i=endIndex-1; i>=index; i-- ) {
            averageAmplitude += newSamples[i];
          }
          averageAmplitude/=static_cast<double>(_nSamples);
        }
      UNLOCK_SAMPLES(this)
      // Commit average and maximum values smoothly out of main data lock
      _amplitudeMutex.lock();
      _maxAmplitude=maxAmplitude;
      _averageAmplitude=averageAmplitude;
      _amplitudeMutex.unlock();
    } else if(globalIndex>0 && _maxNSamples==0) { // If _nSamples==0 then _maxNSamples is not necessarily 0
                                                  // if a shiftT0() is called.
      ASSERT(_maxNSamples==0 && _startTimeShift==0);
      _nSamples=globalIndex;
      _maxNSamples=_nSamples;
      LOCK_SAMPLES(double, thisSamples, this)
        for(int i=0; i<index; i++) {
          thisSamples[i]=0.0;
        }
        for(int i=0; i<nSamples; i++) {
          thisSamples[i+index]=(double)samples[i];
        }
      UNLOCK_SAMPLES(this)
      _amplitudeMutex.lock();
      _maxAmplitude=std::numeric_limits<double>::infinity();
      _averageAmplitude=std::numeric_limits<double>::infinity();
      _amplitudeMutex.unlock();
    }
  }

  void DynamicSignal::set(const DateTime& t, const int * samples, int nSamples)
  {
    set<int>(t, samples, nSamples);
  }

  void DynamicSignal::set(const DateTime& t, const double * samples, int nSamples)
  {
    set<double>(t, samples, nSamples);
  }

  /*!
    Shift t0 by \a nSamples. \a nSamples will be lost and won't be accessible anymore by normal processings.
    t0() is modified accordingly, nSamples() is reduced by \a nSamples.
  */
  void DynamicSignal::shiftStartTime(int nSamples)
  {
    TRACE;
    ASSERT(nSamples>=0);
    if(_nSamples<nSamples) {
      if(_nSamples>0) {
        nSamples=_nSamples;
      } else {
        return;
      }
    }
    LOCK_SAMPLES(double, thisSamples, this)
      _startTime.addSeconds(nSamples*samplingPeriod());
      _startTimeShift+=nSamples;
      double * newSamples=thisSamples+nSamples;
      setSamples(nullptr); // To avoid ASSERT in SignalTemplate
      setNSamples(_nSamples-nSamples);
      setSamples(newSamples);
    UNLOCK_SAMPLES(this)
  }

  bool DynamicSignal::allocate() const
  {
    TRACE;
    if(!isAllocated()) {
      if(_maxNSamples>0) {
        setSamples(new double[_maxNSamples]);
        _startTimeShift=0;
        return true;
      } else return false;
    }
    return true;
  }

  void DynamicSignal::free() const
  {
    TRACE;
    if(isAllocated()) {
      delete [] (samples()-_startTimeShift);
      _startTimeShift=0;
      setSamples(nullptr);
    }
  }

  void DynamicSignal::setNSamples(int n)
  {
    DoubleSignal::setNSamples(n);
  }

} // namespace GeopsyCore
