/***************************************************************************
**
**  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)
**
***************************************************************************/

#include "Scale.h"

namespace QGpCoreMath {

  /*!
    \class Scale Scale.h
    \brief Manage a scale for transforming real coordinates into screen coordinates

    The scale is defined by a minimum() and a maximum() which correspond to the visible
    minimum and maximum of a Axis. The scale tranformation is defined by the type of
    scale (Normal, Inversed or Log) by its reversed flag (isReversed()) and by a() and b().
    a() and b() are set indirectly by setVerticalTransformation() or
    setHorizontalTransformation().

    Three types of coordinates are in use here: screen, real, axis.

    \li screen (s): integer coordinate in pixel
    \li real   (r): real coordinate in value units (double)
    \li axis   (a): transformed real coordinate (double). Equivalent to real for linear scale.
                    Scaled viewed by the user.
                    Usage for line counting (scroll movements).
  */

  /*!
    Description of constructor still missing
  */
  Scale::Scale()
  {
    TRACE;
    _globalMinimum=0;
    _globalMaximum=100;
    _minimum=0.0;
    _maximum=100.0;
    _type=Linear;
    _reversed=false;
    _majorTicks=5.0;
    _minorTicks=1.0;
    _a=1.0;
    _b=0.0;
  }

  /*!
  */
  void Scale::operator=(const Scale& o)
  {
    TRACE;
    _globalMinimum=o._globalMinimum;
    _globalMaximum=o._globalMaximum;
    _minimum=o._minimum;
    _maximum=o._maximum;
    _type=o._type;
    _reversed=o._reversed;
    _a=o._a;
    _b=o._b;
    _majorTicks=o._majorTicks;
    _minorTicks=o._minorTicks;
    _timeReference=o._timeReference;
    _majorTickValues=o._majorTickValues;
    _majorTickPrecision=o._majorTickPrecision;
    _minorTickValues=o._minorTickValues;
    _timeFormat= o._timeFormat;
  }

  void Scale::setGlobalMinimum(double m)
  {
    TRACE;
    switch(_type) {
    case Log:
    case InversedLog:
      if(m<=0) {
        _globalMinimum=1e-16;
      } else {
        _globalMinimum=m;
      }
      break;
    case Inversed:
    case Linear:
    case AbsoluteTime:
    case RelativeTime:
      _globalMinimum=m;
      break;
    }
  }

  void Scale::setGlobalMaximum(double m)
  {
    TRACE;
    switch(_type) {
    case Log:
    case InversedLog:
      if(m<=0) {
        _globalMaximum=1e-16;
      } else {
        _globalMaximum=m;
      }
      break;
    case Inversed:
    case Linear:
    case AbsoluteTime:
    case RelativeTime:
      _globalMaximum=m;
      break;
    }
  }

  /*!
    \fn double Scale::s2r(int val) const
    Conversion from screen coordinate to real coordinate
  */

  /*!
    \fn int Scale::r2s(double val) const
    Conversion from real coordinate to screen coordinate
  */

  /*!
    \fn double Scale::a2r(double val) const
    Conversion from axis value coordinate to real coordinate
  */

  /*!
    \fn double Scale::r2a(double val) const
    Conversion from real coordinate to axis value
  */

  /*!
    Returns the type() as a SamplingOption
  */
  SamplingOption Scale::sampling() const
  {
    TRACE;
    switch(_type) {
    case Log:
      return LogScale;
    case Inversed:
      return InversedScale;
    case InversedLog:
      return LogScale;
    case Linear:
    case AbsoluteTime:
    case RelativeTime:
      return LinearScale;
    }
    return LinearScale;
  }

  /*!
    Make sure minimum() and maximum() are within \a min and \a max.
    If not, they are corrected keeping the difference between minimum() and maximum() constant
  */
  void Scale::checkLimits()
  {
    TRACE;
    double delta=r2a(_maximum)-r2a(_minimum);
    if(_minimum<_globalMinimum) {
      _maximum=a2r(r2a(_globalMinimum)+delta);
      _minimum=_globalMinimum;
      if(_maximum>_globalMaximum)
        _maximum=_globalMaximum;
    } else if(_maximum>_globalMaximum) {
      _minimum=a2r(r2a(_globalMaximum)-delta);
      _maximum=_globalMaximum;
      if(_minimum<_globalMinimum)
        _minimum=_globalMinimum;
    }
  }

  /*!
    Set scale transformation factors (_a and _b). \a length is the size of axis along its direction.
    For a Y axis (vertical), \a length is the height of the axis object.
  */
  void Scale::setVerticalTransformation(int length)
  {
    TRACE;
    _a=static_cast<double>(length-1)/(r2a(_maximum)-r2a(_minimum));
    if(isEffectivelyReversed()) {
      _b=-_a*r2a(_minimum);
    } else {
      _a=-_a;
      _b=-_a*r2a(_maximum);
    }
  }

  /*!
    Set scale transformation factors (_a and _b). \a length is the size of axis along its direction.
    For an X axis (horizontal), \a length is the width of the axis object.
  */
  void Scale::setHorizontalTransformation(int length)
  {
    TRACE;
    _a=static_cast<double>(length-1)/(r2a(_maximum)-r2a(_minimum));
    if(isEffectivelyReversed()) {
      _a=-_a;
      _b=-_a*r2a(_maximum);
    } else {
      _b=-_a*r2a(_minimum);
    }
  }

  /*!
    The scroll is counted in "lines", one line is one tenth of the current
    scale length. This line count refers to \a min and \a max.

    The main trick here is that delta cannot change when scrolling, never,
    never, ever ...
  */
  void Scale::setVerticalCurrentLine(int line)
  {
    TRACE;
    double delta=r2a(_maximum)-r2a(_minimum);
    if(isEffectivelyReversed()) {
      _minimum=a2r(static_cast<double>(line)*0.1*delta+r2a(_globalMinimum));
      _maximum=a2r(r2a(_minimum)+delta);
    } else {
      _maximum=a2r(r2a(_globalMaximum)-static_cast<double>(line)*0.1*delta);
      _minimum=a2r(r2a(_maximum)-delta);
    }
    cacheTicks();
  }

  /*!
    The scroll is counted in "lines", one line is one tenth of the current
    scale length. This line count refers to \a min and \a max.

    The main trick here is that delta cannot change when scrolling, never,
    never, ever ...
  */
  void Scale::setHorizontalCurrentLine(int line)
  {
    TRACE;
    double delta=r2a(_maximum)-r2a(_minimum);
    if(isEffectivelyReversed()) {
      _maximum=a2r(r2a(_globalMaximum)-static_cast<double>(line)*0.1*delta);
      _minimum=a2r(r2a(_maximum)-delta);
    } else {
      _minimum=a2r(static_cast<double>(line)*0.1*delta+r2a(_globalMinimum));
      _maximum=a2r(r2a(_minimum)+delta);
    }
  }

  /*!
    The scroll is counted in "lines", one line is one tenth of the current scale length.
  */
  int Scale::verticalCurrentLine() const
  {
    TRACE;
    if(isEffectivelyReversed())
      return qRound((r2a(_minimum)-r2a(_globalMinimum))/((r2a(_maximum)-r2a(_minimum))*0.1));
    else
      return qRound((r2a(_maximum)-r2a(_globalMaximum))/((r2a(_minimum)-r2a(_maximum))*0.1));
  }

  /*!
    The scroll is counted in "lines", one line is one tenth of the current scale length.
  */
  int Scale::horizontalCurrentLine() const
  {
    if(isEffectivelyReversed())
      return qRound((r2a(_maximum)-r2a(_globalMaximum))/((r2a(_minimum)-r2a(_maximum))*0.1));
    else
      return qRound((r2a(_minimum)-r2a(_globalMinimum))/((r2a(_maximum)-r2a(_minimum))*0.1));
  }

  inline void Scale::setTicks(double delta, double expo, double& majorTicks, double& minorTicks)
  {
    if(delta<=1.2) {
      majorTicks=0.2*expo;
      minorTicks=0.04*expo;
    } else if(delta<=2.4) {
      majorTicks=0.4*expo;
      minorTicks=0.1*expo;
    } else if(delta<=6.0) {
      majorTicks=expo;
      minorTicks=0.2*expo;
    } else {
      majorTicks=2.0*expo;
      minorTicks=0.4*expo;
    }
  }

  #define TIME_TICKS(refCeil, addMajorTicks, addMinorTicks, subMinorTicks) \
    refTime.refCeil; \
    majorTime=refTime; \
    while(majorTime<=maxTime) { \
      _majorTickValues.append(_timeReference.secondsTo(majorTime)); \
      minorTime=majorTime; \
      minorTime.addMinorTicks; \
      majorTime.addMajorTicks; \
      if(majorTime<maxTime) { \
        while(minorTime<majorTime) { \
          _minorTickValues.append(_timeReference.secondsTo(minorTime)); \
          minorTime.addMinorTicks; \
        } \
      } else { \
        while(minorTime<maxTime) { \
          _minorTickValues.append(_timeReference.secondsTo(minorTime)); \
          minorTime.addMinorTicks; \
        } \
      } \
    } \
    minorTime=refTime; \
    minorTime.subMinorTicks; \
    while(minorTime>=minTime) { \
      _minorTickValues.prepend(_timeReference.secondsTo(minorTime)); \
      minorTime.subMinorTicks; \
    } \

  void Scale::setDateTimeTicks()
  {
    if(!_timeReference.isValid()) {
      return;
    }
    DateTime minTime=_timeReference;
    minTime.addSeconds(minimum());
    DateTime maxTime=_timeReference;
    maxTime.addSeconds(maximum());
    DateTime refTime=minTime;
    DateTime minorTime, majorTime;
    double delta=minTime.secondsTo(maxTime);
    double dminor, dmajor;
    int iminor, imajor;
    if(delta<=7200.0) {
      if(delta<=1200.0) {
        if(delta<=300.0) {
          if(delta<=60.0) {
            // Less than 60 s
            double expo=pow(10., floor(log10(delta)));
            delta/=expo;
            setTicks(delta, expo, dmajor, dminor);
            dmajor*=_majorTicks;
            dminor*=_minorTicks;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            _timeFormat="Csz's'";
          } else {
            // Between 60 s and 300 s
            dmajor=_majorTicks*30.0;
            dminor=_minorTicks*5.0;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            _timeFormat="Cmm:ss";
          }
        } else {
          if(delta<=600.0){
            // Between 300 s and 600 s
            dmajor=_majorTicks*60.0;
            dminor=_minorTicks*10.0;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
          } else {
            // Between 600 s and 1200 s
            dmajor=_majorTicks*120.0;
            dminor=_minorTicks*20.0;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
          }
          _timeFormat="Cm'm'";
        }
      } else {
        if(delta<=3600.0) {
          if(delta<=2400.0) {
            // Between 1200 s and 2400 s
            dmajor=_majorTicks*240.0;
            dminor=_minorTicks*60.0;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
          } else {
            // Between 2400 s and 3600 s
            dmajor=_majorTicks*300.0;
            dminor=_minorTicks*60.0;
            TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
          }
        } else {
          // Between 3600 s and 7200 s
          dmajor=_majorTicks*600.0;
          dminor=_minorTicks*120.0;
          TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
        }
        _timeFormat="Chh:mm";
      }
    } else {
      if(delta<=3628800.0) {
        if(delta<=86400.0) {
          if(delta<=21600.0) {
            if(delta<=14400.0) {
              // Between 7200 s and 14400 s (4h)
              dmajor=_majorTicks*1200.0;
              dminor=_minorTicks*240.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            } else {
              // Between 14400 s (4h) and 21600 s (6h)
              dmajor=_majorTicks*1800.0;
              dminor=_minorTicks*300.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            }
            _timeFormat="Chh:mm";
          } else {
            if(delta<=43200.0) {
              // Between 21600 s (6h) and 43200 s (12h)
              dmajor=_majorTicks*3600.0;
              dminor=_minorTicks*600.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            } else {
              // Between 43200 s (12h) and 86400 s (24h)
              dmajor=_majorTicks*7200.0;
              dminor=_minorTicks*1200.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            }
            _timeFormat="Ch'h'";
          }
        } else {
          if(delta<=345600.0) {
            if(delta<=172800.0) {
              // Between 86400 s (1d) and 172800 s (2d)
              dmajor=_majorTicks*14400.0;
              dminor=_minorTicks*3600.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            } else {
              // Between 172800 s (2d) and 345600 s (4d)
              dmajor=_majorTicks*43200.0;
              dminor=_minorTicks*7200.0;
              TIME_TICKS(ceilSeconds(dmajor), addSeconds(dmajor), addSeconds(dminor), addSeconds(-dminor))
            }
            _timeFormat="Cddd h'h'";
          } else {
            if(delta<=604800.0) {
              // Between 345600 s (4d) and 604800 s (1w)
              imajor=qRound(_majorTicks);
              dminor=_minorTicks*14400.0;
              TIME_TICKS(ceilDay(), addDays(imajor), addSeconds(dminor), addSeconds(-dminor))
            } else {
              // Between 604800 s (1w) and 3628800 s (6w)
              imajor=qRound(_majorTicks*7.0);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilWeek(), addDays(imajor), addDays(iminor), addDays(-iminor))
            }
            _timeFormat="CMMM-dd";
          }
        }
      } else {
        if(delta<=63115200.0) {
          if(delta<=15822086.0) {
            if(delta<=7911043.0) {
              // Between 3628800 s (6w) and 7911043 s (~3m)
              imajor=qRound(_majorTicks);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilHalfMonth(), addHalfMonths(imajor), addDays(iminor), addDays(-iminor))
              _timeFormat="CMMM-dd";
            } else {
              // Between 7911043 s (~3m) and 15822086 s (~6m)
              imajor=qRound(_majorTicks);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilMonths(1), addMonths(imajor), addHalfMonths(iminor), addHalfMonths(-iminor))
              _timeFormat="Cyy-MMM";
            }
          } else {
            if(delta<=31557600.0) {
              // Between 15822086 s (~6m) and 31557600 s (~1y)
              imajor=qRound(_majorTicks*2.0);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilMonths(imajor), addMonths(imajor), addHalfMonths(iminor), addHalfMonths(-iminor))
            } else {
              // Between 31557600 s (~1y) and 63115200 s (~2y)
              imajor=qRound(_majorTicks*3.0);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilMonths(imajor), addMonths(imajor), addMonths(iminor), addMonths(-iminor))
            }
            _timeFormat="Cyy-MMM";
          }
        } else {
          if(delta<=315576000.0) {
            if(delta<=157788000.0) {
              // Between 63115200 s (~2y) and 157788000 s (5y)
              imajor=qRound(_majorTicks*6.0);
              iminor=qRound(_minorTicks);
              TIME_TICKS(ceilMonths(imajor), addMonths(imajor), addMonths(iminor), addMonths(-iminor))
              _timeFormat="Cyy-MMM";
            } else {
              // Between 157788000 s (~5y) and 315576000 s (~10y)
              imajor=qRound(_majorTicks);
              iminor=qRound(_minorTicks*3.0);
              TIME_TICKS(ceilYears(imajor), addYears(imajor), addMonths(iminor), addMonths(-iminor))
              _timeFormat="Cyyyy";
            }
          } else {
            if(delta<=788940000.0) {
              // Between 315576000 s (~10y) and 788940000 s (~25y)
              imajor=qRound(_majorTicks*2.0);
              iminor=qRound(_minorTicks*3.0);
              TIME_TICKS(ceilYears(imajor), addYears(imajor), addMonths(iminor), addMonths(-iminor))
            } else {
              // Above 788940000 s (~25y)
              // Below 24 years, setTicks can return a decimal number
              delta=maxTime.date().year()-minTime.date().year();
              double expo=pow(10., floor(log10(delta)));
              delta/=expo;
              setTicks(delta, expo, dmajor, dminor);
              imajor=qRound(dmajor*_majorTicks);
              iminor=qRound(dminor*_minorTicks);
              TIME_TICKS(ceilYears(imajor), addYears(imajor), addYears(iminor), addYears(-iminor))
            }
            _timeFormat="Cyyyy";
          }
        }
      }
    }
  }

  /*!
    According to the min and max values, the numberType and the scaleType,
    typical rounded values are set for label and tick.

    For log scales label is set to 1 and tick is set to 0.2.

    If numberType is 't', a maximum of 12 labels is chosen, 6 ticks between
    two labels.

    For other scales, the difference between max and min is divided by
    the appropriate power of ten (expo) to get a number between 1 and 10.
    For example if the difference is between 1 and 10. The labels and ticks will be

      - if difference < 1.2:
         - labels - 0.2, 0.4, 0.6, 0.8, 1.0
         - ticks - 0.04, 0.08, 0.12, 0.16, 0.2, ...
      - if difference < 2.4:
         - labels - 0.4, 0.8, 1.2, 1.6, 2.0
         - ticks - 0.1, 0.2, 0.3, 0.4, ...
      - if difference < 6:
         - labels - 1, 2, 3, 4, 5
         - ticks - 0.2, 0.4, 0.6, 0.8, 1.0, ...
      - else:
         - labels - 2, 4, 6, 8, 10
         - ticks - 0.4, 0.8, 1.2, 1.6, 2.0, ...

    \sa setAutoTicks(), autoTicks()
  */
  void Scale::autoTicks()
  {
    TRACE;
    double delta;
    SAFE_UNINITIALIZED(delta, 0.0)

    switch (_type) {
    case AbsoluteTime:
      _majorTicks=1.0;
      _minorTicks=1.0;
      return;
    case Linear:
    case RelativeTime:
      delta=_maximum-_minimum;
      break;
    case Inversed:
      delta=1.0/_minimum-1.0/_maximum;
      break;
    case Log:
      _majorTicks=5.0;
      _minorTicks=1.0;
      return;
    case InversedLog:
      _majorTicks=5.0;
      _minorTicks=1.0;
      return;
    }
    double expo=pow(10., floor(log10(delta)));
    delta/=expo;
    setTicks(delta, expo, _majorTicks, _minorTicks);
  }

  inline bool Scale::safeAdd(double& value, double inc)
  {
    double oldValue=value;
    value+=inc;
    return value!=oldValue;
  }

  inline bool Scale::safeSubtract(double& value, double inc)
  {
    double oldValue=value;
    value-=inc;
    return value!=oldValue;
  }

  void Scale::cacheTicks()
  {
    TRACE;
    // In case of infinite or nan limits, tick and precision lists must be consistent
    // for instance after a modification of the scale type from linear to log.
    // For linear precision list is always empty, for log, it must have the same size as the other.
    _majorTickValues.clear();
    _minorTickValues.clear();
    _majorTickPrecision.clear();

    if(!std::isfinite(_globalMinimum) || !std::isfinite(_globalMaximum)) {
      return;
    }
    double visMin, visMax, majorTicks, minorTicks, baseTicks;
    double inv_majorTicks;
    double eps_baseTicks;

    SAFE_UNINITIALIZED(visMin, 0.0)
    SAFE_UNINITIALIZED(visMax, 0.0)
    SAFE_UNINITIALIZED(majorTicks, 0.0)
    SAFE_UNINITIALIZED(minorTicks, 0.0)
    SAFE_UNINITIALIZED(baseTicks, 0.0)
    SAFE_UNINITIALIZED(eps_baseTicks, 0.0)
    SAFE_UNINITIALIZED(inv_majorTicks, 0.0)

    switch (_type) {
    case AbsoluteTime:
      setDateTimeTicks();
      return; // already computed
    case Linear:
    case RelativeTime:
      visMin=_minimum;
      visMax=_maximum;
      majorTicks=_majorTicks;
      minorTicks=_minorTicks;
      inv_majorTicks=1.0/majorTicks;
      break;
    case Inversed:
      visMin=1.0/_maximum;
      visMax=1.0/_minimum;
      majorTicks=_majorTicks;
      minorTicks=_minorTicks;
      inv_majorTicks=1.0/majorTicks;
      break;
    case Log:
      visMin=_minimum;
      visMax=_maximum;
      baseTicks=pow(10.0, floor(log10(visMin)));
      majorTicks=baseTicks*_majorTicks;
      minorTicks=baseTicks*_minorTicks;
      inv_majorTicks=1.0/majorTicks;
      baseTicks*=10.0;
      eps_baseTicks=baseTicks*(1-1e-10);
      break;
    case InversedLog:
      visMin=1.0/_maximum;
      visMax=1.0/_minimum;
      baseTicks=pow(10.0, floor(log10(visMin)));
      majorTicks=baseTicks*_majorTicks;
      minorTicks=baseTicks*_minorTicks;
      inv_majorTicks=1.0/majorTicks;
      baseTicks*=10.0;
      eps_baseTicks=baseTicks*(1-1e-10);
      break;
    }
    int numberPrecision=0;
    if(majorTicks>0.0 && minorTicks>0.0) {
      double refX=majorTicks*ceil(visMin*inv_majorTicks);
      double originalMinorTicks=minorTicks;              // For log scales, minorTicks is modified
      double majorX=refX;
      double minorX;
      switch (_type) {
      case Linear:
      case RelativeTime:
      case Inversed:
        while(majorX<=visMax) {
          _majorTickValues.append(majorX);
          minorX=majorX;
          if(!safeAdd(minorX, minorTicks)) break;
          if(!safeAdd(majorX, majorTicks)) break;
          if(majorX<visMax) {
            while(minorX<majorX) {
              _minorTickValues.append(minorX);
              if(!safeAdd(minorX, minorTicks)) break;
            }
          } else {
            while(minorX<visMax) {
              _minorTickValues.append(minorX);
              if(!safeAdd(minorX, minorTicks)) break;
            }
          }
        }
        minorX=refX-originalMinorTicks;
        while(minorX>=visMin) {
          _minorTickValues.prepend(minorX);
          if(!safeSubtract(minorX, originalMinorTicks)) break;
        }
        break;
      case Log:
      case InversedLog:
        while(majorX<=visMax) {
          _majorTickValues.append(majorX);
          if(majorX>eps_baseTicks) {
            majorTicks*=10.0;
            minorTicks*=10.0;
            baseTicks*=10.0;
            eps_baseTicks=baseTicks*(1.0-1e-10);
            numberPrecision--;
            minorX=majorX;
            if(!safeAdd(minorX, minorTicks)) break;
            if(majorTicks>majorX) {
              majorX=majorTicks;
            } else {
              majorX=ceil(majorX/majorTicks)*majorTicks;
            }
          } else {
            minorX=majorX;
            if(!safeAdd(minorX, minorTicks)) break;
            if(!safeAdd(majorX, majorTicks)) break;
          }
          _majorTickPrecision.append(numberPrecision);
          if(majorX<visMax) {
            while(minorX<majorX) {
              _minorTickValues.append(minorX);
              if(!safeAdd(minorX, minorTicks)) break;
            }
          } else {
            while(minorX<visMax) {
              _minorTickValues.append(minorX);
              if(!safeAdd(minorX, minorTicks)) break;
            }
          }
        }
        minorX=refX-originalMinorTicks;
        while(minorX>=visMin) {
          _minorTickValues.prepend(minorX);
          if(!safeSubtract(minorX, originalMinorTicks)) break;
        }
        break;
      case AbsoluteTime:
        break;
      }
    }
  }

  void Scale::setTimeReference(const DateTime& t)
  {
    _timeReference=t;
    // Set fraction to null to limit rounding errors
    _timeReference.roundSeconds(1.0);
  }


} // namespace QGpCoreMath
