/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-03-04
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "SamplingParameters.h"

namespace QGpCoreMath {
//#define COMPATIBILITY_2_5_0
  /*!
    \class SamplingParameters SamplingParameters.h
    \brief An axis sampling definition

    Full description of class still missing
  */

  SamplingParameters::SamplingParameters()
    : AbstractParameters()
  {
    TRACE;
    _minIndex=0;
    _min=0.0;
    _max=0.0;
    _step=0.0;
    _type=Linear;
    _stepType=Step;
    _n=0;
    _inversed=false;
  }

  SamplingParameters::SamplingParameters(const SamplingParameters& o)
    : AbstractParameters(o)
  {
    TRACE;
    _minIndex=o._minIndex;
    _min=o._min;
    _max=o._max;
    _step=o._step;
    _type=o._type;
    _stepType=o._stepType;
    _n=o._n;
    _inversed=o._inversed;
  }

  SamplingOptions SamplingParameters::options() const
  {
    TRACE;
    SamplingOptions o;
    switch(_type) {
    case Linear:
      o=LinearScale;
      break;
    case Log:
      o=LogScale;
      break;
    }
    if(_inversed) {
      o|=InversedScale;
    }
    return o;
  }

  /*!
    Set the number of samples according to the provided \a step
    Minimum, maximum and the type of sampling must be properly set.
  */
  void SamplingParameters::setStep(double step)
  {
    TRACE;
    _step=step;
    _stepType=Step;
    setCount();
  }

  void SamplingParameters::setCount(int n)
  {
    _n=n;
    if(_n<1) {
      _n=1;
    }
    _stepType=Count;
    setCount();
  }

  void SamplingParameters::setCount()
  {
    TRACE;
    if(_min<_max) {
      switch(_stepType) {
      case Step:
        switch(_type) {
        case Linear: {
            _minIndex=qCeil(_min/_step);
            _n=1+qFloor(_max/_step)-_minIndex;
          }
          break;
        case Log:
          if(_min>0.0 && _step>1.0) {
            double logStep=std::log(_step);
            _minIndex=qCeil(std::log(_min)/logStep);
            _n=1+qFloor(std::log(_max)/logStep)-_minIndex;
          }
        }
        break;
      case Count:
        switch(_type) {
        case Linear:
#ifdef COMPATIBILITY_2_5_0
          _step=(_max-_min)/static_cast<double>(_n-1);
#else
          _step=(_max-_min)/static_cast<double>(_n);
          _minIndex=qCeil(_min/_step);
#endif
          break;
        case Log: {
#ifdef COMPATIBILITY_2_5_0
            _step=std::pow(_max/_min, 1.0/static_cast<double>(_n-1));
#else
            _step=std::pow(_max/_min, 1.0/static_cast<double>(_n));
#endif
            _minIndex=qCeil(std::log(_min)/std::log(_step));
          }
          break;
        }
        break;
      }
    } else if(_n==1 && _min==_max) {
      _minIndex=1;
      _type=Linear;
      _step=_min;
    } else {
      // Set as null the calculated value
      switch(_stepType) {
      case Count:
        _step=0.0;
        break;
      case Step:
        _n=0;
        break;
      }
      _minIndex=0;
    }
  }

  double SamplingParameters::value(int i) const
  {
    TRACE;
    switch(_type) {
    case Linear:
#ifdef COMPATIBILITY_2_5_0
      return _min+_step*i;
#else
      return _step*(i+_minIndex);
#endif
    case Log:
      break;
    }
#ifdef COMPATIBILITY_2_5_0
    return _min*std::pow(_step, i);
#else
    return std::pow(_step, i+_minIndex);
#endif
  }

  /*!
    Return the closest index to \a val
  */
  int SamplingParameters::index(double val) const
  {
    TRACE;
    int i;
    switch(_type) {
    case Linear:
#ifdef COMPATIBILITY_2_5_0
      i=qRound(val-_min)/_step);
#else
      i=qRound(val/_step)-_minIndex;
#endif
      break;
    case Log:
#ifdef COMPATIBILITY_2_5_0
      i=qRound(std::log(val/_min)/std::log(_step));
#else
      i=qRound(std::log(val)/std::log(_step))-_minIndex;
#endif
      break;
    }
    if(i<0) {
      i=0;
    } else if(i>=_n) {
      i=_n-1;
    }
    return i;
  }

  QVector<double> SamplingParameters::values() const
  {
    TRACE;
    QVector<double> v(_n);
    for(int i=0; i<_n; i++) {
      v[i]=value(i);
    }
    return v;
  }

  QString SamplingParameters::type(Type t)
  {
    switch(t) {
    case Linear:
      break;
    case Log:
      return "Log";
    }
    return "Linear";
  }

  SamplingParameters::Type SamplingParameters::type(QString t)
  {
    t=t.toLower();
    if(t=="linear") {
      return Linear;
    } else if(t=="log") {
      return Log;
    } else {
      App::log(tr("Bad sampling type '%1'.\n").arg(t));
      return Linear;
    }
  }

  /*!
   Do not use the static function for compatibility.
  */
  void SamplingParameters::setType(QString t)
  {
    t=t.toLower();
    if(t=="linear") {
      _type=Linear;
    } else if(t=="log") {
      _type=Log;
    } else if(t=="0"){           // Compatibility
      _type=Log;
    } else if(t=="1"){           // Compatibility
      _type=Linear;
    } else if(t=="period"){      // This object is used mainly for frequency scales
      _type=Linear;
      _inversed=true;
    } else if(t=="frequency") {
      _type=Linear;
      _inversed=false;
    } else {
      App::log(tr("Bad sampling type '%1'.\n").arg(t) );
    }
  }

  QString SamplingParameters::stepTypeString() const
  {
    switch(_stepType) {
    case Count:
      break;
    case Step:
      return "Step";
    }
    return "Count";
  }

  void SamplingParameters::setStepType(QString t)
  {
    t=t.toLower();
    if(t=="count") {
      _stepType=Count;
    } else if(t=="step") {
      _stepType=Step;
    } else {
      App::log(tr("Bad step type '%1'.\n").arg(t) );
    }
  }

  int SamplingParameters::keywordCount(PARAMETERS_KEYWORDCOUNT_ARGS) const
  {
    return 7+AbstractParameters::keywordCount();
  }

  void SamplingParameters::collectKeywords(PARAMETERS_COLLECTKEYWORDS_ARGS)
  {
    TRACE;
    int baseIndex=AbstractParameters::keywordCount();
    keywords.add(prefix+"MINIMUM"+suffix, this, baseIndex);
    keywords.add(prefix+"MAXIMUM"+suffix, this, baseIndex+1);
    keywords.add(prefix+"SAMPLING_TYPE"+suffix, this, baseIndex+3);
    keywords.add(prefix+"INVERSED"+suffix, this, baseIndex+4);
    keywords.add(prefix+"STEP_TYPE"+suffix, this, baseIndex+6);
    keywords.add(prefix+"SAMPLES_NUMBER"+suffix, this, baseIndex+2);
    keywords.add(prefix+"STEP"+suffix, this, baseIndex+5);
  }

  bool SamplingParameters::setValue(PARAMETERS_SETVALUE_ARGS)
  {
    switch(index-AbstractParameters::keywordCount()) {
    case 0:
      setMinimum(value.toDouble());
      return true;
    case 1:
      setMaximum(value.toDouble());
      return true;
    case 2:
      setCount(value.toInt());
      return true;
    case 3:
      setType(value);
      setCount();
      return true;
    case 4:
      _inversed=(value=="y");
      return true;
    case 5:
      setStep(value.toDouble());
      return true;
    case 6:
      setStepType(value);
      return true;
    default:
      break;
    }
    return AbstractParameters::setValue(index, value, unit, keywords);
  }

  QString SamplingParameters::toString(PARAMETERS_TOSTRING_ARGS_IMPL) const
  {
    TRACE;
    QString log;
    log+=prefix+"MINIMUM"+suffix+"="+QString::number(_min)+"\n";
    log+=prefix+"MAXIMUM"+suffix+"="+QString::number(_max)+"\n";
    log+="# Either 'log' or 'linear'\n";
    log+=prefix+"SAMPLING_TYPE"+suffix+"="+type(_type)+"\n";
    log+="# Number of samples is either set to a fixed value ('Count') or through a step between samples ('Step')'\n";
    log+=prefix+"STEP_TYPE"+suffix+"="+stepTypeString()+"\n";
    log+=prefix+"SAMPLES_NUMBER"+suffix+"="+QString::number(_n)+"\n";
    log+="# STEP=ratio between two successive samples for 'log' scales\n";
    log+="# STEP=difference between two successive samples for 'linear' scales\n";
    log+=prefix+"STEP"+suffix+"="+QString::number(_step)+"\n";
    log+=prefix+"INVERSED"+suffix+" (y/n)="+(_inversed ? "y" : "n")+"\n";
    return log;
  }

  QString SamplingParameters::toUserString() const
  {
    TRACE;
    return tr("from %1 to %2 with %3 %4 samples (step=%5%6)")
        .arg(_n>1 ? value(0) : _min)
        .arg(_n>1 ? value(_n-1) : _max)
        .arg(_n)
        .arg(type(_type))
        .arg(_step)
        .arg(_inversed ? ", inversed" : "");
  }

  bool SamplingParameters::isValid() const
  {
    if(_n>1) {
      return std::isfinite(_min) && std::isfinite(_max) && _min<_max && _step>0.0;
    } else if(_n>0) {
      return std::isfinite(_min);
    } else {
      return false;
    }
  }

} // namespace QGpCoreMath
