/***************************************************************************
                          kstarsdatetime.h  -  K Desktop Planetarium
                             -------------------
    begin                : Tue 05 May 2004
    copyright            : (C) 2001 by Jason Harris
    email                : jharris@30doradus.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#pragma once

#include <QDateTime>

#define J2000          2451545.0    //Julian Date for noon on Jan 1, 2000 (epoch J2000)
#define B1950          2433282.4235 // Julian date for Jan 0.9235, 1950
#define SIDEREALSECOND 1.002737909  //number of sidereal seconds in one solar second

class dms;

/** @class KStarsDateTime
 *@short Extension of QDateTime for KStars
 *KStarsDateTime can represent the date/time as a Julian Day, using a long double,
 *in which the fractional portion encodes the time of day to a precision of a less than a second.
 *Also adds Greenwich Sidereal Time and "epoch", which is just the date expressed as a floating
 *point number representing the year, with the fractional part representing the date and time
 *(with poor time resolution; typically the Epoch is only taken to the hundredths place, which is
 *a few days).
 *@note Local time and Local sideral time are not handled here.  Because they depend on the
 *geographic location, they are part of the GeoLocation class.
 *@sa GeoLocation::GSTtoLST()
 *@sa GeoLocation::UTtoLT()
 *@author Jason Harris
 *@version 1.0
 */

class KStarsDateTime : public QDateTime
{
  public:
    /**
         *@short Default constructor
         *Creates a date/time at J2000 (noon on Jan 1, 200)
         */
    KStarsDateTime();

    /**
         *@short Constructor
         *Creates a date/time at the specified Julian Day.
         *@p jd The Julian Day
         */
    explicit KStarsDateTime(long double djd);

    /**
         *@short Copy constructor
         *@p kdt The KStarsDateTime object to copy.
         */
    KStarsDateTime(const KStarsDateTime &kdt);

    /**
         *@short Copy constructor
         *@p qdt The QDateTime object to copy.
         */
    explicit KStarsDateTime(const QDateTime &qdt);

    /**
         *@short Constructor
         *Create a KStarsDateTimne based on the specified Date and Time.
         *@p _d The QDate to assign
         *@p _t The QTime to assign
         */
    KStarsDateTime(const QDate &_d, const QTime &_t, Qt::TimeSpec timeSpec = Qt::UTC);

    /**
         *Assign the (long double) Julian Day value, which includes the time of day
         *encoded in the fractional portion.
         *@p jd the Julian Day value to assign.
         */
    void setDJD(long double jd);

    /**
         *Assign the Date according to a QDate object.
         *@p d the QDate to assign
         */
    void setDate(const QDate &d);

    /**
         *Assign the Time according to a QTime object.
         *@p t the QTime to assign
         */
    void setTime(const QTime &t);

    /**
         *@return a KStarsDateTime that is the given number of seconds later
         *than this KStarsDateTime.
         *@p s the number of seconds to add.  The number can be negative.
         */
    KStarsDateTime addSecs(double s) const;

    /**
         *Modify the Date/Time by adding a number of days.
         *@p nd the number of days to add.  The number can be negative.
         */
    inline KStarsDateTime addDays(int nd) const { return KStarsDateTime(djd() + (long double)nd); }

    inline bool operator==(const KStarsDateTime &d) const { return DJD == d.djd(); }
    inline bool operator!=(const KStarsDateTime &d) const { return DJD != d.djd(); }
    inline bool operator<(const KStarsDateTime &d) const { return DJD < d.djd(); }
    inline bool operator<=(const KStarsDateTime &d) const { return DJD <= d.djd(); }
    inline bool operator>(const KStarsDateTime &d) const { return DJD > d.djd(); }
    inline bool operator>=(const KStarsDateTime &d) const { return DJD >= d.djd(); }

    /**
         *@return the date and time according to the CPU clock
         */
    static KStarsDateTime currentDateTime();

    /**
         *@return the UTC date and time according to the CPU clock
         */
    static KStarsDateTime currentDateTimeUtc();

    /**
         *@return a KStarsDateTime object parsed from the given string.
         *@note This function is format-agnostic; it will try several formats
         *when parsing the string.
         *@param s the string expressing the date/time to be parsed.
         */
    static KStarsDateTime fromString(const QString &s);

    /**
         *@return the julian day as a long double, including the time as the fractional portion.
         */
    inline long double djd() const { return DJD; }

    /**
         *@return The Greenwich Sidereal Time
         *The Greenwich sidereal time is the Right Ascension coordinate that is currently transiting
         *the Prime Meridian at the Royal Observatory in Greenwich, UK (longitude=0.0)
         */
    dms gst() const;

    /**
         *Convert a given Greenwich Sidereal Time to Universal Time (=Greenwich Mean Time).
         *@p GST the Greenwich Sidereal Time to convert to Universal Time.
         */
    QTime GSTtoUT(dms GST) const; // FIXME: Shouldn't this be static?

    /**
         *@enum EpochType description options
         *@note After 1976, the IAU standard for epochs is Julian Years.
         */
    enum EpochType
    {
        JULIAN,    /**< Julian epoch (see http://scienceworld.wolfram.com/astronomy/JulianEpoch.html) */
        BESSELIAN, /**< Besselian epoch (see http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html) */
    };

    /**
         *@return the (Julian) epoch value of the Date/Time.
         *@short This is (approximately) the year expressed as a floating-point value
         *@sa setFromEpoch()
         *@note The definition of Julian Epoch used here comes from http://scienceworld.wolfram.com/astronomy/JulianEpoch.html
         */
    inline double epoch() const { return 2000.0 + (djd() - J2000) / 365.25; }

    /**
         *Set the Date/Time from an epoch value, represented as a double.
         *@p e the epoch value
         *@sa epoch()
         */
    bool setFromEpoch(double e, EpochType type);

    /**
         *Set the Date/Time from an epoch value, represented as a string.
         *@p e the epoch value
         *@return true if date set successfully
         *@sa epoch()
         */
    bool setFromEpoch(const QString &e);

    /**
         *Set the Date/Time from an epoch value, represented as a double.
         *@p e the epoch value
         *@note This method assumes that the epoch 1950.0 is Besselian, otherwise assumes that the epoch is a Julian epoch. This is provided for backward compatibility, and because custom catalogs may still use 1950.0 to mean B1950.0 despite the IAU standard for epochs being Julian.
         *@sa epoch()
         */
    void setFromEpoch(double e);

    /**
         *@short Takes in an epoch and returns a Julian Date
         *@return the Julian Date (date with fraction)
         *@param epoch A floating-point year value specifying the Epoch
         *@param type JULIAN or BESSELIAN depending on what convention the epoch is specified in
         */
    static long double epochToJd(double epoch, EpochType type = JULIAN);

    /**
         *@short Takes in a Julian Date and returns the corresponding epoch year in the given system
         *@return the epoch as a floating-point year value
         *@param jd Julian date
         *@param type Epoch system (KStarsDateTime::JULIAN or KStarsDateTime::BESSELIAN)
         */
    static double jdToEpoch(long double jd, EpochType type = JULIAN);

    /**
         *@short Takes in a string and returns a Julian epoch
         */
    static double stringToEpoch(const QString &eName, bool &ok);

    /**
         * The following values were obtained from Eric Weisstein's world of science:
         * http://scienceworld.wolfram.com/astronomy/BesselianEpoch.html
         */
    constexpr static const double B1900        = 2415020.31352; // Julian date of B1900 epoch
    constexpr static const double JD_PER_BYEAR = 365.242198781; // Julian days in a Besselian year
  private:
    /**
         *@return the Greenwich Sidereal Time at 0h UT on this object's Date
         *@note used internally by gst() and GSTtoUT()
         */
    dms GSTat0hUT() const;

    long double DJD { 0 };
};
