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

#include "WindowingParameters.h"

namespace GeopsyCore {

  /*!

  */
  WindowingParameters::WindowingParameters()
    : AbstractParameters()
  {
    TRACE;
    _componentCount=0;
    _stationCount=0;

    _rawComponent=nullptr;
    _filterComponent=nullptr;
    _rawStation=nullptr;
    _filterStation=nullptr;

    _lengthType=Exactly;
    _minimumLength=0.0;
    _maximumLength=0.0;
    _periodCount=0.0;
    _maximumWindowCount=0;
    _powerOfTwo=0;

    _overlap=0.0;
    _badSampleThresholdType=NoSampleThreshold;
    _badSampleThresholdValue=100.0;
    _badSampleTolerance=0.0;
    _badSampleGap=0.0;
    _rawSignalAntiTrigger.enabled=false;
    _filterSignalAntiTrigger.enabled=false;

    _seismicEventTrigger=false;
    _seismicEventDelay=-0.1;
  }

  /*!

  */
  WindowingParameters::WindowingParameters(const WindowingParameters& o)
    : AbstractParameters(o), _filter(o._filter)
  {
    TRACE;
    copyBasicValues(o);
    _rawComponent=copyComponentStation(o._rawComponent, _componentCount);
    _filterComponent=copyComponentStation(o._filterComponent, _componentCount);
    _rawStation=copyComponentStation(o._rawStation, _stationCount);
    _filterStation=copyComponentStation(o._filterStation, _stationCount);

    memcpy(&_rawSignalAntiTrigger, &o._rawSignalAntiTrigger, sizeof(ShortLongTermAverage));
    memcpy(&_filterSignalAntiTrigger, &o._filterSignalAntiTrigger, sizeof(ShortLongTermAverage));
  }

  WindowingParameters::~WindowingParameters()
  {
    TRACE;
    delete [] _rawComponent;
    delete [] _filterComponent;
    delete [] _rawStation;
    delete [] _filterStation;
  }

  void WindowingParameters::operator=(const WindowingParameters& o)
  {
    TRACE;
    copyBasicValues(o);
    _rawComponent=copyComponentStation(o._rawComponent, _componentCount);
    _filterComponent=copyComponentStation(o._filterComponent, _componentCount);
    _rawStation=copyComponentStation(o._rawStation, _stationCount);
    _filterStation=copyComponentStation(o._filterStation, _stationCount);

    memcpy(&_rawSignalAntiTrigger, &o._rawSignalAntiTrigger, sizeof(ShortLongTermAverage));
    memcpy(&_filterSignalAntiTrigger, &o._filterSignalAntiTrigger, sizeof(ShortLongTermAverage));
  }

  void WindowingParameters::copyBasicValues(const WindowingParameters& o)
  {
    TRACE;
    _componentCount=o._componentCount;
    _stationCount=o._stationCount;

    _lengthType=o._lengthType;
    _minimumLength=o._minimumLength;
    _maximumLength=o._maximumLength;
    _periodCount=o._periodCount;
    _maximumWindowCount=o._maximumWindowCount;
    _powerOfTwo=o._powerOfTwo;

    _overlap=o._overlap;
    _badSampleThresholdType=o._badSampleThresholdType;
    _badSampleThresholdValue=o._badSampleThresholdValue;
    _badSampleTolerance=o._badSampleTolerance;
    _badSampleGap=o._badSampleGap;

    _seismicEventTrigger=o._seismicEventTrigger;
    _seismicEventDelay=o._seismicEventDelay;
  }

  bool * WindowingParameters::copyComponentStation(bool * v, int count)
  {
    TRACE;
    bool * newV;
    if(v) {
      newV=new bool[count];
      memcpy(newV, v, sizeof(bool)*count);
    } else {
      newV=nullptr;
    }
    return newV;
  }

  void WindowingParameters::setComponentCount(int n)
  {
    TRACE
    _componentCount=n;
    delete [] _rawComponent;
    delete [] _filterComponent;
    if(_componentCount>0) {
      _rawComponent=new bool[_componentCount];
      for(int i=0; i< _componentCount; i++) {
        _rawComponent[i]=false;
      }
      _filterComponent=new bool[_componentCount];
      for(int i=0; i<_componentCount; i++) {
        _filterComponent[i]=false;
      }
    } else {
      _rawComponent=nullptr;
      _filterComponent=nullptr;
    }
  }

  void WindowingParameters::setStationCount(int n)
  {
    TRACE
    _stationCount=n;
    delete [] _rawStation;
    delete [] _filterStation;
    if(_stationCount>0) {
      _rawStation=new bool[_stationCount];
      for(int i=0; i<_stationCount; i++) {
        _rawStation[i]=false;
      }
      _filterStation=new bool[_stationCount];
      for(int i=0; i<_stationCount; i++) {
        _filterStation[i]=false;
      }
    } else {
      _rawStation=nullptr;
      _filterStation=nullptr;
    }
  }

  void WindowingParameters::setLength(double min, double max)
  {
    TRACE;
    _lengthType=AtLeast;
    if(min<max) {
      _minimumLength=min;
      _maximumLength=max;
    } else {
      _maximumLength=min;
      _minimumLength=max;
    }
  }

  void WindowingParameters::setLength(double l)
  {
    TRACE;
    _lengthType=Exactly;
    _minimumLength=l;
    _maximumLength=l;
  }

  void WindowingParameters::setPeriodCount(double n)
  {
    TRACE;
    _lengthType=FrequencyDependent;
    _periodCount=n;
    _minimumLength=n; // To avoid warning for null time windows in pre-processings
    _maximumLength=n;
  }

  QString WindowingParameters::lengthTypeString() const
  {
    TRACE;
    switch(_lengthType) {
    case Exactly:
      break;
    case AtLeast:
      return "AtLeast";
    case FrequencyDependent:
      return "FrequencyDependent";
    }
    return "Exactly";
  }

  bool WindowingParameters::setLengthType(const QString& l)
  {
    TRACE;
    if(!l.isEmpty()) {
      QString ll=l.toLower();
      switch(ll[0].unicode()) {
      case 'a':
        if(ll=="atleast" ||
           ll=="at least") { // Compatibility
          _lengthType=AtLeast;
          return true;
        }
        break;
      case 'e':
        if(ll=="exactly") {
          _lengthType=Exactly;
          return true;
        }
        break;
      case 'f':
        if(ll=="frequencydependent" ||
           ll=="freq. dep.") {  // Compatibility
          _lengthType=FrequencyDependent;
          return true;
        }
        break;
      default:
        break;
      }
    }
    App::log(tr("Unrecognized length type: %1\n").arg(l) );
    return false;
  }

  bool WindowingParameters::setBadSampleThresholdType(const QString& t)
  {
    if(!t.isEmpty()) {
      QString lt=t.toLower();
      switch(lt[0].unicode()) {
      case 'a':
        if(lt=="absolutesamplethreshold") {
          _badSampleThresholdType=AbsoluteSampleThreshold;
          return true;
        }
        break;
      case 'n':
        if(lt=="nosamplethreshold") {
          _badSampleThresholdType=NoSampleThreshold;
          return true;
        }
        break;
      case 'r':
        if(lt=="relativesamplethreshold") {
          _badSampleThresholdType=RelativeSampleThreshold;
          return true;
        }
        break;
      default:
        break;
      }
    }
    App::log(tr("Unrecognized bad sample threshold type: %1\n").arg(t) );
    return false;
  }

  QString WindowingParameters::badSampleThresholdTypeString() const
  {
    switch(_badSampleThresholdType) {
    case NoSampleThreshold:
      break;
    case RelativeSampleThreshold:
      return "RelativeSampleThreshold";
    case AbsoluteSampleThreshold:
      return "AbsoluteSampleThreshold";
    }
    return "NoSampleThreshold";
  }

  double WindowingParameters::minimumLength(double frequency) const
  {
    if(_lengthType==FrequencyDependent) {
      if(frequency <= 0.0) frequency=1.0;
      return _periodCount/frequency;
    } else {
      return _minimumLength;
    }
  }

  double WindowingParameters::maximumLength(double frequency) const
  {
    if(_lengthType==FrequencyDependent) {
      if(frequency<=0.0) frequency=1.0;
      return _periodCount/frequency;
    } else {
      return _maximumLength;
    }
  }

  void WindowingParameters::printDebug()
  {
    TRACE;
    printf("Overlap=%lf\n", _overlap);
    printf("BadSampleThresholdType=%s\n", badSampleThresholdTypeString().toLatin1().data());
    printf("BadSampleThresholdValue=%lf\n", _badSampleThresholdValue);
    printf("BadSampleTolerance=%lf\n", _badSampleTolerance);
    printf("BadSampleGap=%lf\n", _badSampleGap);
    printf("Raw enabled %s\n", _rawSignalAntiTrigger.enabled ? "true" : "false");
    printf("Raw short term average length=%lf\n", _rawSignalAntiTrigger.shortLength);
    printf("Raw long term average length=%lf\n", _rawSignalAntiTrigger.longLength);
    printf("Raw minimum threshold=%lf\n", _rawSignalAntiTrigger.minimumThreshold);
    printf("Raw maximum threshold=%lf\n", _rawSignalAntiTrigger.maximumThreshold);
    printf("Filtered enabled %s\n", _filterSignalAntiTrigger.enabled ? "true" : "false");
    printf("Filtered short term average length=%lf\n", _filterSignalAntiTrigger.shortLength);
    printf("Filtered long term average length=%lf\n", _filterSignalAntiTrigger.longLength);
    printf("Filtered minimum threshold=%lf\n", _filterSignalAntiTrigger.minimumThreshold);
    printf("Filtered maximum threshold=%lf\n", _filterSignalAntiTrigger.maximumThreshold);
    printf("Component count=%i\n", _componentCount);
    printf("Station count=%i\n", _stationCount);
    printf("Length type %s\n", lengthTypeString().toLatin1().data());
    printf("Minimum length=%lf\n", _minimumLength);
    printf("Maximum length=%lf\n", _maximumLength);
    printf("periodNum=%lf\n", _periodCount);
    printf("maximumWindowCount=%i\n", _maximumWindowCount);
    printf("powerOfTwo=%s\n", _powerOfTwo ? "true" : "false");
    printf("Selected raw components: ");
    for(int i=0;i < _componentCount; i++) printf("%s, ", _rawComponent[i] ? "true" : "false");
    printf("\nSelected filter components: ");
    for(int i=0;i < _componentCount; i++) printf("%s, ", _filterComponent[i] ? "true" : "false");
    printf("Selected raw: ");
    for(int i=0;i < _stationCount; i++) printf("%s, ", _rawStation[i] ? "true" : "false");
    printf("\nSelected filtered: ");
    for(int i=0;i < _stationCount; i++) printf("%s, ", _filterStation[i] ? "true" : "false");
    printf("\n");
  }

  bool WindowingParameters::isValid()
  {
    TRACE;
    if(_periodCount<=0) return false;
    if(_minimumLength<=0) return false;
    if(_maximumLength<=0) return false;
    return true;
  }

  QString WindowingParameters::toString(PARAMETERS_TOSTRING_ARGS_IMPL) const
  {
    TRACE;
    QString log;
    log+="# TYPEs: Exactly, AtLeast, FrequencyDependent\n";
    log+=prefix+"WINDOW_LENGTH_TYPE"+suffix+"="+lengthTypeString()+"\n";
    switch (_lengthType) {
    case Exactly:
    case AtLeast:
      log+=prefix+"WINDOW_MIN_LENGTH"+suffix+"(s)="+QString::number(_minimumLength)+"\n";
      log+=prefix+"WINDOW_MAX_LENGTH"+suffix+"(s)="+QString::number(_maximumLength)+"\n";
      break;
    case FrequencyDependent:
      log+=prefix+"PERIOD_COUNT"+suffix+"="+QString::number(_periodCount)+"\n";
      break;
    }
    log+=prefix+"WINDOW_MAX_COUNT"+suffix+"="+QString::number(_maximumWindowCount)+"\n";
    log+=prefix+"WINDOW_POWER_OF_TWO"+suffix+" (y/n)=";
    log+=_powerOfTwo ? "y\n" : "n\n";
    log+=prefix+"BAD_SAMPLE_TOLERANCE"+suffix+" (s)="+QString::number(_badSampleTolerance)+"\n";
    log+=prefix+"BAD_SAMPLE_GAP"+suffix+" (s)="+QString::number(_badSampleGap)+"\n";
    log+=prefix+"WINDOW_OVERLAP"+suffix+" (%)="+QString::number(_overlap)+"\n";
    log+="# TYPEs: NoSampleThreshold, RelativeSampleThreshold, AbsoluteSampleThreshold\n";
    log+=prefix+"BAD_SAMPLE_THRESHOLD_TYPE"+suffix+"="+badSampleThresholdTypeString()+"\n";
    switch(_badSampleThresholdType) {
    case NoSampleThreshold:
      break;
    case RelativeSampleThreshold:
      log+=prefix+"BAD_SAMPLE_THRESHOLD_VALUE"+suffix+" (%)="+QString::number(_badSampleThresholdValue)+"\n";
      break;
    case AbsoluteSampleThreshold:
      log+=prefix+"BAD_SAMPLE_THRESHOLD_VALUE"+suffix+" (signal unit)="+QString::number(_badSampleThresholdValue)+"\n";
      break;
    }
    log+=prefix+"ANTI-TRIGGERING_ON_RAW_SIGNAL"+suffix+" (y/n)=";
    log+=_rawSignalAntiTrigger.enabled ? "y\n" : "n\n";
    if(_filterSignalAntiTrigger.enabled) {
      if(_componentCount>0) {
        log+=prefix+"USED_RAW_COMPONENTS"+suffix+"=";
        log+=_rawComponent[0] ? "y" : "n";
        for(int i=1; i<_componentCount; i++) {
          log+=_rawComponent[i] ? ", y" : ", n";
        }
        log+="\n";
      }
      log+=prefix+"RAW_STA"+suffix+" (s)="+QString::number(_rawSignalAntiTrigger.shortLength)+"\n";
      log+=prefix+"RAW_LTA"+suffix+" (s)="+QString::number(_rawSignalAntiTrigger.longLength)+"\n";
      log+=prefix+"RAW_MIN_SLTA"+suffix+"="+QString::number(_rawSignalAntiTrigger.minimumThreshold)+"\n";
      log+=prefix+"RAW_MAX_SLTA"+suffix+"="+QString::number(_rawSignalAntiTrigger.maximumThreshold)+"\n";
    }
    log+=prefix+"ANTI-TRIGGERING_ON_FILTERED_SIGNAL"+suffix+" (y/n)=";
    log+=_filterSignalAntiTrigger.enabled ? "y\n" : "n\n";
    if(_filterSignalAntiTrigger.enabled) {
      log+=_filter.toString(prefix, suffix);
      if(_componentCount>0) {
        log+=prefix+"USED_FILTERED_COMPONENTS"+suffix+"=";
        log+=_filterComponent[0] ? "y" : "n";
        for(int i=1; i<_componentCount; i++) {
          log+=_filterComponent[i] ? ", y" : ", n";
        }
        log+="\n";
      }
      log+=prefix+"FILTERED_STA"+suffix+" (s)="+QString::number(_filterSignalAntiTrigger.shortLength)+"\n";
      log+=prefix+"FILTERED_LTA"+suffix+" (s)="+QString::number(_filterSignalAntiTrigger.longLength)+"\n";
      log+=prefix+"FILTERED_MIN_SLTA"+suffix+"="+QString::number(_filterSignalAntiTrigger.minimumThreshold)+"\n";
      log+=prefix+"FILTERED_MAX_SLTA"+suffix+"="+QString::number(_filterSignalAntiTrigger.maximumThreshold)+"\n";
    }
    log+="# Start a time window for each seismic event available inside the time range.\n";
    log+=prefix+"SEISMIC_EVENT_TRIGGER"+suffix+" (y/n)="+(_seismicEventTrigger ? "y\n" : "n\n");
    log+=prefix+"SEISMIC_EVENT_DELAY"+suffix+" (s)="+QString::number(_seismicEventDelay)+"\n";
    return log;
  }

  void WindowingParameters::setVersion(PARAMETERS_SETVERSION_ARGS)
  {
    TRACE;
    _filter.setVersion(version);
    AbstractParameters::setVersion(version);
  }

  int WindowingParameters::keywordCount(PARAMETERS_KEYWORDCOUNT_ARGS) const
  {
    return 29+AbstractParameters::keywordCount();
  }

  int WindowingParameters::totalKeywordCount(PARAMETERS_TOTALKEYWORDCOUNT_ARGS) const
  {
    return AbstractParameters::totalKeywordCount()
        +_filter.totalKeywordCount();
  }

  void WindowingParameters::collectKeywords(PARAMETERS_COLLECTKEYWORDS_ARGS)
  {
    TRACE;
    int baseIndex=AbstractParameters::keywordCount();
    keywords.add(prefix+"WINDOW_LENGTH_TYPE"+suffix, this, baseIndex);
    keywords.add(prefix+"WINDOW_MIN_LENGTH"+suffix, this, baseIndex+1);
    keywords.add(prefix+"WINDOW_MAX_LENGTH"+suffix, this, baseIndex+2);
    keywords.add(prefix+"PERIOD_COUNT"+suffix, this, baseIndex+26);
    keywords.add(prefix+"WINDOW_MAX_COUNT"+suffix, this, baseIndex+21);
    keywords.add(prefix+"WINDOW_POWER_OF_TWO"+suffix, this, baseIndex+22);
    keywords.add(prefix+"DO_BAD_SAMPLE_TOLERANCE"+suffix, this, baseIndex+23);        // For compatibility
    keywords.add(prefix+"BAD_SAMPLE_TOLERANCE"+suffix, this, baseIndex+3);
    keywords.add(prefix+"BAD_SAMPLE_GAP"+suffix, this, baseIndex+20);
    keywords.add(prefix+"WINDOW_OVERLAP"+suffix, this, baseIndex+4);
    keywords.add(prefix+"DO_WINDOW_OVERLAP"+suffix, this, baseIndex+25);              // For compatibility
    keywords.add(prefix+"BAD_SAMPLE_THRESHOLD_TYPE"+suffix, this, baseIndex+5);
    keywords.add(prefix+"BAD_SAMPLE_THRESHOLD_VALUE"+suffix, this, baseIndex+6);
    keywords.add(prefix+"BAD_SAMPLE_THRESHOLD"+suffix, this, baseIndex+7);            // For compatibility
    keywords.add(prefix+"DO_BAD_SAMPLE_THRESHOLD"+suffix, this, baseIndex+24);        // For compatibility
    keywords.add(prefix+"ANTI-TRIGGERING_ON_RAW_SIGNAL"+suffix, this, baseIndex+8);
    keywords.add(prefix+"USED_RAW_COMPONENTS"+suffix, this, baseIndex+9);
    keywords.add(prefix+"RAW_STA"+suffix, this, baseIndex+10);
    keywords.add(prefix+"RAW_LTA"+suffix, this, baseIndex+11);
    keywords.add(prefix+"RAW_MIN_SLTA"+suffix, this, baseIndex+12);
    keywords.add(prefix+"RAW_MAX_SLTA"+suffix, this, baseIndex+13);
    keywords.add(prefix+"ANTI-TRIGGERING_ON_FILTERED_SIGNAL"+suffix, this, baseIndex+14);
    keywords.add(prefix+"USED_FILTERED_COMPONENTS"+suffix, this, baseIndex+15);
    keywords.add(prefix+"FILTERED_STA"+suffix, this, baseIndex+16);
    keywords.add(prefix+"FILTERED_LTA"+suffix, this, baseIndex+17);
    keywords.add(prefix+"FILTERED_MIN_SLTA"+suffix, this, baseIndex+18);
    keywords.add(prefix+"FILTERED_MAX_SLTA"+suffix, this, baseIndex+19);
    keywords.add(prefix+"SEISMIC_EVENT_TRIGGER"+suffix, this, baseIndex+27);
    keywords.add(prefix+"SEISMIC_EVENT_DELAY"+suffix, this, baseIndex+28);
    _filter.collectKeywords(keywords, prefix, suffix);
  }

  bool WindowingParameters::setValue(PARAMETERS_SETVALUE_ARGS)
  {
    TRACE;
    bool ok=true;
    switch(index-AbstractParameters::keywordCount()) {
    case 0:
      return setLengthType(value);
    case 1:
      _minimumLength=value.toDouble(&ok);
      return ok;
    case 2:
      _maximumLength=value.toDouble(&ok);
      return ok;
    case 26:
      _periodCount=value.toDouble(&ok);
      return ok;
    case 3:
      _badSampleTolerance=value.toDouble(&ok);
      return ok;
    case 23:                                          // For compatibility
      obsoleteKeyword(keywords, 23);
      if(value=="n") {
        _badSampleTolerance=0.0;
      }
      return true;
    case 4:
      _overlap=value.toDouble(&ok);
      return ok && _overlap>=0.0;
    case 25:                                          // For compatibility
      obsoleteKeyword(keywords, 25);
      if(value=="n") {
        _overlap=-1.0;
      }
      return true;
    case 5:
      return setBadSampleThresholdType(value);
    case 6:
      _badSampleThresholdValue=value.toDouble(&ok);
      return ok;
    case 7:                                           // For compatibility
      obsoleteKeyword(keywords, 7);
      if(_badSampleThresholdType==RelativeSampleThreshold) {
        _badSampleThresholdValue=value.toDouble(&ok);
      }
      return ok;
    case 24:                                           // For compatibility
      obsoleteKeyword(keywords, 24);
      if(value=="n") {
        _badSampleThresholdType=WindowingParameters::NoSampleThreshold;
      } else {
        _badSampleThresholdType=RelativeSampleThreshold;
      }
      return true;
    case 8:
      _rawSignalAntiTrigger.enabled=(value=="y");
      return true;
    case 9: {
      LineParser p(value);
        p.setDelimiters(",");
        if(_componentCount==0) {
          setComponentCount(p.count());
        }
        bool ok=true;
        for(int i=0; i<_componentCount; i++) {
          _rawComponent[i]=(p.toString(i, ok)=="y" && ok);
        }
      }
      return true;
    case 10:
      _rawSignalAntiTrigger.shortLength=value.toDouble(&ok);
      return ok;
    case 11:
      _rawSignalAntiTrigger.longLength=value.toDouble(&ok);
      return ok;
    case 12:
      _rawSignalAntiTrigger.minimumThreshold=value.toDouble(&ok);
      return ok;
    case 13:
      _rawSignalAntiTrigger.maximumThreshold=value.toDouble(&ok);
      return ok;
    case 14:
      _rawSignalAntiTrigger.enabled=(value=="y");
      return true;
    case 15: {
        LineParser p(value);
        p.setDelimiters(",");
        if(_componentCount==0) {
          setComponentCount(p.count());
        }
        bool ok=true;
        for(int i=0; i<_componentCount; i++) {
          _filterComponent[i]=(p.toString(i, ok)=="y" && ok);
        }
      }
      return true;
    case 16:
      _filterSignalAntiTrigger.shortLength=value.toDouble(&ok);
      return ok;
    case 17:
      _filterSignalAntiTrigger.longLength=value.toDouble(&ok);
      return ok;
    case 18:
      _filterSignalAntiTrigger.minimumThreshold=value.toDouble(&ok);
      return ok;
    case 19:
      _filterSignalAntiTrigger.maximumThreshold=value.toDouble(&ok);
      return ok;
    case 20:
      _badSampleGap=value.toDouble(&ok);
      return ok;
    case 21:
      _maximumWindowCount=value.toInt(&ok);
      return ok;
    case 22:
      _powerOfTwo=(value=="y");
      return true;
    case 27:
      _seismicEventTrigger=(value=="y");
      return true;
    case 28:
      _seismicEventDelay=value.toDouble(&ok);
      return ok;
    default:
      break;
    }
    return AbstractParameters::setValue(index, value, unit, keywords);
  }

} // namespace GeopsyCore
