/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore 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.
**
**  ArrayCore 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: 2008-02-06
**  Copyright: 2008-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "ArrayParameters.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  ArrayParameters::ArrayParameters()
    : AbstractParameters()
  {
    _taper.setShape(WindowFunctionParameters::Tukey);
    _taper.setAlpha(0.2);
    _frequencySampling.setType(SamplingParameters::Log);
    _frequencySampling.setStep(1.025);
    _frequencyBandwidth=0.05;
    _oversamplingFactor=1.0;
    _selectArrayRadius=0.0;
    _selectDurationFactor=0.0;
    _magneticDeclination=0.0;
    //_windowing.setPowerOfTwo(true);
    acceptAllFrequencies();
  }

  ArrayParameters::ArrayParameters(const ArrayParameters& o)
    : AbstractParameters(o),
      _timeLimits(o._timeLimits),
      _windowing(o._windowing),
      _frequencySampling(o._frequencySampling),
      _blockAveraging(o._blockAveraging),
      _taper(o._taper)
  {
    _frequencyBandwidth=o._frequencyBandwidth;
    _oversamplingFactor=o._oversamplingFactor;
    _outputBaseName=o._outputBaseName;
    for(int i=0; i<2; i++) {
      _minimumFrequency[i]=o._minimumFrequency[i];
      _maximumFrequency[i]=o._maximumFrequency[i];
    }
    _selectArrayCenter=o._selectArrayCenter;
    _selectArrayRadius=o._selectArrayRadius;
    _selectDurationFactor=o._selectDurationFactor;
    _magneticDeclination=o._magneticDeclination;
  }

  void ArrayParameters::setVersion(PARAMETERS_SETVERSION_ARGS)
  {
    TRACE;
    _timeLimits.setVersion(version);
    _windowing.setVersion(version);
    _frequencySampling.setVersion(version);
    _blockAveraging.setVersion(version);
    _taper.setVersion(version);
    AbstractParameters::setVersion(version);
  }

  int ArrayParameters::keywordCount(PARAMETERS_KEYWORDCOUNT_ARGS) const
  {
    return 8+AbstractParameters::keywordCount();
  }

  int ArrayParameters::totalKeywordCount(PARAMETERS_TOTALKEYWORDCOUNT_ARGS) const
  {
    return AbstractParameters::totalKeywordCount()+
        _timeLimits.totalKeywordCount()+
        _windowing.totalKeywordCount()+
        _frequencySampling.totalKeywordCount()+
        _blockAveraging.totalKeywordCount()+
        _taper.totalKeywordCount();
  }

  void ArrayParameters::collectKeywords(PARAMETERS_COLLECTKEYWORDS_ARGS)
  {
    TRACE;
    int baseIndex=AbstractParameters::keywordCount();
    keywords.add(prefix+"FREQ_BAND_WIDTH", this, baseIndex);
    keywords.add(prefix+"SELECT_DURATION_FACTOR", this, baseIndex+1);
    keywords.add(prefix+"SELECT_ARRAY_CENTER", this, baseIndex+2);
    keywords.add(prefix+"SELECT_ARRAY_RADIUS", this, baseIndex+3);
    keywords.add(prefix+"OUTPUT_BASE_NAME", this, baseIndex+4);
    keywords.add(prefix+"MAGNETIC_DECLINATION", this, baseIndex+5);
    keywords.add(prefix+"OUTPUT_FILE", this, baseIndex+6);  // Kept for compatibility
    keywords.add(prefix+"OVER_SAMPLING_FACTOR"+suffix, this, baseIndex+7);
    _timeLimits.collectKeywords(keywords, prefix, suffix);
    _windowing.collectKeywords(keywords, prefix, suffix);
    _frequencySampling.collectKeywords(keywords, prefix, "_FREQUENCY"+suffix);
    _blockAveraging.collectKeywords(keywords, prefix, suffix);
    _taper.collectKeywords(keywords, prefix+"TAPER_", suffix);
  }

  QString ArrayParameters::toString(PARAMETERS_TOSTRING_ARGS_IMPL) const
  {
    TRACE;
    QString log;
    log+=_timeLimits.toString(prefix, suffix);
    log+=_windowing.toString(prefix, suffix);
    log+=_frequencySampling.toString(prefix, "_FREQUENCY"+suffix);
    log+=_blockAveraging.toString(prefix, suffix);
    log+=_taper.toString(prefix+"TAPER_", suffix);
    log+="# Gaussian band width from f*(1-bw) to f*(1+bw), f*bw=stddev\n";
    log+=prefix+"FREQ_BAND_WIDTH"+suffix+"="+QString::number(_frequencyBandwidth)+"\n";
    log+="# Required when using short and fixed length time windows, avoid classical oblique lines visible in the results\n"
         "# when the number of frequency samples is higher than the number of points in the spectra.\n";
    log+=prefix+"OVER_SAMPLING_FACTOR"+suffix+"="+QString::number(_oversamplingFactor)+"\n";
    log+="# A station is selected for processing only if it is available over a duration greater or equal to\n"
         "# SELECT_DURATION_FACTOR*[total required duration]. The factor can vary from 0 to 1\n";
    log+=prefix+"SELECT_DURATION_FACTOR"+suffix+"="+QString::number(_selectDurationFactor)+"\n";
    log+="# A station is selected for processing only if it is located at less than SELECT_ARRAY_RADIUS\n"
         "# from SELECT_ARRAY_CENTER. SELECT_ARRAY_CENTER is the X, Y coordinates of the center.\n";
    log+=prefix+"SELECT_ARRAY_CENTER"+suffix+"="+_selectArrayCenter.toString()+"\n";
    log+=prefix+"SELECT_ARRAY_RADIUS"+suffix+"="+QString::number(_selectArrayRadius)+"\n";
    log+="# Assuming that north of sensors is aligned to the magnetic north and sensor coordinates to UTM grid,\n"
         "# relative coordinates between stations are calculated with a correction for the difference between the\n"
         "# geographical and the local UTM norths and for the magnetic declination. The later can be, for instance,\n"
         "# calculated at https://www.ngdc.noaa.gov/geomag-web/#declination\n"
         "# The value must be in degrees.\n";
    log+=prefix+"MAGNETIC_DECLINATION"+suffix+"="+QString::number(_magneticDeclination)+"\n";
    log+=prefix+"OUTPUT_BASE_NAME"+suffix+"="+_outputBaseName+"\n";
    return log;
  }

  bool ArrayParameters::setValue(PARAMETERS_SETVALUE_ARGS)
  {
    TRACE;
    bool ok=true;
    switch(index-AbstractParameters::keywordCount()) {
    case 0:
      _frequencyBandwidth=value.toDouble(&ok);
      return ok && _frequencyBandwidth>=0.0;
    case 1:
      _selectDurationFactor=value.toDouble(&ok);
      return ok && _selectDurationFactor>=0.0 && _selectDurationFactor<=1.0;
    case 2:
      return _selectArrayCenter.fromString(value);
    case 3:
      _selectArrayRadius=value.toDouble(&ok);
      return ok && _selectArrayRadius>=0.0;
    case 4:
      _outputBaseName=value;
      return true;
    case 5:
      _magneticDeclination=value.toDouble(&ok);
      return ok && _magneticDeclination>=-180.0 && _magneticDeclination<=180.0;
      // Magnetic declination above 30 or below -30 can be found close to the poles (up to +/-180)
    case 6:  // Kept for compatibility
      obsoleteKeyword(keywords, 6);
      return true;
    case 7:
      _oversamplingFactor=value.toDouble(&ok);
      return ok;
    default:
      break;
    }
    return AbstractParameters::setValue(index, value, unit, keywords);
  }

  void ArrayParameters::acceptAllFrequencies()
  {
    TRACE;
    for(int i=0; i<2; i++) {
      _minimumFrequency[i]=-std::numeric_limits<double>::infinity();
      _maximumFrequency[i]=std::numeric_limits<double>::infinity();
    }
  }

  bool ArrayParameters::acceptFrequency(int gridIndex, double frequency) const
  {
    TRACE;
    return frequency>=_minimumFrequency[gridIndex] &&
           frequency<=_maximumFrequency[gridIndex];
  }

  /*!

  */
  void ArrayParameters::selectStations(ArraySelection& array) const
  {
    TRACE;
    QVector<int> sel;
    int n=array.count();

    if(n>0) {
      App::log(1, tr("Checking time availibility of station signals...\n"));
      TimeRange r=timeLimits().absoluteRange(array.at(0));
      double requiredDuration=r.lengthSeconds()*selectDurationFactor();
      n=array.count();
      sel.clear();
      for(int i=0; i<n; i++) {
        const StationSignals& s=*array.at(i);
        SparseTimeRange ssr=s.timeRange(r);
        if(ssr.totalLength()>=requiredDuration) {
          sel.append(i);
        }
      }
      array.select(sel);
      if(array.count()<n) {
        App::log(1, tr("Selected %1 stations:\n%2")
                         .arg(array.count())
                         .arg(array.toString(ArraySelection::Relative)));
      }

      if(selectArrayRadius()>0.0) {
        App::log(1, tr("Checking location of stations...\n"));
        n=array.count();
        sel.clear();
        for(int i=0; i<n; i++) {
          const StationSignals& s=*array.at(i);
          if(selectArrayCenter().distanceTo(s.coordinates())<=selectArrayRadius()) {
            sel.append(i);
          }
        }
        array.select(sel);
        if(array.count()<n) {
          App::log(1, tr("Selected %1 stations:\n%2")
                           .arg(array.count())
                           .arg(array.toString(ArraySelection::Relative)));
        }
      }
    }
  }

} // namespace ArrayCore
