/***************************************************************************
**
**  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: 2009-10-24
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef DATETIME_H
#define DATETIME_H

#include <QtCore>

#include "QGpCoreToolsDLLExport.h"
#include "Translations.h"
#include "Global.h"

namespace QGpCoreTools {

#ifndef CAP
/************************************************/
/* an older struct remaining from PITSA         */
/*************************************************/
typedef struct  PitsaTime  {
                int           yr;     /* year         */
                int           mo;     /* month        */
                int           day;    /* day          */
                int           hr;     /* hour         */
                int           mn;     /* minute       */
                float         sec;    /* second       */
                int           base;   /* GMT, DST,etc.*/
                } TIME;
#endif

class QGPCORETOOLS_EXPORT DateTimeData: public QSharedData
{
public:
  inline DateTimeData();
  inline DateTimeData(const QDate& day, int seconds, double fractions);
  inline DateTimeData(const QDateTime& dt);
  inline DateTimeData(const DateTimeData& o);
  ~DateTimeData() {}

  inline bool operator==(const DateTimeData& o) const;
  inline bool operator!=(const DateTimeData& o) const;
  inline bool operator<(const DateTimeData& o) const;
  inline bool operator<=(const DateTimeData& o) const;
  inline bool operator>(const DateTimeData& o) const;
  inline bool operator>=(const DateTimeData& o) const;

  inline void operator=(const DateTimeData& o);

  void addSeconds(double s);
  void addFewSeconds(double s);
  void addMinutes(int m);
  void addHours(int h);
  inline void addDays(int d);
  void addHalfMonths(int hm);
  inline void addMonths(int m);
  inline void addYears(int y);

  void roundSeconds(double s);
  void floorSeconds(double s);
  void ceilSeconds(double s);
  void floorDay() {_seconds=0; _fractions=0.0;}
  void ceilDay();
  void floorWeek();
  void ceilWeek();
  void floorHalfMonth();
  void ceilHalfMonth();
  void floorMonths(int m);
  void ceilMonths(int m);
  void floorYears(int y);
  void ceilYears(int y);

  void fromTime_t(time_t t);
  void fromTime_t(QString t);
  time_t toTime_t() const;
  QString toTime_t(int precision) const;
  bool fromString(const QString & text, const QString & format);
  QString toString(const QString & format, int precision) const;

  QDate date() const {return _date;}
  inline QDateTime dateTime() const;
  inline bool setDate(int year=2000, int month=1, int day=1);
  inline void setDate(const QDate& d);
  inline void setDateTime(const QDateTime& dt);

  int hour() const {return _seconds/3600;}
  int minute() const {return _seconds/60-hour()*60;}
  int second() const {return _seconds-(_seconds/60)*60;}
  QTime time() const {return QTime(hour(), minute(), second());}
  double fractions() const {return _fractions;}
  bool setTime(int hour=0, int minute=0, int second=0, double fractions=0.0);
  bool setTime(const QTime& t);

  double secondsTo(const DateTimeData& o) const;
  void average(DateTimeData& a, const DateTimeData& o) const;
  bool isValid() const {return _date.isValid() &&
                               _seconds>=0 && _seconds<86400 &&
                               _fractions>=0.0 && _fractions<1.0;}
  bool isNull() const {return _date.isNull() &&
                              (_seconds<0 || _seconds>=86400) &&
                              (_fractions<0.0 || _fractions>=1.0);}
  bool isMidnight() const {return _seconds==0 && _fractions==0.0;}

  TIME pitsaTime() const;
private:
  int _seconds;
  QDate _date;
  double _fractions;
};

inline DateTimeData::DateTimeData()
{
  _seconds=0;
  _fractions=0.0;
}

inline DateTimeData::DateTimeData(const QDate& day, int seconds, double fractions)
  : QSharedData()
{
  _date=day;
  _seconds=seconds;
  _fractions=fractions;
  ASSERT(_seconds>=0 && _seconds<86400);
  ASSERT(_fractions>=0.0 && _fractions<1.0);
}

inline DateTimeData::DateTimeData(const QDateTime& dt)
    : QSharedData()
{
  _date=dt.date();
  _seconds=QTime(0,0).secsTo(dt.time());
  _fractions=0;
}

inline DateTimeData::DateTimeData(const DateTimeData& o)
    : QSharedData(o)
{
  _date=o._date;
  _seconds=o._seconds;
  _fractions=o._fractions;
}

inline bool DateTimeData::operator==(const DateTimeData& o) const
{
  return _date==o._date &&
         _seconds==o._seconds &&
         _fractions==o._fractions;
}

inline bool DateTimeData::operator!=(const DateTimeData& o) const
{
  return _date!=o._date ||
         _seconds!=o._seconds ||
         _fractions!=o._fractions;
}

inline bool DateTimeData::operator<(const DateTimeData& o) const
{
  if(_date==o._date) {
    if(_seconds==o._seconds) {
      return _fractions<o._fractions;
    } else {
      return _seconds<o._seconds;
    }
  } else {
    return _date<o._date;
  }
}

inline bool DateTimeData::operator<=(const DateTimeData& o) const
{
  if(_date==o._date) {
    if(_seconds==o._seconds) {
      return _fractions<=o._fractions;
    } else {
      return _seconds<=o._seconds;
    }
  } else {
    return _date<=o._date;
  }
}

inline bool DateTimeData::operator>(const DateTimeData& o) const
{
  if(_date==o._date) {
    if(_seconds==o._seconds) {
      return _fractions>o._fractions;
    } else {
      return _seconds>o._seconds;
    }
  } else {
    return _date>o._date;
  }
}

inline bool DateTimeData::operator>=(const DateTimeData& o) const
{
  if(_date==o._date) {
    if(_seconds==o._seconds) {
      return _fractions>=o._fractions;
    } else {
      return _seconds>=o._seconds;
    }
  } else {
    return _date>=o._date;
  }
}

inline void DateTimeData::operator=(const DateTimeData& o)
{
  _date=o._date;
  _seconds=o._seconds;
  _fractions=o._fractions;
}

inline void DateTimeData::addDays(int d)
{
  _date=_date.addDays(d);
}

inline void DateTimeData::addMonths(int m)
{
  _date=_date.addMonths(m);
}

inline void DateTimeData::addYears(int y)
{
  _date=_date.addYears(y);
}

inline bool DateTimeData::setDate(int year, int month, int day)
{
  return _date.setDate(year, month, day);
}

inline void DateTimeData::setDate(const QDate& d)
{
  _date=d;
}

inline void DateTimeData::setDateTime(const QDateTime& dt)
{
  _date=dt.date();
  _seconds=QTime(0,0,0).secsTo(dt.time());
  _fractions=dt.time().msec()*0.001;
}

inline QDateTime DateTimeData::dateTime() const
{
#if(QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
  QDateTime dt(_date);
#else
  QDateTime dt(_date.startOfDay());
#endif
  dt=dt.addSecs(_seconds);
  return dt;
}

class QGPCORETOOLS_EXPORT DateTime
{
  TRANSLATIONS("DateTime")
public:
  DateTime() {_d=new DateTimeData;}
  DateTime(const QDate& day, int seconds, double fractions) {_d=new DateTimeData(day, seconds, fractions);}
  DateTime(const QDateTime& dt) {_d=new DateTimeData(dt);}
  DateTime(const DateTime& o) : _d(o._d) {}
  ~DateTime() {}

  bool operator==(const DateTime& o) const {return *_d==*o._d;}
  bool operator!=(const DateTime& o) const {return *_d!=*o._d;}
  bool operator<(const DateTime& o) const {return *_d<*o._d;}
  bool operator<=(const DateTime& o) const {return *_d<=*o._d;}
  bool operator>(const DateTime& o) const {return *_d>*o._d;}
  bool operator>=(const DateTime& o) const {return *_d>=*o._d;}

  void operator=(const DateTime& o) {return *_d=*o._d;}

  inline DateTime operator+(double s) const;
  inline void operator+=(double s) {addSeconds(s);}

  void addSeconds(double s) {_d->addSeconds(s);}
  void addFewSeconds(double s) {_d->addFewSeconds(s);}
  void addMinutes(int m) {_d->addMinutes(m);}
  void addHours(int h) {_d->addHours(h);}
  void addDays(int d) {_d->addDays(d);}
  void addHalfMonths(int hm) {_d->addHalfMonths(hm);}
  void addMonths(int m) {_d->addMonths(m);}
  void addYears(int y) {_d->addYears(y);}

  void roundSeconds(double s) {_d->roundSeconds(s);}
  void floorSeconds(double s) {_d->floorSeconds(s);}
  void ceilSeconds(double s) {_d->ceilSeconds(s);}
  void floorDay() {_d->floorDay();}
  void ceilDay() {_d->ceilDay();}
  void floorWeek() {_d->floorWeek();}
  void ceilWeek() {_d->ceilWeek();}
  void floorHalfMonth() {_d->floorHalfMonth();}
  void ceilHalfMonth() {_d->ceilHalfMonth();}
  void floorMonths(int m) {_d->floorMonths(m);}
  void ceilMonths(int m) {_d->ceilMonths(m);}
  void floorYears(int y) {_d->floorYears(y);}
  void ceilYears(int y) {_d->ceilYears(y);}
  DateTime shifted(double s) const {DateTime t(*this); t.addSeconds(s); return t;}

  void fromTime_t(time_t t) {_d->fromTime_t(t);}
  void fromTime_t(QString t) {_d->fromTime_t(t);}
  time_t toTime_t() const {return _d->toTime_t();}
  QString toTime_t(int precision) const {return _d->toTime_t(precision);}

  bool fromString(const QString & text, const QString & format=defaultFormat) {return _d->fromString(text, format);}
  QString toString(const QString & format=defaultFormat, int precision=6) const {return _d->toString(format, precision);}

  QDate date() const {return _d->date();}
  inline QDateTime dateTime() const {return _d->dateTime();}
  bool setDate(int year=1900, int month=1, int day=1) {return _d->setDate(year, month, day);}
  void setDate(const QDate& d) {_d->setDate(d);}
  void setDateTime(const QDateTime& dt) {_d->setDateTime(dt);}

  int hour() const {return _d->hour();}
  int minute() const {return _d->minute();}
  int second() const {return _d->second();}
  QTime time() const {return _d->time();}
  double fractions() const {return _d->fractions();}
  bool setTime(int hour=0, int minute=0, int second=0, double fractions=0.0) {
    return _d->setTime(hour, minute, second, fractions);
  }
  bool setTime(const QTime& t) {return _d->setTime(t);}

  double secondsTo(const DateTime& o) const {return _d->secondsTo(*o._d);}
  DateTime average(const DateTime& o) const {DateTime t; _d->average(*t._d, *o._d); return t;}

  bool isValid() const {return _d->isValid();}
  bool isNull() const {return _d->isNull();}

  TIME pitsaTime() const {return _d->pitsaTime();}

  static const DateTime minimumTime, maximumTime;
  static const DateTime null;
  static const QString defaultFormat, defaultUserFormat;
private:
  QSharedDataPointer<DateTimeData> _d;
};

inline DateTime DateTime::operator+(double s) const
{
  DateTime d=*this;
  d.addSeconds(s);
  return d;
}

} // namespace QGpCoreTools

#endif // DATETIME_H
