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

#include <math.h>

#include <qglobal.h>

#include "Number.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

  QLocale Number::_locale;

  Number::Number()
  {
    _locale=QLocale();
    _locale.setNumberOptions(QLocale::OmitGroupSeparator);
  }

  /*!
    \fn Number::Type Number::type(char t)
    Convert char format to type

    \li 'f' for Fixed
    \li 'e' for Scientific
    \li 't' for Time
  */

  /*!
    Convert any value into a valid user string using \a format and \a precision.
    Current QLocale settings are taken into account.
    Do not use this function to store values in a file.
    Negative precisions are considered as maximum precision.
  */
  QString Number::toUserString(double val, char format, int precision, QLocale *locale)
  {
    TRACE;
    // Eventually set automatic precision
    if(precision<0) {
      precision=-precision;
      double vr=floor(val);
      double vf=val;
      int i=0;
      for(; i<precision && vr!=vf; i++) {
        vf*=10.0;
        vr=floor(vf);
      }
      precision=i;
    }
    return locale->toString(val, format, precision);
  }

  /*!
    Convert a user string into a double value using type \a t.
    Current QLocale settings are taken into account.
    Do not use this function to convert values stored in a file.
    If conversion is OK, \a ok is not touched, nor set to true.
  */
  double Number::toDouble(const QString& str, bool &ok, QLocale * locale)
  {
    TRACE;
    bool qok;
    double val=locale->toDouble(str, &qok);
    if(!qok) {
      ok=false;
    }
    return val;
  }

  /*!
    Convert a QVariant into a double value using \a locale.
    If conversion is OK, \a ok is not touched, nor set to true.
  */
  double Number::toDouble(const QVariant& val, bool &ok, QLocale * locale)
  {
    TRACE;
    if(val.type()==QVariant::Double) {
      return val.toDouble();
    } else {
      return toDouble(val.toString(), ok, locale);
    }
  }

  /*!
    \fn double Numer::toDouble(double a)
    Does nothing else than returning \a a. Offers a transparent conversion from any real type to double.

    \sa toDouble(mpf_class a), toDouble (float a)
  */

  /*!
    \fn double Number::toDouble(mpf_class a)
    Converts arbitrary precision \a to a double. Offers a transparent conversion from any real type to double.

    \sa toDouble(double a), toDouble (float a)
  */

  /*!
    Converts a float to a double keeping track of rounding errors.
    Converting a float to a double in a basic way may lead to strange results.

    \pre
    float f=0.002;
    double d=f;
    // d is 0.0020000000949949026108
    d=0.002;
    // d is 0.0020000000000000000416
    \pre

    Tests made with this function:

    0.002 returns 0.0020000000000000000416 instead of 0.0020000000949949026108
    0.0020405 returns 0.0020405000000000002129 instead of 0.0020405000541359186172
    0.002040567 returns 0.0020405670000000000752 instead of 0.0020405671093612909317
    204056700000 returns 204056705154.90594482 instead of 204056707072

    Offers a transparent conversion from any real type to double.

    \sa toDouble(double a), toDouble (float a)
  */
  double Number::toDouble(float f)
  {
    if (f==0.0) return 0.0;
    // Extracts the exponent value of the float (bits 2-9)
    // Generates a warning difficult to avoid... any other solution?
  #if Q_BYTE_ORDER==Q_BIG_ENDIAN
    int exp=((*(reinterpret_cast<unsigned short*>(&f)) & 0x7F80) >> 7)-127;
  #else
    int exp=((*(reinterpret_cast<unsigned short*>(&f)+1) & 0x7F80) >> 7)-127;
  #endif
    // log10(2)=0.301029995663981198
    // Precision for float mantissa is 1/2^23, look for power of 10 to multiply f so that
    // 1/2^23 stand after decimal point, to remove it by rounding.
    double fac=pow(10.0,floor(0.301029995663981198*(22-exp)));
    return ::round(fac*f)/fac;
  }

  /*!
    Return all divisors of \a n
  */
  QList<int> Number::divisors(int n)
  {
    // max number to analyse: 157609=397*397
    static const int primes[]={   2,   3,   5,   7,  11,  13,  17,  19,  23,  29,  31,  37,  41,  43,  47,
                                   53,  59,  61,  67,  71,  73,  79,  83,  89,  97, 101, 103, 107, 109, 113,
                                  127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197,
                                  199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
                                  283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379,
                                  383, 389, 397};
    QList<int> ds;
    int q=398, d=2, i=0;
    while(d<q) {
      q=n/d;
      if((q*d)==n) {
        n=q;
        ds << d;
      } else {
        i++;
      }
      d=primes[i];
    }
    ds << n;
    return ds;
  }

  /*!
    Returns the next power of two for any unsigned integer less than or equal to 2147483647.
    Inspired from http://acius2.blogspot.com/2007/11/calculating-next-power-of-2.html

    Converts number to a solid '1' number before adding just 1.
  */
  unsigned int Number::nextPowerOfTwo(unsigned int val)
  {
    val--; // if val is already a power of 2
    val=(val >> 1) | val;
    val=(val >> 2) | val;
    val=(val >> 4) | val;
    val=(val >> 8) | val;
    val=(val >> 16) | val;
    val++;
    return val;
  }

  unsigned char Number::fromBinaryCodedDecimal(unsigned char bcd)
  {
    return ((bcd & 0xF0u) >> 3)*5u+
           (bcd & 0x0Fu);
  }

  unsigned short Number::fromBinaryCodedDecimal(unsigned short bcd)
  {
    return ((bcd & 0xF000u) >> 9)*125u+
           ((bcd & 0x0F00u) >> 6)*25u+
           ((bcd & 0x00F0u) >> 3)*5u+
           (bcd & 0x000Fu);
  }

  unsigned int Number::fromBinaryCodedDecimal(unsigned int bcd)
  {
    return ((bcd & 0xF0000000u) >> 21)*78125u+
           ((bcd & 0x0F000000u) >> 18)*15625u+
           ((bcd & 0x00F00000u) >> 15)*3125u+
           ((bcd & 0x000F0000u) >> 12)*625u+
           ((bcd & 0x0000F000u) >> 9)*125u+
           ((bcd & 0x00000F00u) >> 6)*25u+
           ((bcd & 0x000000F0u) >> 3)*5u+
           (bcd & 0x0000000Fu);
  }

  unsigned char Number::toBinaryCodedDecimal(unsigned char val)
  {
    if(val>=100) {
      App::log(tr("Number::toBinaryCodedDecimal: overflow, value>=100\n") );
      return 0x99;
    }
    unsigned char bcd;
    ldiv_t d;
    d=ldiv(val, 10);
    bcd=d.rem;
    if(d.quot>0) {
      bcd+=d.quot << 4;
    }
    return bcd;
  }

  unsigned short Number::toBinaryCodedDecimal(unsigned short val)
  {
    if(val>=10000u) {
      App::log(tr("Number::toBinaryCodedDecimal: overflow, value>=10,000\n") );
      return 0x9999;
    }
    unsigned short bcd;
    ldiv_t d;
    d=ldiv(val, 10);
    bcd=d.rem;
    if(d.quot>9) {
      d=ldiv(d.quot, 10);
      bcd+=d.rem << 4;
      if(d.quot>9) {
        d=ldiv(d.quot, 10);
        bcd+=d.rem << 8;
        if(d.quot>0) {
          bcd+=d.quot << 12;
        }
      } else {
        bcd+=d.quot << 8;
      }
    } else if(d.quot>0) {
      bcd+=d.quot << 4;
    }
    return bcd;
  }

  unsigned int Number::toBinaryCodedDecimal(unsigned int val)
  {
    if(val>=100000000u) {
      App::log(tr("Number::toBinaryCodedDecimal: overflow, value>=100,000,000\n") );
      return 0x99999999;
    }
    unsigned int bcd;
    ldiv_t d;
    d=ldiv(val, 10);
    bcd=d.rem;
    if(d.quot>9) {
      d=ldiv(d.quot, 10);
      bcd+=d.rem << 4;
      if(d.quot>9) {
        d=ldiv(d.quot, 10);
        bcd+=d.rem << 8;
        if(d.quot>9) {
          d=ldiv(d.quot, 10);
          bcd+=d.rem << 12;
          if(d.quot>9) {
            d=ldiv(d.quot, 10);
            bcd+=d.rem << 16;
            if(d.quot>9) {
              d=ldiv(d.quot, 10);
              bcd+=d.rem << 20;
              if(d.quot>9) {
                d=ldiv(d.quot, 10);
                bcd+=d.rem << 24;
                if(d.quot>0) {
                  bcd+=d.quot << 28;
                }
              } else {
                bcd+=d.quot << 24;
              }
            } else {
              bcd+=d.quot << 20;
            }
          } else {
            bcd+=d.quot << 16;
          }
        } else {
          bcd+=d.quot << 12;
        }
      } else {
        bcd+=d.quot << 8;
      }
    } else if(d.quot>0) {
      bcd+=d.quot << 4;
    }
    return bcd;
  }

  /*!
    Round \a val so that the difference between two distinct values is less
    than \a maximum
  */
  double Number::round(double val, double maximum)
  {
    if(maximum==1.0) {
      return ::round(val);
    } else {
      double factor=pow(10.0, floor(log10(maximum)));
      return ::round(val/factor)*factor;
    }
  }

  /*!
    Convert \a sec seconds into a string with format ".w.d.h.n.s"
  */
  QString Number::secondsToDuration(double sec, int numberPrecision, QLocale * locale)
  {
    TRACE;
    if(sec==0.0) {
      return "0s";
    }
    QString str;
    if(sec<0.0) {
      sec=-sec;
      str+="-";
    }
    double sec0=sec;
    if(sec>=604800.0) {
      int week=qFloor(sec/604800.0);
      sec=sec-static_cast<double>(week)*604800.0;
      str+=locale->toString(week);
      str+="w";
      if(sec==0.0) return str;
    }
    if(sec>=86400.0) {
      int day=qFloor(sec/86400.0);
      sec=sec-static_cast<double>(day)*86400.0;
      str+=locale->toString(day);
      str+="d";
      if(sec==0.0) return str;
    } else if(sec<sec0) {
      str+="0d";
    }
    if(sec>=3600.0) {
      int hour=qFloor(sec/3600.0);
      sec=sec-static_cast<double>(hour)*3600.0;
      str+=locale->toString(hour);
      str+="h";
      if(sec==0.0) return str;
    } else if(sec<sec0) {
      str+="0h";
    }
    if(sec>=60.) {
      int min=qFloor(sec/60.0);
      sec=sec-static_cast<double>(min)*60.0;
      str+=locale->toString(min);
      str+="m";
      if(sec==0.0) return str;
    } else if(sec<sec0) {
      str+="0m";
    }
    if(sec>0.0) {
      str+=locale->toString(sec,'f',numberPrecision);
      str+="s";
    }
    return str;
  }

  double Number::durationToSecondsError(const QString& str, int pos, bool &ok)
  {
    TRACE;
    App::log(tr("Error parsing time '%1' at position %2\n").arg(str).arg(pos) );
    ok=false;
    return 0.0;
  }

  /*!
    Loop until the first non-blank character. If it is a '-' return true.
  */
  inline bool Number::durationToSecondsSign(const QChar * p, int& i, int n)
  {
    TRACE;
    for(i=0; i<n; i++ ) {
      switch(p[i].unicode()) {
      case ' ':
        break;
      case '-':
        i++;
        return true;
      default:
        return false;
      }
    }
    return false;
  }

  double Number::durationToSeconds(const QVariant& val, bool &ok, QLocale * locale)
  {
    TRACE;
    if(val.type()==QVariant::Double) {
      return val.toDouble();
    } else {
      return durationToSeconds(val.toString(), ok, locale);
    }
  }

  /*!
    Converts a string with format ".w.d.h.n.s" or "h:m:s" into a number of seconds
  */
  double Number::durationToSeconds(const QString& str, bool &ok, QLocale * locale)
  {
    TRACE;
    const QChar * p=str.data();
    int n=str.count();
    QString number;
    bool qok;
    int vali;
    double vald;
    double sec=0.0;
    enum ColonState {Unknown, NoColon, Hours, Minutes, Seconds};
    ColonState colonState=Unknown;
    double lastNumberFactor=1.0;
    // Skip first blanks and check sign
    int i;
    bool neg=durationToSecondsSign(p, i, n);
    for(; i<n; i++) {
      switch(p[i].unicode()) {
      case ' ':
        break;
      case ':':
        if(number.isEmpty()) return durationToSecondsError(str, i, ok);
        switch(colonState) {
        case Unknown:
        case Hours:
          vali=locale->toInt(number, &qok);
          if(!qok) return durationToSecondsError(str, i, ok);
          sec+=vali*3600.0;
          colonState=Minutes;
          lastNumberFactor=60.0; // If next number is the last one, it is minutes
          break;
        case Minutes:
          vali=locale->toInt(number, &qok);
          if(!qok) return durationToSecondsError(str, i, ok);
          sec+=vali*60.0;
          colonState=Seconds;
          lastNumberFactor=1.0; // If next number is the last one, it is seconds
          break;
        case Seconds:
          vald=locale->toDouble(number, &qok);
          if(!qok) return durationToSecondsError(str, i, ok);
          sec+=vald;
          colonState=Seconds;
          break;
        case NoColon:
          return durationToSecondsError(str, i, ok);
        }
        number.clear();
        break;
      case 'w':
        switch (colonState) {
        case NoColon:
          break;
        case Unknown:
          colonState=NoColon;
          break;
        default:
          return durationToSecondsError(str, i, ok);
        }
        vali=locale->toInt(number, &qok);
        if(!qok) return durationToSecondsError(str, i, ok);
        sec+=vali*604800.0;
        number.clear();
        lastNumberFactor=86400.0; // If next number is the last one, it is days
        break;
      case 'd':
        switch (colonState) {
        case NoColon:
          break;
        case Unknown:
          colonState=NoColon;
          break;
        default:
          return durationToSecondsError(str, i, ok);
        }
        vali=locale->toInt(number, &qok);
        if(!qok) return durationToSecondsError(str, i, ok);
        sec+=vali*86400.0;
        number.clear();
        lastNumberFactor=3600.0; // If next number is the last one, it is hours
        break;
      case 'h':
        switch (colonState) {
        case NoColon:
          break;
        case Unknown:
          colonState=NoColon;
          break;
        default:
          return durationToSecondsError(str, i, ok);
        }
        vali=locale->toInt(number, &qok);
        if(!qok) return durationToSecondsError(str, i, ok);
        sec+=vali*3600.0;
        number.clear();
        lastNumberFactor=60.0; // If next number is the last one, it is minutes
        break;
      case 'm':
        switch (colonState) {
        case NoColon:
          break;
        case Unknown:
          colonState=NoColon;
          break;
        default:
          return durationToSecondsError(str, i, ok);
        }
        vali=locale->toInt(number, &qok);
        if(!qok) return durationToSecondsError(str, i, ok);
        sec+=vali*60.0;
        number.clear();
        lastNumberFactor=1.0; // If next number is the last one, it is seconds
        break;
      case 's':
        switch (colonState) {
        case NoColon:
          break;
        case Unknown:
          colonState=NoColon;
          break;
        default:
          return durationToSecondsError(str, i, ok);
        }
        vald=locale->toDouble(number, &qok);
        if(!qok) return durationToSecondsError(str, i, ok);
        sec+=vald;
        number.clear();
        break;
      default:
        // For Arabic, Farsi or other scripts, numbers are not the latin digits 0 to 9 and .
        // So we accept all characters. In case of error, it will be reported later on
        // when converting to int or double.
        number+=p[i];
        break;
      }
    }
    if(!number.isEmpty()) {
      vald=locale->toDouble(number, &qok);
      if(!qok) return durationToSecondsError(str, i, ok);
      sec+=vald*lastNumberFactor;
    }
    return neg ? -sec : sec;
  }

} // namespace QGpCoreTools
