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

#ifndef SIGNAL_H
#define SIGNAL_H

#include <QGpCoreMath.h>

#include "GeopsyCoreDLLExport.h"
#include "SignalFile.h"
#include "DoubleSignal.h"
#include "MetaData.h"
#include "MetaDataIndex.h"
#include "SparseTimeRange.h"
#include "MetaDataMap.h"
#include "MetaDataFactory.h"

namespace GeopsyCore {

class SubSignalPool;
class SignalDatabase;

class GEOPSYCORE_EXPORT Signal: public DoubleSignal, public SharedObject, public XMLClass
{
  TRANSLATIONS("Signal")
public:
  enum Components {Vertical, North, East, All, Horizontal, Time, Ignore, UndefinedComponent};
  enum PickWhat {Min=1, Max=2};
  enum AmplitudeUnits {UndefinedUnit, Displacement, Velocity, Acceleration};

  Signal(SignalDatabase * db);
  Signal(const Signal& sig, SignalDatabase * db=nullptr);
  virtual ~Signal();

  virtual const QString& xml_tagName() const {return xmlSignalTag;}
  static const QString xmlSignalTag;

  static Signal * newCopy(Signal * sig);

  void setTemporary();
  void copyBasicProperties(const Signal& p);

  bool operator<(const Signal& o) const {return compare(o)<0;}
  bool operator>(const Signal& o) const {return compare(o)>0;}
  bool operator==(const Signal& o) const {return compare(o)==0;}
  int compare(const Signal& o) const;

  // Modification control
  bool isHeaderModified() const {return _isHeaderModified;}
  void setHeaderModified(bool m) {_isHeaderModified=m;}
  inline bool warnReadOnlySamples() const;
  bool isReadOnlySamples() const {return _isReadOnlySamples;}
  void setReadOnlySamples(bool ro) {_isReadOnlySamples=ro;}
  void resetAmplitudes() const {_maxAmplitude=std::numeric_limits<double>::infinity(); _averageAmplitude=std::numeric_limits<double>::infinity();}

  // Functions to save signal values into a stream
  bool writeSeg2(QFile& f, double delay) const;
  bool writeAscii(QFile& f, int numInFile) const;
  bool writeSac(QDataStream& s) const;
  bool writeSegySu(QDataStream& s, int indexInFile, bool su) const;
  bool writeGse(QFile& f) const;
  bool writeMiniSeed(QFile& f) const;

  // Interface functions to data members
  QString name() const {return _name;}
  void setName(QString n) {_name=n;}
  Components component() const {return _component;}
  void setComponent(Components c) {_component=c;}
  static Components userComponent(QString c);
  static Components standardComponent(QString c);
  static QString userName(Components c);
  static QString standardName(Components c);
  static Components globalSeismographicNetworkComponent(const char * c);
  static QString componentLetter(Components c);
  QString componentUserName() const {return userName(_component);}
  QString componentStandardName() const {return standardName(_component);}
  QString nameComponent() const;
  inline virtual QString debugName() const;

  const Point& receiver() const {return _receiver;}
  void setReceiver(const Point& p) {_receiver=p;}
  const UtmZone& utmZone() const {return _utmZone;}
  void setUtmZone(const UtmZone& z) {_utmZone=z;}

  Point source() const;

  virtual void setNSamples(int n);
  virtual void setSamplingPeriod(double newval);
  void setSamplingFrequency(double newval);

  const DateTime& startTime() const {return _startTime;}
  void setStartTime(const DateTime& t);
  DateTime endTime() const {return _startTime.shifted(samplingPeriod()*_nSamples);}
  const SparseTimeRange& timeRange() const {return _timeRange;}
  void setTimeRange(const SparseTimeRange& r);
  DateTime roundTime(const DateTime& t) const;
  TIME t0AsTIME() const;
  TIME endTimeAsTIME() const;

  SignalFile * file() const {return _file;}
  void setFile(SignalFile * f) {_file=f;}
  bool isOriginalFile() const {if(file() && file() ->isOriginalFile()) return true; else return false;}

  int numberInFile() const {return _numberInFile;}
  void setNumberInFile(int i) {_numberInFile=i;}
  qint64 offsetInFile() const {return _offsetInFile;}
  void setOffsetInFile(qint64 o) {_offsetInFile=o;}
  int byteIncrement() const {return _byteIncrement;}
  void setByteIncrement(int i) {_byteIncrement=i;}

  double countPerVolt() const {return _countPerVolt;}
  void setCountPerVolt(double c);

  double voltPerCount() const {return 1.0/_countPerVolt;}
  void setVoltPerCount(double c) {setCountPerVolt(1.0/c);}

  double voltPerUnit() const {return _voltPerUnit;}
  void setVoltPerUnit(double c);

  double unitPerVolt() const {return 1.0/_voltPerUnit;}
  void setUnitPerVolt(double c) {setVoltPerUnit(1.0/c);}

  void fixVoltPerUnit(double c) {_voltPerUnit=c;}
  void fixCountPerVolt(double c) {_countPerVolt=c;}

  double unitPerCount() const {return 1.0/countPerUnit();}
  double countPerUnit() const {return _countPerVolt * _voltPerUnit;}

  int id() const {return _id;}
  void setId(int id);

  SignalDatabase * database() const {return _db;}
  void setDatabase(SignalDatabase * db) {_db=db;}

  AmplitudeUnits amplitudeUnit() const {return _amplitudeUnit;}
  void setAmplitudeUnit(AmplitudeUnits u) {_amplitudeUnit=u;}
  static AmplitudeUnits userAmplitudeUnit(QString u);
  static AmplitudeUnits standardAmplitudeUnit(QString u);
  static QString userName(AmplitudeUnits u);
  static QString standardName(AmplitudeUnits u);
  QString amplitudeUnitUserName() const {return userName(_amplitudeUnit);}
  QString amplitudeUnitStandardName() const {return standardName(_amplitudeUnit);}
  QString effectiveAmplitudeUnit() const;

  double maximumAmplitude(int itmin=0, int itmax=0) const;
  DateTime maximumAmplitudeAt(const TimeRange& r=TimeRange()) const;
  double averageAmplitude(int itmin=0, int itmax=0) const;
  inline double amplitudeAt(const DateTime& t) const;
  inline double interpoleAt(const DateTime& t) const;

  // Funtion to perform signal processing
  inline bool taper(const TimeRange& tw, const WindowFunctionParameters& param);
  void copySamplesFrom(const Signal * sig) {DoubleSignal::copySamplesFrom(sig);}
  inline void copySamplesFrom(const Signal * sig, const TimeRange& tw);
  Signal * cut(TimeRange r) const;
  bool shift(double dt);
  inline int add(const Signal * sig, const TimeRange& tw, bool checkInvalid=false, double invalidValue=std::numeric_limits<double>::infinity());
  double correlation(const Signal * sig, const TimeRange& tw);
  void correlation(const Signal * s1, const Signal * s2, double maxDelay);
  void normalizedCorrelation(const Signal * s1, const Signal * s2, double maxDelay);

  // Function to restore old databases (compatibility)
  bool read(FILE *f, SignalDatabase * db);

  virtual double * lockSamples();
  virtual const double * constLockSamples() const;
#ifdef LOCK_SAMPLES_DEBUG
  double * lockSamples(char * file, int line) {return DoubleSignal::lockSamples(file, line);}
  const double * constLockSamples(char * file, int line) const {return DoubleSignal::constLockSamples(file, line);}
#endif
  // MetaData interface
  template <class MetaDataClass> void shareMetaData(Signal * sig);
  template <class MetaDataClass> const MetaDataClass& metaData() const;
  template <class MetaDataClass> void setMetaData(const MetaDataClass& d);
  template <class MetaDataClass> MetaDataClass& beginModifyMetaData();
  template <class MetaDataClass> void endModifyMetaData(const MetaDataClass& d);
  const MetaDataMap& metaDataMap() const {return _optionalData;}

  QVariant header(const MetaDataIndex& index) const;
  bool setHeader(const MetaDataIndex& index, QVariant val);

  // XML serialization
  virtual void xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const;
  virtual void xml_writeChildren(XML_WRITECHILDREN_ARGS) const;
  virtual bool xml_setProperty(XML_SETPROPERTY_ARGS);
  virtual bool xml_polish(XML_POLISH_ARGS);
  virtual XMLMember xml_member(XML_MEMBER_ARGS);
protected:
  mutable double _maxAmplitude;
  mutable double _averageAmplitude;
  mutable Mutex _amplitudeMutex;

  uint _isHeaderModified:1;
  uint _isReadOnlySamples:1;
  uint _unused:30;

  DateTime _startTime;
  SparseTimeRange _timeRange;
private:
  int _id;    // Unique ID in the database
  UtmZone _utmZone;
  Point _receiver;
  QString _name;
  SignalFile * _file;
  int _numberInFile;
  qint64 _offsetInFile;
  int _byteIncrement;
  Components _component;
  MetaDataMap _optionalData;

  double _countPerVolt;
  double _voltPerUnit;
  AmplitudeUnits _amplitudeUnit;

  SignalDatabase * _db;

  bool writeError(const DoubleSignal * sig, QString logMessage) const;
  bool readError(const char * buf);

  // Functions to load samples from specific file formats into memory
  bool load(double * samples) const;
  bool loadGeopsySignal(double * samples) const;
  bool loadSeg2(double * samples) const;
  bool loadSegD(double * samples) const;
  bool loadSu(double * samples, QDataStream::ByteOrder bo) const;
  bool loadSegY(double * samples, QDataStream::ByteOrder bo) const;
  bool loadPasscalSegY(double * samples, QDataStream::ByteOrder bo) const;
  bool loadSac(double * samples, QDataStream::ByteOrder bo) const;
  bool loadRD3(double * samples) const;
  bool loadRadan(double * samples) const;
  bool loadGse2(double * samples) const;
  bool loadCity2(double * samples) const {return loadAscii(samples, "Loading City 2 values from %1");}
  bool loadAscii(double * samples, const QString &title) const;
  bool loadSismalp(double * samples) const;
  bool loadSyscom3Bytes(double * samples) const;
  bool loadSyscom2Bytes(double * samples) const;
  bool loadGuralpGcf(double * samples) const;
  bool loadWav(double * samples) const;
  bool loadMiniSeed(double * samples) const;
  bool loadAsciiOneColumn(double * samples) const;
  bool loadFourier(double * samples) const;
  static bool loadMultiChannelsWav(Signal * sig0, double * samples0);
  static bool loadMultiChannelsAscii(Signal * sig0, double * samples0);
  static bool loadMultiChannelsFourier(Signal * sig0, double * samples0);
  static SubSignalPool beginMultiChannelLoad(Signal * sig0, double * samples0,
                                                 int & nSig, double **& samplePtr);
  static void endMultiChannelLoad(Signal * sig0, const SubSignalPool& loadedSigs, SignalType typeInFile=DoubleSignal::Waveform);
  static void miniSeedRecordHandler (char *record, int reclen, void *f);
  bool writeSeg2Header(QFile& f, const QString& keyword,
                       const QString &sep, const QString& value,
                       const DoubleSignal * sig, qint16& blocksize) const;
};

inline bool Signal::warnReadOnlySamples() const
{
  if(_isReadOnlySamples) {
    App::log(tr("Trying to modify read only signal %1, aborted\n").arg(name()) );
    return true;
  } else return false;
}

inline QString Signal::debugName() const
{
  return _name + "_" + componentLetter(_component) + QString::number(_id);
}

inline double Signal::amplitudeAt(const DateTime& t) const
{
  return DoubleSignal::amplitudeAt(_startTime.secondsTo(t));
}

inline double Signal::interpoleAt(const DateTime& t) const
{
  return DoubleSignal::interpoleAt(_startTime.secondsTo(t));
}

inline bool Signal::taper(const TimeRange& tw, const WindowFunctionParameters& param)
{
  return DoubleSignal::taper(_startTime.secondsTo(tw.start()),
                             _startTime.secondsTo(tw.end()), param);
}

inline void Signal::copySamplesFrom(const Signal * sig, const TimeRange& tw)
{
  DoubleSignal::copySamplesFrom(sig, sig->_startTime.secondsTo(tw.start()),
                                _startTime.secondsTo(tw.start()), tw.lengthSeconds());
}

inline int Signal::add(const Signal * sig, const TimeRange& tw, bool checkInvalid, double invalidValue)
{
  return DoubleSignal::add(sig, sig->_startTime.secondsTo(tw.start()),
                           _startTime.secondsTo(tw.start()), tw.lengthSeconds(),
                           checkInvalid, invalidValue);
}

inline double Signal::correlation(const Signal * sig, const TimeRange& tw)
{
  return DoubleSignal::correlation(sig, sig->_startTime.secondsTo(tw.start()),
                                   _startTime.secondsTo(tw.start()), tw.lengthSeconds());
}

template <class MetaDataClass> void Signal::shareMetaData(Signal * sig)
{
  _optionalData.remove(MetaDataClass::staticId());
  _optionalData.add(sig->_optionalData.data(MetaDataClass::staticId()));
}

template <class MetaDataClass> void Signal::setMetaData(const MetaDataClass& d)
{
  if(d==MetaDataClass::staticDefaultValue()) {
    _optionalData.remove(MetaDataClass::staticId());
  } else {
    static_cast<MetaDataClass&>(*_optionalData.data(MetaDataClass::staticId()))=d;
  }
  setHeaderModified(true);
}

template <class MetaDataClass> MetaDataClass& Signal::beginModifyMetaData()
{
  return static_cast<MetaDataClass&>(*_optionalData.data(MetaDataClass::staticId()));
}

template <class MetaDataClass> void Signal::endModifyMetaData(const MetaDataClass& d)
{
  if(d==MetaDataClass::staticDefaultValue()) {
    _optionalData.remove(MetaDataClass::staticId());
  }
  setHeaderModified(true);
}

template <class MetaDataClass> const MetaDataClass& Signal::metaData() const
{
  const MetaDataClass * d=static_cast<const MetaDataClass *>(_optionalData.data(MetaDataClass::staticId()));
  return *d;
}

} // namespace GeopsyCore

#endif // SIGNAL_H
