#ifndef INCLUDED_BOBCAT_DATETIME_
#define INCLUDED_BOBCAT_DATETIME_

#include <ctime>
#include <iosfwd>
#include <chrono>
#include <sstream>

namespace FBB
{

    // DateTime objects define d_time as the time since the epoch
    // in seconds, and d_zone as the time zone shift in seconds.
    //
    // The time to display equals d_time + d_zone.

class DateTime
{
    friend  std::ostream &operator<<(std::ostream &str, DateTime const &dt);
    friend  std::istream &operator>>(std::istream &str, DateTime &dt);

    friend bool operator==(DateTime const &left, DateTime const &right);
    friend bool operator!=(DateTime const &left, DateTime const &right);
    friend bool operator<(DateTime const &left, DateTime const &right);
    friend bool operator<=(DateTime const &left, DateTime const &right);
    friend bool operator>(DateTime const &left, DateTime const &right);
    friend bool operator>=(DateTime const &left, DateTime const &right);

    typedef struct tm TM;

    class Pimpl;
    class ZoneData;
    class ZoneNames;
    class Parse;

    public:
        enum TimeType
        {
            LOCALTIME,
            UTC,
        };
        enum Month
        {
            JANUARY,                    Jan = JANUARY,
            FEBRUARY,                   Feb = FEBRUARY,
            MARCH,                      Mar = MARCH,
            APRIL,                      Apr = APRIL,
            MAY,                        May = MAY,
            JUNE,                       Jun = JUNE,
            JULY,                       Jul = JULY,
            AUGUST,                     Aug = AUGUST,
            SEPTEMBER,                  Sep = SEPTEMBER,
            OCTOBER,                    Oct = OCTOBER,
            NOVEMBER,                   Nov = NOVEMBER,
            DECEMBER,                   Dec = DECEMBER,
        };
        enum Relative
        {
            THIS_YEAR,
            LAST,
            NEXT,
            THIS_WEEK,
        };
        enum Weekday
        {
            SUNDAY,                     Sun = SUNDAY,
            MONDAY,                     Mon = MONDAY,
            TUESDAY,                    Tue = TUESDAY,
            WEDNESDAY,                  Wed = WEDNESDAY,
            THURSDAY,                   Thu = THURSDAY,
            FRIDAY,                     Fri = FRIDAY,
            SATURDAY,                   Sat = SATURDAY,
        };
        enum TriVal: int
        {
            UNKNOWN = -1,
            NO,
            YES
        };
        enum TimeFields
        {
            SECONDS  = 1 << 0,
            MINUTES  = 1 << 1,
            HOURS    = 1 << 2,
            MONTHDAY = 1 << 3,
            MONTH    = 1 << 4,
            YEAR     = 1 << 5
        };

    private:
        TimeType    d_type;     // current type of info in d_tm member
                                // (LOCALTIME (implied when using displayZone)
                                // or UTC)
        time_t      d_time;     // UTC time in seconds

        time_t      d_zone;     // correction from UTC (seconds)
        int         d_dst;      // DST shift (seconds)

        mutable TM  d_tm;       // holds the latest broken down time
                                // given d_type, d_time, d_zone,
                                // and d_dst. d_type UTC only
                                // considers d_time -> updateTM()

        bool        d_1 = true; // previous d_ok and d_error data
        size_t      d_2 = 0;    // members. Not used anymore

        static char const *s_month[];
        static char const *s_day[];

    public:
        struct DSTSpec;
                                                // time displayed as TimeType
        explicit DateTime(TimeType type = UTC);                 // 1
        DateTime(DateTime const &other);                        // 2
        DateTime(DateTime &&tmp);                               // 3
                                                // LOCAL: UTC + zoneMinutes
        explicit DateTime(int zoneMinutes);                     // 4.f
                                                // LOCAL: UTC + zoneMinutes
        DateTime(time_t zoneMinutes, DSTSpec const &spec);      // 5.f
                                                // specify UTC/LOCAL time in
        DateTime(time_t time, TimeType type);   // seconds         6.cc
                                                // LOCALTIME: time (UTC) +
                                                //   zoneMinutes (= TZ + DST)
        DateTime(time_t time, int zoneMinutes);                 // 7.f
                                                // LOCALTIME: time (UTC) +
                                                // zoneMinutes (= TZ + DST)
        DateTime(time_t time, int zoneMinutes,                  // 8
                              DSTSpec const &spec);
                                                // specify tm fields as
                                                // either UTC or LOCALTIME
                                                // using the default
                                                // zoneMinutes
        explicit DateTime(TM const &tm, TimeType type = UTC);   // 9
                                                // specify UTC tm fields
                                                // display + zoneMinutes
        DateTime(TM const &tm, int zoneMinutes);                // 10.f
                                                // specify UTC tm fields
                                                // display + zoneMinutes
        DateTime(TM const &tm, int zoneMinutes,                 // 11
                                DSTSpec const &spec);
                                                // specify UTC/LOCAL text
                                                // time
        explicit DateTime(std::string const &timeStr,           // 12
                          TimeType type = UTC);
        DateTime(std::istream &in, TimeType type);              // 13
        DateTime(std::istream &&in, TimeType type);             // 14.f
                                                // specify UTC text time
                                                // display + displayZone
        DateTime(std::string const &timeStr, int zoneMinutes);  // 15
        DateTime(std::istream  &in, int zoneMinutes);           // 16.f
        DateTime(std::istream  &&in, int zoneMinutes);          // 17.f
                                                //specify UTC text time
                                                //    display +  displayZone
        DateTime(std::string const &timeStr, int zoneMinutes,   // 18
                 DSTSpec const &spec);
        DateTime(std::istream &in, int zoneMinutes,             // 19
                 DSTSpec const &spec);
        DateTime(std::istream &&in, int zoneMinutes,            // 20.f
                 DSTSpec const &spec);

        ~DateTime();

        operator bool() const;                          // .f

        DateTime &operator=(DateTime const &other);     // 1
        DateTime &operator=(DateTime &&tmp);            // 2

        DateTime &operator+=(int  seconds);             // 1.
        DateTime &operator+=(TM const &tm);             // 2.

            template <typename Type>
        DateTime &operator+=(Type const &time);         // 3.f

        DateTime &operator-=(int  seconds);             // 1.f
        DateTime &operator-=(TM const &tm);             // 2.

            template <typename Type>
        DateTime &operator-=(Type const &time);         // 3.f

        void swap(DateTime &other);

        TriVal      dst() const;              // YES if DST active, else NO
        size_t      hours() const;
        DateTime    localTime() const;
        size_t      minutes() const;
        Month       month() const;
        size_t      monthDayNr() const;
        std::string rfc2822() const;
        std::string rfc3339() const;
        size_t      seconds() const;
        TM const   *timeStruct() const;
        DateTime    to(TimeType type) const;
        DateTime    utc() const;
        time_t      utcSeconds() const;
        Weekday     weekday() const;
        size_t      weekNr() const;
        size_t      year() const;
        size_t      yearDay() const;
        size_t      yearDayNr() const;
        int         zoneMinutes() const;      // current zone in minutes 1.f

        bool setDay(int day);
        bool setFields(TM const &ts, TimeFields fields);
        bool setHours(int hours);
        bool setMinutes(int minutes);
        bool setMonth(Month month);
        bool setMonth(Month month, Relative where);
        bool setMonth(int month);
        bool setSeconds(int seconds);
        bool setTime(time_t utcSeconds);
        void setValid();
        bool setWeekday(Weekday weekday, Relative where);
        bool setYear(int year);

        static void        addZone(std::string const &name, int zoneMinutes,
                                   DSTSpec const &spec);
                                                    // minutes of the
        static int         defaultZoneMinutes();    // computer's time zone
                                                    // minutes of the named
                                                    // time zone
        static int         minutes(std::string const &zoneName);
        static void        readZones(std::string const &fname);
        static void        tm2cout(char const *label, TM const &ts);
        static std::string zoneTxt(int zoneMinutes);


        int displayZoneShift() const;               // OBS  .f
        bool setFields(TM const &ts, int fields);   // OBS  .f

        //-- OBSOLETE, Do not use: ----------------------------------------
        DateTime(std::string const &timeStr, TimeType type, int);   // dep9
        DateTime(std::string const &timeStr, int displayZone, int); // dep10

    private:
        void iniZoneDstPimpl();

        void iniLocal(DSTSpec const &spec);
        void iniPimplTM(DSTSpec const &spec);

        void refreshTM();                       // refresh d_tm given d_type,
                                                // d_time, d_zone and d_dst
        int checkDST() const;

        void zoneOut(int zoneMinutes);

        void utc2utc();                         // d_tm utc -> utc, ret: DST
        void utc2local();
        void utc2zone(int zoneMinutes);             // d_tm utc -> zone shift

        int local2utc();
        void local2local();                     // d_tm local -> local

        void zone2utc(int zoneMinutes);             // d_tm zone -> utc
        void zone2local(int zoneMinutes);           // d_tm zone -> local
        void zone2zone(int tzIn, int tzOut);    // d_tm zone1 -> zone2 shift

        DateTime &setTMfields(TM const &fields,
                         void (*modifier)(TM &dest, TM const &src));

        DateTime &install(DateTime &tmp);

        std::ostream &timeStr(std::ostream &out) const;

        static void addFields(TM &dest, TM const &src);
        static int asSeconds(int minutes);  // .f (.ih)
                                            // shifts multiples of 30'
                                            // at most +/-12 hours away

        static void subFields(TM &dest, TM const &src);
        static void chgDay(TM &dest, TM const &src);
        static int defaultZoneSecs();               // shift-seconds for
                                                // the computer's time zone
        [[noreturn]] static void timeException();
};

class DateTime::DSTSpec
{
    friend class DateTime;

    uint8_t d_startMon;
    uint8_t d_startDay = 0;                 // 0: tm_isdst is used
    uint8_t d_endMon;
    uint8_t d_endDay;

    bool d_supportsDST;                      // this time spec. supports DST
    int d_dstSeconds;                       // explicit DST shift

    public:
        DSTSpec() = default;
        explicit DSTSpec(bool supportsDST,
                         int dstMinutes = 60);              // 1.f
        DSTSpec(int dstMinutes);                            // 2.f
        DSTSpec(std::string const &begin,
                std::string const &end, int dstMinutes = 60); // 3.cc

        int minutes() const;                        // DST time shift   2.f
        bool supportsDST() const;                           // 1.f
        int startMon() const;
        int startDay() const;
        int endMon() const;
        int endDay() const;

    private:
        static void split(uint8_t *mon, uint8_t *day, std::string const & in);
};


