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

#include <math.h>

#include <QGpCoreTools.h>
#include "StationSignals.h"
#include "SparseKeepSignal.h"
#include "KeepSignal.h"

namespace GeopsyCore {

StationSignals::StationSignals(Components c)
{
  TRACE;
  _components=c;
  if(_components==UndefinedComponent)
    _signals=nullptr;
  else
    _signals=new SubSignalPool[nComponents()];
}

StationSignals::~StationSignals()
{
  TRACE;
  delete [] _signals;
}

/*!
  Organise a subPool with signals sorted by station
*/
void StationSignals::organizeSubPool(SubSignalPool * subPool)
{
  TRACE;
  SortKey::clear();
  SortKey::add(MetaDataFactory::Name);
  SortKey::add(MetaDataFactory::UtmZone);
  SortKey::add(MetaDataFactory::ReceiverX);
  SortKey::add(MetaDataFactory::ReceiverY);
  SortKey::add(MetaDataFactory::Component);
  SortKey::add(MetaDataFactory::StartTime);
  subPool->sort();
}

/*!
  Modify number of components. Never remove components. Always add if necessary.
*/
void StationSignals::setComponents(Components c)
{
  TRACE;
  if(c==_components || c==UndefinedComponent) return;
  switch(_components) {
  case UndefinedComponent:
    _components=c;
    _signals=new SubSignalPool[nComponents()];
    break;
  case VerticalComponent:
    if(c==HorizontalComponent || c==AllComponent) {
      _components=AllComponent;
      SubSignalPool * s=new SubSignalPool[nComponents()];
      s[0]=_signals[0];
      delete [] _signals;
      _signals=s;
    }
    break;
  case HorizontalComponent:
    if(c==VerticalComponent || c==AllComponent) {
      _components=AllComponent;
      SubSignalPool * s=new SubSignalPool[nComponents()];
      s[1]=_signals[0];
      s[2]=_signals[1];
      delete [] _signals;
      _signals=s;
    }
    break;
  case AnySingleComponent:
  case AnyComponent:
  case AllComponent:
    break;
  }
}

/*!
  Add signal \a sig to this station. Return false if component of \a sig is not accepted.
*/
bool StationSignals::addSignal(Signal * sig)
{
  TRACE;
  // Identify component index
  int iComp;
  SAFE_UNINITIALIZED(iComp,0)
  switch(_components) {
  case VerticalComponent:
    if(sig->component()!=Signal::Vertical) return false;
    iComp=0;
    break;
  case HorizontalComponent:
    switch(sig->component()) {
    case Signal::North:
      iComp=0;
      break;
    case Signal::East:
      iComp=1;
      break;
    default:
      return false;
    }
    break;
  case AllComponent:
    switch(sig->component()) {
    case Signal::Vertical:
      iComp=0;
      break;
    case Signal::North:
      iComp=1;
      break;
    case Signal::East:
      iComp=2;
      break;
    default:
      return false;
    }
    break;
  case AnySingleComponent:
  case AnyComponent:
    iComp=0;
    break;
  case UndefinedComponent:
    return false;
  }
  // Make sure signals are in time (needed for signal composition)
  SubSignalPool& subPool=_signals[iComp];
  sig->fastFourierTransform(Signal::Waveform);
  subPool.addSignal(sig);
  App::log(tr("Add signal id %1 to component %2 of station %3 at %4\n")
                .arg(sig->id())
                .arg(Signal::userName(sig->component()) )
                .arg(name())
                .arg(coordinates().toString(2, 'f')));
  return true;
}

/*!
  Check that all components are available
*/
bool StationSignals::hasAllComponents() const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    if(_signals[iComp].isEmpty()) {
      App::log(tr("Missing %1 component for station %2.\n")
                    .arg(Signal::userName(component( iComp, _components)))
                    .arg(name()));
      return false;
    }
  }
  return true;
}

/*!
  Return true if signal \a sig is one of the original signals of this station
*/
bool StationSignals::contains(Signal * sig) const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    if(_signals[iComp].contains(sig)) {
      return true;
    }
  }
  return false;
}

/*!
  Return minimum starting time for all components
*/
DateTime StationSignals::minTime() const
{
  TRACE;
  int nComp=nComponents();
  DateTime t=DateTime::maximumTime;
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=_signals[iComp];
    for(SubSignalPool::const_iterator it=subPool.begin(); it!=subPool.end(); it++) {
      Signal * sig=*it;
      if(sig->startTime()<t) t=sig->startTime();
    }
  }
  return t;
}

/*!
  Return signal unit. If various units are found, "n/a" is returned.
*/
QString StationSignals::unit() const
{
  TRACE;
  QString u;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=_signals[iComp];
    for(SubSignalPool::const_iterator it=subPool.begin(); it!=subPool.end(); it++) {
      Signal * sig=*it;
      if(u.isNull()) {
        u=sig->effectiveAmplitudeUnit();
      } else if(u!=sig->effectiveAmplitudeUnit()) {
        return "n/a";
      }
    }
  }
  return u;
}

/*!
  Return maximum ending time for all components
*/
DateTime StationSignals::maxTime() const
{
  TRACE;
  int nComp=nComponents();
  DateTime t=DateTime::minimumTime;
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=_signals[iComp];
    for(SubSignalPool::const_iterator it=subPool.begin(); it!=subPool.end(); it++) {
      Signal * sig=*it;
      if(sig->endTime()>t) t=sig->endTime();
    }
  }
  return t;
}

int StationSignals::nComponents(Components c)
{
  TRACE;
  switch (c) {
  case VerticalComponent:
  case AnySingleComponent:
  case AnyComponent:
    return 1;
  case HorizontalComponent:
    return 2;
  case AllComponent:
    return 3;
  default:
    return 0;
  }
}

Signal::Components StationSignals::component(int index, Components c)
{
  switch (c) {
  case VerticalComponent:
    return Signal::Vertical;
  case HorizontalComponent:
    switch (index) {
    case 0: return Signal::North;
    default: return Signal::East;
    }
  case AllComponent:
    switch (index) {
    case 0: return Signal::Vertical;
    case 1: return Signal::North;
    default: return Signal::East;
    }
  case AnySingleComponent:
  case AnyComponent:
  case UndefinedComponent:
    break;
  }
  return Signal::UndefinedComponent;
}

Signal * StationSignals::firstValidSignal() const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    if(!_signals[iComp].isEmpty()) return _signals[iComp].first();
  }
  return nullptr;
}

Signal * StationSignals::lastValidSignal() const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    if(!_signals[iComp].isEmpty()) return _signals[iComp].last();
  }
  return nullptr;
}

/*!
  Get the range where all components are available
*/
SparseTimeRange StationSignals::timeRange(const TimeRange& r) const
{
  TRACE;
  SparseTimeRange globalRange(r);
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=originals(iComp);
    SparseTimeRange compRange;
    for(SubSignalPool::const_iterator itSig=subPool.begin();itSig!=subPool.end();++itSig) {
      compRange.add((*itSig)->timeRange(), false);
    }
    globalRange=globalRange.intersection(compRange);
  }
  return globalRange;
}

/*!
  Check sampling in all sub-ranges. In case of incompatibilities, discard the sub-ranges.
*/
void StationSignals::setSampling(SparseKeepSignal * keep) const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=originals(iComp);
    for(SubSignalPool::const_iterator itSig=subPool.begin();itSig!=subPool.end();++itSig) {
      const Signal * sig=*itSig;
      TimeRange r=sig->timeRange().range();
      if(!keep->setSampling(r, sig->samplingPeriod())) {
        App::log(tr("Station '%1': incompatible sampling from %2 to %3 s, skipping range\n")
                     .arg(name()).arg(r.start().toString()).arg(r.end().toString()));
        keep->remove(r);
      }
    }
  }
}

/*!
  Values of \a keep are initially all to 1. This function toogles them to 0 if \a param conditions are not
  satisfied.
*/
void StationSignals::setKeep(SparseKeepSignal * keep, const WindowingParameters& param, int stationIndex) const
{
  TRACE;
  int nComp=nComponents();
  for(int iComp=0; iComp<nComp; iComp++) {
    const SubSignalPool& subPool=originals(iComp);
    for(SubSignalPool::const_iterator itSig=subPool.begin();itSig!=subPool.end(); ++itSig) {
      const Signal * sig=*itSig;
      QVector<KeepSignal *> keeps=keep->keeps(sig->timeRange().range());
      for(QVector<KeepSignal *>::iterator itKeep=keeps.begin(); itKeep!=keeps.end(); itKeep++) {
        KeepSignal * keepSig=*itKeep;
        double delay=keepSig->startTime().secondsTo(sig->startTime());
        if(param.rawSignalAntiTrigger().enabled &&
           param.rawComponent(iComp) &&
           param.rawStation(stationIndex)) {
          DoubleSignal * tempsig=new DoubleSignal( *sig);
          tempsig->copySamplesFrom(sig);
          tempsig->abs();
          tempsig->staltaToKeep(param.rawSignalAntiTrigger(), keepSig, delay);
          delete tempsig;
        }
        if(param.filterSignalAntiTrigger().enabled &&
           param.filterComponent(iComp) &&
           param.filterStation(stationIndex)) {
          DoubleSignal * tempsig=new DoubleSignal( *sig);
          tempsig->copySamplesFrom(sig);
          tempsig->filter(param.filter());
          tempsig->abs();
          tempsig->staltaToKeep(param.filterSignalAntiTrigger(), keepSig, delay);
          delete tempsig;
        }
        switch(param.badSampleThresholdType()) {
        case WindowingParameters::NoSampleThreshold:
          break;
        case WindowingParameters::AbsoluteSampleThreshold:
          sig->clipMaxToKeep(param.badSampleThreshold(), keepSig, delay);
          break;
        case WindowingParameters::RelativeSampleThreshold:
          sig->clipMaxToKeep(param.badSampleThreshold()*(sig->maximum()-sig->average(0, sig->nSamples())), keepSig, delay);
          break;
        }
      }
    }
  }
}

} // namespace GeopsyCore
