/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#pragma once

#include "mars.h"

#include <ostream>
#include <string>

//! Class to handle dates and times
/*! This class stores the date as a Julian day (where the reference day #0
 *  is Monday 01 January 4713 BCE). The time part is stored as seconds since
 *  midnight.
 *  The class contains methods, among others, to compare or increase or decrease
 *  dates, extract different date and time components, and format for printing.\n \n
 *  \b Customisable \n
 *  Non-numeric date information (short and full month names, short and full
 *  weekday names), as well as the default date formats, are user customisable
 *  through Metview Preferences menu item.
 *  This makes it very easy for non-English users to translate these texts
 *  into their own language and to customise the default date format to their
 *  own date writing standard (or for USA users to use their weird notation
 *  of swapped month and day ;-).\n
 *  For the time part, only formats for 24 hour clock have been implemented,
 *  i.e. it is not possible to customise hours to be formatted like "6pm" or "10AM".\n \n
 *  \b Reference: \n
 *  Collected Algorithms from CACM.\n
 *  Algorithm #199 - "Conversions between Calendar Date and Julian Day Number"
 */

class MvDate
{
    //! Friend function to write MvDate objects
    /*! The format is taken from user's Metview Preferences.
     *  The default in Metview is ISO 8601 standard date and time format.
     */
    friend std::ostream& operator<<(std::ostream& aStream, const MvDate& dd);

    static const char* stringFormat;
    static const char* numberFormat;

    long julian;
    long second;

    //! Internal comparision function
    int compare(const MvDate& d) const
    {
        return julian != d.julian ? julian - d.julian : second - d.second;
    }

public:
    //! Constructor; by default sets today's date at 00UTC
    /*! Negative values can be used to indicate a relative date.
     *  By default relative days are relative to today, unless
     *  environment variable \c MARS_REFERENCE_DATE has been set.\n \n
     *  The desimal part is interpreted as a fraction of a day (of 24 hours),
     *  thus 0.25 indicates 06 utc and 0.875 indicates 21 utc.\n \n
     *  NOTE that there is no validity test for the syntax and
     *  some clearly invalid values are converted to valid dates,
     *  for example \c 20081535 becomes 2009-04-06!
     */
    MvDate(double n = 0);

    //! Copy constructor
    MvDate(const MvDate& d)
    {
        julian = d.julian;
        second = d.second;
    }

    //! Constructor from a string
    /*! If \c dd has a valid date syntax, the constructor
     *  parses \c dd into the internal presentation.
     *  Otherwise first converts \c dd into an integer which
     *  is then interpreted as a date.\n \n
     *  NOTE that there is no validity test for the syntax and
     *  some clearly invalid values are converted to valid dates,
     *  for example "2008-15-35" becomes 2009-04-06!
     */
    MvDate(const char* dd);

    //! Assignment operator
    MvDate& operator=(const MvDate& d)
    {
        julian = d.julian;
        second = d.second;
        return *this;
    }

    //! Returns the internal Julian day number (days since 01 January 4713 BCE)
    long _julian() const { return julian; }

    //! Returns the internal time of the day, in seconds since midnight
    long _second() const { return second; }

    //! Decreases the date/time
    /*! The integer part of \c ddtt represents days and the decimal part
     *  is for hours and minutes (as a fraction of 24 hours). Thus the value
     *  of \c 1.75 as an operand would "subtract" one day and 18 hours
     *  from the operand MvDate object.\n \n
     *  Example:\n
     *  <PRE>
     *     MvDate d;            //-- today at 00 utc
     *     MvDate x = d - 1.75; //-- day before yesterday at 06 utc
     *  </PRE>
     */
    MvDate operator-(double ddtt) const;

    //! Increases the date/time
    /*! The integer part of \c ddtt represents days and the decimal part
     *  is for hours and minutes (as a fraction of a day). Thus the value
     *  of \c 1.25 as an argument would "add" one day and 6 hours
     *  to the operand MvDate object.
     */
    MvDate operator+(double ddtt) const;

    //! Increases the current date/time
    /*! Example:\n
     *  <PRE>
     *     MvDate d(0);   //-- today at 00 utc
     *     d += 1.125;    //-- tomorrow at 03 utc
     *  </PRE>
     */
    MvDate& operator+=(double ddtt);

    //! Decreases the current date/time
    MvDate& operator-=(double ddtt);

    //! Returns the time difference between two MvDate objects
    /*! The integer part of the returned value represents days
     *  and the decimal part represents the fraction of a day
     */
    double operator-(const MvDate&) const;

    //! 'Greater-than' comparison operator
    bool operator>(const MvDate& d) const { return compare(d) > 0; }

    //! 'Less-than' comparison operator
    bool operator<(const MvDate& d) const { return compare(d) < 0; }

    //! 'Greater-or-equal' comparison operator
    bool operator>=(const MvDate& d) const { return compare(d) >= 0; }

    //! 'Less-or-equal' comparison operator
    bool operator<=(const MvDate& d) const { return compare(d) <= 0; }

    //! 'Equal' comparison operator
    bool operator==(const MvDate& d) const { return compare(d) == 0; }

    //! 'Non-equal' comparison operator
    bool operator!=(const MvDate& d) const { return compare(d) != 0; }

    //! "C" style print method for standard output
    /*! Use "C++" style friend operator<<() for chaining several output
     *  objects or for writing to any stream
     */
    void Print() const;

    //! Returns the date part as an integer YYMMDD
    /*! WARNING: two digit year!
     */
    int YyMmDd(void) const { return mars_julian_to_date(julian, false); }

    //! Returns the date part as an integer YYYYMMDD
    int YyyyMmDd(void) const { return mars_julian_to_date(julian, true); }

    //! Returns the date part as an integer YYYYddd
    /*! Digits \c ddd represent the running day (Julian) of the year
     */
    int yyyyddd(void) const;

    //! Returns the year part of the date
    int Year(void) const { return YyyyMmDd() / 10000; }

    //! Returns the month part of the date
    int Month(void) const { return (YyyyMmDd() / 100) % 100; }

    //! Returns the day part of the date
    int Day(void) const { return YyyyMmDd() % 100; }

    //! Returns the hour part of the time
    int Hour(void) const { return second / 3600; }

    //! Returns the minutes part of the time
    int Minute(void) const { return (second / 60) % 60; }

    //! Returns the seconds part of the time
    int Second(void) const { return second % 60; }

    //! Returns the time as an integer HHMMSS
    int hhmmss() const;

    //! Returns the Julian day within the year of MvDate
    int Julian(void) const;

    //! Returns the day of the week: 1=Monday, 7=Sunday
    int DayOfWeek(void) const { return julian % 7 + 1; }

    //! Creates a custom formatted date/time text string
    /*! Argument \c fmt defines the format and \c buf is the target character array.
     *  Recognised format letter combinations are:
     *  <PRE>
     *     yy   - year with 2 digits
     *     yyyy - year with 4 digits
     *     m    - month with minimum digits
     *     mm   - month with 2 digits
     *     mmm  - short month name (user customisable)
     *     mmmm - full month name (user customisable)
     *     d    - day of the month with minimum digits
     *     dd   - day of the month with 2 digits
     *     ddd  - short weekday name (user customisable)
     *     dddd - full weekday name (user customisable)
     *     D    - Julian day with minimum digits
     *     DDD  - Julian day with 3 digits
     *     H    - hour with minimum digits (24 hour clock)
     *     HH   - hour with 2 digits (24 hour clock)
     *     M    - minutes with minimum digits
     *     MM   - minutes with 2 digits
     *     S    - seconds with minimum digits
     *     SS   - seconds with 2 digits
     *  </PRE> All other characters are used as is. Users can customise
     *  month and weekday names through Metview User Interface.\n
     *  Here are some (non-customised) examples:
     *  <PRE>
     *    MvDate d(20080105.375);
     *    std::cout << d << std::endl;   //-- 2008-01-05 09:00:00
     *
     *    char buf[100];
     *    d.Format("d mmm yyyy at H:M", buf);
     *    std::cout << buf << std::endl; //-- 5 Jan 2008 at 9:0
     *
     *    d.Format("d/m/yy - yymmdd:HH", buf);
     *    std::cout << buf << std::endl; //-- 5/1/08 - 080105:09
     *
     *    d.Format("dd/mm/yyyy @ HH:MM", buf);
     *    std::cout << buf << std::endl; //-- 05/01/2008 @ 09:00
     *
     *    d.Format("dddd d mmmm yyyy", buf);
     *    std::cout << buf << std::endl; //-- Saturday 5 January 2008
     *  </PRE>
     */
    void Format(const char* fmt, char* buf) const;

    //! Returns the number of days of the month in the current object
    int daysInMonth() const;


    // day of the year starting from 1 January as 0
    double dayOfTheYear() const;

    //! Subtracts date \c dd from the current object
    /*! The returned value is an integer of the form \c mmddHH
     *  where \c mm is for months, \c dd for days, and \c HH for hours.
     *  Thus returned value \c 1506 indicates a difference of \c 0
     *  months, \c 15 days and \c 6 hours.
     */
    long magicsSub(const MvDate& dd) const;

    //! Advances the current object date/time
    /*! The argument \c mmddHH is an integer, as described
     *  for the return value from method magicsSub()
     */
    MvDate magicsAdd(long mmddHH) const;

    //! Returns the date formatted as "yyyy-mm-dd HH:MM", suitable for Magics 6
    /*! See Format()
     */
    std::string magicsDate() const;


    //! Returns the date formatted as ISO8641 "YYYY-MM-DDTHH:00:00Z"
    /*! See Format()
     */
    std::string ISO8601() const;


    //! Returns the date formatted as "yyyymmdd.f"
    /*! Where \c .f is a fraction of a day (of 24 hours)
     */
    double YyyyMmDd_r() const;

    //! Returns the time difference between \c dd and the current object, in days
    /*! The decimal part will represent a fraction of a day (of 24 hours),
     *  i.e. 3.25 would represent 3 days and 6 hours
     */
    double time_interval_days(const MvDate& dd) const;

    //! Returns the time difference between \c dd and the current object, in hours
    /*! The decimal part will represent a fraction of an hour (of 60 minutes)
     *  i.e. 3.25 would represent 3 hours and 15 minutes
     */
    double time_interval_hours(const MvDate& dd) const;

    //! Returns the time difference between \c dd and the current object, in minutes
    /*! The decimal part will represent a fraction of a minute (of 60 seconds)
     *  i.e. 3.2 would represent 3 minutes and 12 seconds
     */
    double time_interval_mins(const MvDate& dd) const;

    //! Returns the default format string for text output
    /*! The default format string is taken from user's Preferences
     *  and thus different users can have different defaults.
     */
    static const char* StringFormat();

    //! Returns the default format string for numeric output
    /*! The default format string is taken from user's Preferences
     *  and thus different users can have different defaults.
     */
    static const char* NumberFormat();

    //! Returns the name of the month \c mm (\c mm = 1...12)
    /*! If \c full is \c true, then the full name is returned,
     *  otherwise the short name is returned.
     *  The name is taken from user's Preferences
     *  and thus different users can have diff:time_interval_dayserent defaults.
     */
    static const char* MonthName(int mm, boolean full);

    //! Returns the name of the day \c dd (\c dd = 1...7)
    /*! If \c full is \c true, then the full name is returned,
     *  otherwise the short name is returned.
     *  The name is taken from user's Preferences
     *  and thus different users can have different defaults.
     */
    static const char* DayName(int dd, boolean full);


    //! Returns a sortable number, e.g. JAN-02 -> 102, MAR-17 -> 317, DEC-03 -> 1203
    static int climDateNumber(const char* climDate);


    static bool parseYYYYMMDD(const std::string&, std::string&);
    static bool timeToHHMMSS(const std::string& tt, std::string& res);
    static bool HHMMSStoSec(const std::string& tt, int& sec);
    static bool HHMMSStoSec(int tt, int& sec);
    static bool timeToLenInSec(const std::string& tt, std::string& res);
    static bool timeToLenAsHHHMMSS(const std::string& tt, std::string& res);
};
