#pragma once
#include <panda/date/inc.h>
#include <panda/date/parse.h>

namespace panda { namespace date {

using panda::time::tz;
using panda::time::dt;
using panda::time::days_in_month;
using panda::time::tzget;
using panda::time::tzlocal;
    
class DateRel;

class Date {

private:
    static const int MAX_FMT = 255;
    static char      _strfmt[MAX_FMT+1];
    static bool      _range_check;

    const tz* _zone;

    union {
                ptime_t _epoch;
        mutable ptime_t _epochMUT;
    };
    union {
                dt _date;
        mutable dt _dateMUT;
    };
    union {
                bool _has_epoch;
        mutable bool _has_epochMUT;
    };
    union {
                bool _has_date;
        mutable bool _has_dateMUT;
    };
    union {
                bool _normalized;
        mutable bool _normalizedMUT;
    };

    uint8_t _error;
    
    void esync () const;
    void dsync () const;
    err_t validate_range();
    
    void echeck () const { if (!_has_epoch) esync(); }
    void dcheck () const { if (!_has_date || !_normalized) dsync(); };

    void dchg () {
        _has_epoch = false;
        _normalized = false;
    }

    void dchg_auto () {
        dchg();
        _date.isdst = -1;
    }

    void echg () {
        _has_date = false;
        _normalized = false;
    }

    void _zone_set (const tz* zone) {
        if (_zone == NULL) {
            if (zone == NULL) _zone = tzlocal();
            else _zone = zone;
            _zone->retain();
        } else if (zone != NULL) {
            _zone->release();
            zone->retain();
            _zone = zone;
        }
    }

public: 

    ptime_t epoch () const      { echeck(); return _epoch; }
    void    epoch (ptime_t val) { _epoch = val; _has_epoch = true; echg(); }

    void set (ptime_t val, const tz* zone = NULL) {
        _zone_set(zone);
        epoch(val);
    }

    err_t set (const char* str, size_t len = 0, const tz* zone = NULL) {
        _zone_set(zone);
        _error    = parse_iso(str, len, &_date);
        _has_date = true;
        dchg_auto();
        if (_range_check && _error == E_OK) validate_range();
        return (err_t) _error;
    }

    err_t set (int32_t year, ptime_t month, ptime_t day, ptime_t hour, ptime_t min, ptime_t sec, int isdst= -1, const tz* zone = NULL) {
        _zone_set(zone);
        _error      = E_OK;
        _date.year  = year;
        _date.mon   = month - 1;
        _date.mday  = day;
        _date.hour  = hour;
        _date.min   = min;
        _date.sec   = sec;
        _date.isdst = isdst;
        _has_date   = true;
        dchg();
        if (_range_check) validate_range();
        return (err_t) _error;
    }

    void set (const Date* source, const tz* zone = NULL) {
        _error = source->_error;
        if (_zone != NULL) _zone->release();

        if (zone == NULL || _error) {
            _has_epoch  = source->_has_epoch;
            _has_date   = source->_has_date;
            _normalized = source->_normalized;
            _zone       = source->_zone;
            _epoch      = source->_epoch;
            if (_has_date) _date  = source->_date;
        } else {
            source->dcheck();
            _has_epoch  = false;
            _has_date   = true;
            _normalized = source->_normalized;
            _date       = source->_date;
            _zone       = zone;
        }

        _zone->retain();
    }

    Date (const Date& source) : _zone(NULL) {
        set(&source);
    }

    Date (ptime_t epoch = (ptime_t) ::time(NULL), const tz* zone = NULL) : _zone(NULL), _error(E_OK) {
        set(epoch, zone);
    }

    Date (const char* str, size_t len = 0, const tz* zone = NULL) : _zone(NULL) {
        set(str, len, zone);
    }

    Date (int32_t year, ptime_t mon, ptime_t day, ptime_t hour, ptime_t min, ptime_t sec, int isdst = -1, const tz* zone = NULL) : _zone(NULL) {
        set(year, mon, day, hour, min, sec, isdst, zone);
    }
    
    Date (const Date* source, const tz* zone = NULL) : _zone(NULL) {
        set(source, zone);
    }

    ~Date () {
        _zone->release();
    }

    Date& operator= (const Date& source) {
        if (this != &source) set(&source);
        return *this;
    }

    inline err_t change (int32_t year, ptime_t mon=-1, ptime_t day=-1, ptime_t hour=-1, ptime_t min=-1, ptime_t sec=-1, int isdst=-1, const tz* zone=NULL) {
        dcheck();
        _error = E_OK;
        if (year >= 0) _date.year = year;
        if (mon   > 0) _date.mon  = mon - 1;
        if (day   > 0) _date.mday = day;
        if (hour >= 0) _date.hour = hour;
        if (min  >= 0) _date.min  = min;
        if (sec  >= 0) _date.sec  = sec;
        _date.isdst = isdst;
        dchg();
        _zone_set(zone);
        if (_range_check) validate_range();
        return (err_t) _error;
    }

    const dt* date       () const    { dcheck(); return &_date; }
    bool      has_epoch  () const    { return _has_epoch; }
    bool      has_date   () const    { return _has_date; }
    bool      normalized () const    { return _normalized; }
    err_t     error      () const    { return (err_t) _error; }
    void      error      (err_t val) { _error = val; epoch(0); }
    const tz* timezone   () const    { return _zone; }

    void timezone (const tz* zone) {
        dcheck();
        if (zone == NULL) zone = tzlocal();
        dchg_auto();
        _zone_set(zone);
    }

    void to_timezone (const tz* zone) {
        echeck();
        if (zone == NULL) zone = tzlocal();
        echg();
        _zone_set(zone);
    }
    
    int32_t year  () const      { dcheck(); return _date.year; }
    void    year  (int32_t val) { dcheck(); _date.year = val; dchg_auto(); }
    int32_t _year () const      { return year() - 1900; }
    void    _year (int32_t val) { year(val + 1900); }
    int8_t  yr    () const      { return year() % 100; }
    void    yr    (int val)     { year( year() - yr() + val ); }

    uint8_t month  () const      { dcheck(); return _date.mon + 1; }
    void    month  (ptime_t val) { dcheck(); _date.mon = val - 1; dchg_auto(); }
    uint8_t _month () const      { return month() - 1; }
    void    _month (ptime_t val) { month(val + 1); }

    uint8_t mday () const      { dcheck(); return _date.mday; }
    void    mday (ptime_t val) { dcheck(); _date.mday = val; dchg_auto(); }
    uint8_t day  () const      { return mday(); }
    void    day  (ptime_t val) { mday(val); }

    uint8_t hour () const      { dcheck(); return _date.hour; }
    void    hour (ptime_t val) { dcheck(); _date.hour = val; dchg_auto(); }

    uint8_t min () const      { dcheck(); return _date.min; }
    void    min (ptime_t val) { dcheck(); _date.min = val; dchg_auto(); }

    uint8_t sec () const      { dcheck(); return _date.sec; }
    void    sec (ptime_t val) { dcheck(); _date.sec = val; dchg_auto(); }

    uint8_t wday () const       { dcheck(); return _date.wday + 1; }
    void    wday (ptime_t val)  { dcheck(); _date.mday += val - (_date.wday + 1); dchg_auto(); }
    uint8_t _wday () const      { return wday() - 1; }
    void    _wday (ptime_t val) { wday(val + 1); }
    uint8_t ewday () const      { dcheck(); return _date.wday == 0 ? 7 : _date.wday; }
    void    ewday (ptime_t val) { _date.mday += val - ewday(); dchg_auto(); }

    uint16_t yday  () const      { dcheck(); return _date.yday + 1; }
    void     yday  (ptime_t val) { dcheck(); _date.mday += val - 1 - _date.yday; dchg_auto(); }
    uint16_t _yday () const      { return yday() - 1; }
    void     _yday (ptime_t val) { yday(val + 1); }

    bool        isdst  () const { dcheck(); return _date.isdst > 0 ? true : false; }
    int32_t     gmtoff () const { dcheck(); return _date.gmtoff; }
    const char* tzabbr () const { dcheck(); return _date.zone; }

    int days_in_month () const { dcheck(); return panda::time::days_in_month(_date.year, _date.mon); }

    Date* month_begin     () { mday(1); return this; }
    Date* month_end       () { mday(days_in_month()); return this; }
    Date* month_begin_new () { Date* ret = clone(); ret->mday(1); return ret; }
    Date* month_end_new   () { Date* ret = clone(); ret->mday(days_in_month()); return ret; }
    
    Date* truncate () {
        dcheck();
        _date.sec  = 0;
        _date.min  = 0;
        _date.hour = 0;
        dchg_auto();
        return this;
    }
    Date* truncate_new () { return clone()->truncate(); }

    Date* clone (const tz* zone = NULL) const {
        return new Date(this, zone);
    }

    Date* clone (int32_t year, ptime_t mon=-1, ptime_t day=-1, ptime_t hour=-1, ptime_t min=-1, ptime_t sec=-1, int isdst=-1, const tz* zone=NULL) const {
        Date* ret = clone();
        ret->change(year, mon, day, hour, min, sec, isdst, zone);
        return ret;
    }

    char*       strftime (const char*, char*, size_t) const;
    const char* errstr   () const;

    const char* toString () const {
        if (_error) return NULL;
        return _strfmt[0] == '\0' ? iso() : this->strftime(_strfmt, NULL, 0);
    }

    int compare (const Date&) const;
    int compare (const Date* arg) const { return compare(*arg); };

    bool operator== (const Date& operand) const { return compare(operand) == 0; }
    bool operator<  (const Date& operand) const { return compare(operand) == -1; }
    bool operator<= (const Date& operand) const { return compare(operand) != 1; }
    bool operator>  (const Date& operand) const { return compare(operand) == 1; }
    bool operator>= (const Date& operand) const { return compare(operand) != -1; }

    void operator+= (const DateRel&);
    void operator-= (const DateRel&);

    Date operator+ (const DateRel& operand) const {
        Date ret(*this);
        ret += operand;
        return ret;
    }

    Date operator- (const DateRel& operand) const {
        Date ret(*this);
        ret -= operand;
        return ret;
    }
    
    bool equals (const Date* operand) const { return operator==(*operand); }
    bool lt     (const Date* operand) const { return operator<(*operand); }
    bool lte    (const Date* operand) const { return operator<=(*operand); }
    bool gt     (const Date* operand) const { return operator>(*operand); }
    bool gte    (const Date* operand) const { return operator>=(*operand); }

    Date* add      (const DateRel* operand) { operator+=(*operand); return this; }
    Date* subtract (const DateRel* operand) { operator-=(*operand); return this; }

    Date* add_new      (const DateRel* operand) { return clone()->add(operand); }
    Date* subtract_new (const DateRel* operand) { return clone()->subtract(operand); }

    const char* iso      () const;
    const char* mysql    () const;
    const char* hms      () const;
    const char* ymd      () const;
    const char* mdy      () const;
    const char* dmy      () const;
    const char* meridiam () const;
    const char* ampm     () const;

    static Date* now   () { return new Date(); }
    static Date* today () {
        Date* ret = now();
        ret->truncate();
        return ret;
    }

    static bool range_check ()         { return _range_check; }
    static void range_check (bool val) { _range_check = val; }

    static void string_format (const char*);
    static const char* string_format ();
};

}}
