/***************************************************************************
**
**  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 SIGNALTEMPLATE_H
#define SIGNALTEMPLATE_H

#include <QGpCoreMath.h>

#include "GeopsyCoreEngine.h"
#include "GeopsyCoreDLLExport.h"

//#define LOCK_SAMPLES_DEBUG

namespace GeopsyCore {

template <class sampleType> class GEOPSYCORE_EXPORT SignalTemplate : public CacheItem
{
public:
  SignalTemplate( );
  SignalTemplate(int n);
  SignalTemplate(const SignalTemplate<sampleType>& o);
  virtual ~SignalTemplate();

#ifdef LOCK_SAMPLES_DEBUG
  sampleType * lockSamples(char * file, int line);
  const sampleType * constLockSamples(char * file, int line) const;
  void unlockSamples(char * file, int line) const;
#endif

  virtual sampleType * lockSamples();
  virtual const sampleType * constLockSamples() const;
  virtual inline void unlockSamples() const;
  void freeSamples();
  // Properties
  int nSamples() const {return _nSamples;}
  virtual void setNSamples(int n) {ASSERT(!_samples); _nSamples=n;}
  // Funtion to perform signal processing
  bool multiply(const SignalTemplate<sampleType> * sig);
  bool multiply(sampleType value);
  void subtractSignal(const SignalTemplate<sampleType> * sig);
  void range(sampleType& min, sampleType& max) const;
  sampleType value(int index) const;
  sampleType maximum() const;
  sampleType average(int startAt, int nSamples) const;
  sampleType average2(int startAt, int nSamples) const;
  sampleType variance(int startAt, int nSamples) const;
  void copySamplesFrom(const SignalTemplate<sampleType> * sig);
  bool copySamplesFrom(const SignalTemplate<sampleType> * sig,
                        int sigStart, int thisStart, int nToCopy);
  int add(const SignalTemplate<sampleType> * sig,
          int sigStart=0, int thisStart=0, int nToCopy=-1,
          bool checkInvalid=false, sampleType invalidValue=0);
  void initValues(sampleType value, int startAt, int nSamples);
  int findValue(sampleType value, int from, bool whileNotFound=false) const;

  double dataSizeMb() const {return static_cast<double>(dataSize())/(1024.0*1024.0);}
protected:
  int _nSamples;

  // Used only by Signal::lock and DynamicSignal waiting another object pattern for signal
  sampleType * samples() const {return _samples;}
  void setSamples(sampleType * s) const {_samples=s;}

  void adjustLimits(const SignalTemplate<sampleType> * sig,
                     int& sigStart, int& thisStart, int& nToCopy) const;
  virtual bool isAllocated() const {return _samples;}
  virtual qint64 dataSize() const {return _nSamples*sizeof(sampleType);}
private:
  mutable sampleType *_samples;
  // Memory management for samples
  virtual bool allocate() const;
  virtual void free() const;
  virtual void save(QDir& d) const;
  virtual void load(QDir& d) const;
};

/*!
  General function to be called before using the samples
*/
template <class sampleType>
sampleType * SignalTemplate<sampleType>::lockSamples()
{
  lockAdmin();
  if(isAllocated() || GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
    lockData();
    unlockAdmin();
    return _samples;
  } else {
    unlockAdmin();
    return 0;
  }
}


/*!
  General function to be called before using the samples
*/
template <class sampleType>
const sampleType * SignalTemplate<sampleType>::constLockSamples() const
{
  lockAdmin();
  if(isAllocated() || GeopsyCoreEngine::instance()->cache()->makeAvailable(this)) {
    constLockData();
    unlockAdmin();
    return _samples;
  } else {
    unlockAdmin();
    return 0;
  }
}

/*!
 General function to be called before using the samples
*/
template <class sampleType>
inline void SignalTemplate<sampleType>::unlockSamples() const
{
  unlockData();
}

#ifdef LOCK_SAMPLES_DEBUG
template <class sampleType>
sampleType * SignalTemplate<sampleType>::lockSamples(char * file, int line)
{
  printf( "Lock samples of signal %p at %s:%i\n", this, file, line);
  return lockSamples();
}

template <class sampleType>
const sampleType * SignalTemplate<sampleType>::constLockSamples(char * file, int line) const
{
  printf( "Const lock samples of signal %p at %s:%i\n", this, file, line);
  return constLockSamples();
}

template <class sampleType>
void SignalTemplate<sampleType>::unlockSamples(char * file, int line) const
{
  unlockSamples();
  printf( "Unlock samples of signal %p at %s:%i\n", this, file, line);
}

#define LOCK_SAMPLES(type,samp,sig) \
  type * samp=(sig)->lockSamples(__FILE__, __LINE__); \
  if(samp) {
#define CONST_LOCK_SAMPLES(type,samp,sig) \
  const type * samp=(sig)->constLockSamples(__FILE__, __LINE__); \
  if(samp) {
#define UNLOCK_SAMPLES(sig) \
    (sig)->unlockSamples(__FILE__, __LINE__); \
  }
#else
#define LOCK_SAMPLES(type,samp,sig) \
  type * samp=(sig)->lockSamples(); \
  if(samp) {
#define CONST_LOCK_SAMPLES(type,samp,sig) \
  const type * samp=(sig)->constLockSamples(); \
  if(samp) {
#define UNLOCK_SAMPLES(sig) \
    (sig)->unlockSamples(); \
  }
#endif

template <class sampleType>
SignalTemplate<sampleType>::SignalTemplate( )
{
  _samples=0;
  _nSamples=0;
}

template <class sampleType>
SignalTemplate<sampleType>::SignalTemplate(int n)
{
  _samples=0;
  _nSamples=n;
}

template <class sampleType>
SignalTemplate<sampleType>::SignalTemplate(const SignalTemplate<sampleType>& o) :
    CacheItem(o)
{
  _nSamples=o._nSamples;
  _samples=0;
}

template <class sampleType>
SignalTemplate<sampleType>::~SignalTemplate()
{
  GeopsyCoreEngine::instance()->cache()->free(this, false);
}

template <class sampleType>
void SignalTemplate<sampleType>::freeSamples()
{
  lockData();
  if(isAllocated()) {
    GeopsyCoreEngine::instance()->cache()->free(this, false);
  }
  unlockData();
}

template <class sampleType>
bool SignalTemplate<sampleType>::allocate() const
{
  TRACE;
  if(!_samples) {
    if(_nSamples<=0) {
      // Allocate at least one sample
      _samples=new sampleType [1];
      ASSERT(_samples);
    } else {
      _samples=new sampleType [_nSamples];
      ASSERT(_samples);
    }
  }
  return true;
}

template <class sampleType>
void SignalTemplate<sampleType>::free() const
{
  TRACE;
  if(_samples) {
    delete [] _samples;
    _samples=0;
  }
}

/*!
  Samples must be allocated
*/
template <class sampleType>
void SignalTemplate<sampleType>::save(QDir& d) const
{
  TRACE;
  QFile f(d.absoluteFilePath(swapFileName()) );
  if(f.open(QIODevice::WriteOnly) ) {
    f.write(( const char * ) _samples, dataSize());
  } else {
    App::log(tr("Cannot save temporary file, check disk space!\n"));
  }
}

/*!
  Samples must be allocated
*/
template <class sampleType>
void SignalTemplate<sampleType>::load(QDir& d) const
{
  TRACE;
  QFile f(d.absoluteFilePath(swapFileName()) );
  if(f.open(QIODevice::ReadOnly) ) {
    f.read((char *)_samples, dataSize());
  } else {
    App::log(tr("Cannot load temporary file\n"));
  }
}

template <class sampleType>
bool SignalTemplate<sampleType>::multiply(sampleType value)
{
  TRACE;
  bool ok=false;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    for(int i=0; i<_nSamples; i++) {
      thisSamples[i]*=value;
    }
    ok=true;
  UNLOCK_SAMPLES(this)
  return ok;
}

template <class sampleType>
bool SignalTemplate<sampleType>::multiply(const SignalTemplate<sampleType> * sig)
{
  TRACE;
  if(nSamples()!=sig->nSamples()) {
    return false;
  }
  CacheProcess cp;
  cp << this << sig;
  bool ok=false;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    CONST_LOCK_SAMPLES(sampleType, sigSamples, sig)
      for(int i=0; i<_nSamples; i++) {
        thisSamples[i]*=sigSamples[i];
      }
      ok=true;
    UNLOCK_SAMPLES(sig)
  UNLOCK_SAMPLES(this)
  return ok;
}

template <class sampleType>
void SignalTemplate<sampleType>::subtractSignal(const SignalTemplate<sampleType> * sig)
{
  TRACE;
  CacheProcess cp;
  cp << this << sig;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    CONST_LOCK_SAMPLES(sampleType, sigSamples, sig)
      int nSamples=_nSamples;
      if(sig->nSamples() < _nSamples) nSamples=sig->nSamples();
      for(int i=0; i < nSamples; i++ ) thisSamples[ i ] -= sigSamples[ i ];
    UNLOCK_SAMPLES(sig)
  UNLOCK_SAMPLES(this)
}

template <class sampleType>
void SignalTemplate<sampleType>::range(sampleType& min, sampleType& max) const
{
  TRACE;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    min=thisSamples[ 0 ];
    max=thisSamples[ 0 ];
    for(int i=0; i < _nSamples; i++ ) {
      if(thisSamples[ i ] < min) min=thisSamples[ i ];
      if(thisSamples[ i ] > max) max=thisSamples[ i ];
    }
  UNLOCK_SAMPLES(this)
}

template <class sampleType>
void SignalTemplate<sampleType>::copySamplesFrom(const SignalTemplate<sampleType> * sig)
{
  TRACE;
  if(sig->_nSamples!=_nSamples) return ;
  CacheProcess cp;
  cp << this << sig;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    CONST_LOCK_SAMPLES(sampleType, sigSamples, sig)
      memcpy(thisSamples, sigSamples, _nSamples * sizeof(sampleType) );
    UNLOCK_SAMPLES(sig)
  UNLOCK_SAMPLES(this)
}

template <class sampleType>
void SignalTemplate<sampleType>::adjustLimits(const SignalTemplate<sampleType> * sig,
                                                  int& sigStart, int& thisStart, int& nToCopy) const
{
  TRACE;
  // If signal have no common time range return
  if(sigStart >= sig->_nSamples ||
       thisStart >= _nSamples ||
       sigStart + nToCopy < 0 ||
       thisStart + nToCopy < 0) {
     nToCopy=0;
     return;
  }
  if(sigStart < 0) {
    nToCopy += sigStart;
    thisStart -= sigStart;
    sigStart=0;
  }
  if(thisStart < 0) {
    nToCopy += thisStart;
    sigStart -= thisStart;
    thisStart=0;
  }
  if(thisStart + nToCopy >= _nSamples) nToCopy=_nSamples - thisStart;
  if(sigStart + nToCopy >= sig->_nSamples) nToCopy=sig->_nSamples - sigStart;
}

template <class sampleType>
bool SignalTemplate<sampleType>::copySamplesFrom(const SignalTemplate<sampleType> * sig,
                                                 int sigStart, int thisStart, int nToCopy)
{
  TRACE;
  adjustLimits(sig, sigStart, thisStart, nToCopy);
  if(nToCopy<=0) {
    return true;
  }
  CacheProcess cp;
  cp << this << sig;
  bool ret=false;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    CONST_LOCK_SAMPLES(sampleType, sigSamples, sig)
      memcpy(thisSamples+thisStart, sigSamples+sigStart, nToCopy * sizeof(sampleType));
      ret=true;
    UNLOCK_SAMPLES(sig)
  UNLOCK_SAMPLES(this)
  return ret;
}

template <class sampleType>
int SignalTemplate<sampleType>::add(const SignalTemplate<sampleType> * sig,
                                    int sigStart, int thisStart, int nToCopy,
                                    bool checkInvalid, sampleType invalidValue)
{
  TRACE;
  if(nToCopy==-1) nToCopy=nSamples();
  adjustLimits(sig, sigStart, thisStart, nToCopy);
  if(nToCopy<=0) return 0;
  int nCommon=0;
  CacheProcess cp;
  cp << this << sig;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    CONST_LOCK_SAMPLES(sampleType, sigSamples, sig)
      sampleType * thisPtr=thisSamples + thisStart;
      const sampleType * sigPtr=sigSamples + sigStart;
      sampleType * endPtr=thisPtr + nToCopy;
      if(checkInvalid) {
        for( ;thisPtr < endPtr;thisPtr++, sigPtr++ ) {
          if(( *thisPtr)==invalidValue) ( *thisPtr)=( *sigPtr);
          else {
            nCommon++;
            ( *thisPtr) += ( *sigPtr);
          }
        }
      } else {
        for( ;thisPtr < endPtr;thisPtr++, sigPtr++ )
          ( *thisPtr) += ( *sigPtr);
      }
    UNLOCK_SAMPLES(sig)
  UNLOCK_SAMPLES(this)
  return nCommon;
}

template <class sampleType>
sampleType SignalTemplate<sampleType>::value(int index) const
{
  TRACE;
  double v=0.0;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    v=thisSamples[index];
  UNLOCK_SAMPLES(this)
  return v;
}

template <class sampleType>
sampleType SignalTemplate<sampleType>::maximum() const
{
  TRACE;
  sampleType max=0;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    for(int i=0;i < _nSamples;i++ ) {
      if(thisSamples[ i ] > 0) {
        if(thisSamples[ i ] > max) max=thisSamples[ i ];
      } else {
        if(thisSamples[ i ] < -max) max=-thisSamples[ i ];
      }
    }
  UNLOCK_SAMPLES(this)
  return max;
}

template <class sampleType>
sampleType SignalTemplate<sampleType>::average(int startAt, int nSamples) const
{
  TRACE;
  if(startAt >= _nSamples) return 0;
  int endAt=startAt + nSamples;
  if(endAt > _nSamples) endAt=_nSamples;
  if(startAt < 0) startAt=0;
  sampleType average=0;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    for(int i=startAt;i < endAt;i++ ) average += thisSamples[ i ];
  UNLOCK_SAMPLES(this)
  return average/nSamples;
}

template <class sampleType>
sampleType SignalTemplate<sampleType>::average2(int startAt, int nSamples) const
{
  TRACE;
  if(startAt >= _nSamples) return 0;
  int endAt=startAt + nSamples;
  if(endAt > _nSamples) endAt=_nSamples;
  if(startAt < 0) startAt=0;
  sampleType average2=0;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    for(int i=startAt;i < endAt;i++ ) {
      average2 += thisSamples[ i ] * thisSamples[ i ];
    }
  UNLOCK_SAMPLES(this)
  return average2/(nSamples-1);
}

template <class sampleType>
sampleType SignalTemplate<sampleType>::variance(int startAt, int nSamples) const
{
  double aver=average(startAt, nSamples);
  return average2(startAt, nSamples) - nSamples*aver*aver/(nSamples-1);
}

template <class sampleType>
void SignalTemplate<sampleType>::initValues(sampleType value, int startAt, int nSamples)
{
  TRACE;
  if(startAt >= _nSamples) return ;
  int endAt=startAt + nSamples;
  if(endAt > _nSamples) endAt=_nSamples;
  if(startAt < 0) startAt=0;
  LOCK_SAMPLES(sampleType, thisSamples, this)
    for(int i=startAt;i < endAt;i++ ) thisSamples[ i ]=value;
  UNLOCK_SAMPLES(this)
}

template <class sampleType>
int SignalTemplate<sampleType>::findValue(sampleType value, int from, bool whileNotFound) const
{
  TRACE;
  int i=-1;
  CONST_LOCK_SAMPLES(sampleType, thisSamples, this)
    if(whileNotFound) {
      for(i=from;i < _nSamples;i++ ) {
        if(thisSamples[ i ]!=value) {
          break;
        }
      }
    } else {
      for(i=from;i < _nSamples;i++ ) {
        if(thisSamples[ i ]==value) {
          break;
        }
      }
    }
  UNLOCK_SAMPLES(this)
  return i;
}

} // namespace GeopsyCore

#endif // SIGNALTEMPLATE_H
