/***************************************************************************
**
**  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: 2006-07-25
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef SCALE_H
#define SCALE_H

#include <QGpCoreTools.h>

#include "QGpCoreMathDLLExport.h"
#include "SamplingParameters.h"

namespace QGpCoreMath {

class QGPCOREMATH_EXPORT Scale
{
public:
  Scale();

  enum Type {Linear, Inversed, Log, InversedLog, AbsoluteTime, RelativeTime};

  void operator=(const Scale& o);

  Type type() const {return _type;}
  void setType(Type t) {_type=t;}

  SamplingOption sampling() const;

  void setGlobalMinimum(double m);
  double globalMinimum() const {return _globalMinimum;}

  void setGlobalMaximum(double m);
  double globalMaximum() const {return _globalMaximum;}

  void autoTicks();

  double majorTicks() const {return _majorTicks;}
  void setMajorTicks(double m) {_majorTicks=m;}

  double minorTicks() const {return _minorTicks;}
  void setMinorTicks(double m) {_minorTicks=m;}

  double minimum() const {return _minimum;}
  void setMinimum(double min) {_minimum=min;}

  double maximum() const {return _maximum;}
  void setMaximum(double max) {_maximum=max;}

  const DateTime& timeReference() const {return _timeReference;}
  void setTimeReference(const DateTime& t);

  double a() const {return _a;}
  double b() const {return _b;}

  bool isReversed() const {return _reversed;}
  void setReversed(bool r) {_reversed=r;}
  inline bool isEffectivelyReversed() const;

  double s2r(int val) const {return s2rF(val);}
  int r2s(double val) const {return qRound(r2sF(val));}

  inline double s2rF(double val) const;
  inline double r2sF(double val) const;

  int a2s(double val) const {return qRound(a2sF(val));}
  inline double a2sF(double val) const;

  void setHorizontalTransformation(int length);
  void setVerticalTransformation(int length);

  inline int lineCount() const;
  void setVerticalCurrentLine(int line);
  int verticalCurrentLine() const;
  void setHorizontalCurrentLine(int line);
  int horizontalCurrentLine() const;

  void checkLimits();

  void cacheTicks();
  bool isMajorTicksEmpty() const {return _majorTickValues.isEmpty();}
  const QVector<double>& majorTickValues() const {return _majorTickValues;}
  const QVector<double>& minorTickValues() const {return _minorTickValues;}
  const QVector<int>& majorTickPrecision() const {return _majorTickPrecision;}
  const QString& timeFormat() const {return _timeFormat;}
private:
  static inline void setTicks(double delta, double expo, double& majorTicks, double& minorTicks);
  void setDateTimeTicks();
  inline double a2r(double val) const;
  inline double r2a(double val) const;
  inline bool safeAdd(double& value, double inc);
  inline bool safeSubtract(double& value, double inc);

  double _a, _b;  // Current parameters for tranformation between screen and real coordinates
  Type _type;
  double _globalMinimum, _globalMaximum;
  double _minimum, _maximum;  // Current visible minimum and maximum
  bool _reversed;
  // increment for labels and primary ticks
  double _majorTicks;
  // increment for secondary ticks
  double _minorTicks;
  DateTime _timeReference;
  QVector<double> _majorTickValues;
  QVector<double> _minorTickValues;
  QVector<int> _majorTickPrecision;
  QString _timeFormat;
};

inline double Scale::r2sF(double val) const
{
  TRACE;
  switch (_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
    break;
  case Inversed:
    return _a/val+_b;
  case Log:
    return _a*log10(val)+_b;
  case InversedLog:
    return _a*log10(1.0/val)+_b;
  }
  return _a*val+_b;
}

inline double Scale::s2rF(double val) const
{
  TRACE;
  switch (_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
    break;
  case Inversed:
    return _a/(val-_b);
  case Log:
    return pow(10.0, (val-_b)/_a);
  case InversedLog:
    return pow(10.0, (_b-val)/_a);
  }
  return (val-_b)/_a;
}

inline double Scale::a2sF(double val) const
{
  TRACE;
  switch (_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
  case Inversed:
    break;
  case Log:
  case InversedLog:
    return _a*log10(val)+_b;
  }
  return _a*val+_b;
}

inline double Scale::r2a(double val) const
{
  TRACE;
  switch (_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
    break;
  case Inversed:
    return 1.0/val;
  case Log:
    return log10(val);
  case InversedLog:
    return log10(1.0/val);
  }
  return val;
}

inline double Scale::a2r(double val) const
{
  TRACE;
  switch (_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
    break;
  case Inversed:
    return 1.0/val;
  case Log:
    return pow(10.0, val);
  case InversedLog:
    return pow(10.0, -val);
  }
  return val;
}

inline bool Scale::isEffectivelyReversed() const
{
  TRACE;
  switch(_type) {
  case Linear:
  case AbsoluteTime:
  case RelativeTime:
  case Log:
    break;
  case Inversed:
  case InversedLog:
    return !_reversed;
  }
  return _reversed;
}

inline int Scale::lineCount() const
{
  TRACE;
  // Line is one tenth of physical size of axis in pixel (aSize()*0.1). Hence 9 steps are removed
  // because the screen is always full of content.
  return qCeil((r2a(_globalMaximum)-r2a(_globalMinimum))/((r2a(_maximum)-r2a(_minimum))*0.1))-9;
}

} // namespace QGpCoreMath

#endif // SCALE_H
