#include <panda/lib.h>
#include <panda/date/Date.h>
#include <panda/date/DateRel.h>

using panda::lib::itoa;

#define TOSTR_START(maxlen)\
    dcheck();\
    size_t i;\
    static char ret[maxlen+1];\
    char* ptr = ret;\
    char* buf;\
    size_t len;
    
#define TOSTR_DEL(char) *(ptr++) = char;

#define TOSTR_VAL2(val)\
    buf = itoa(val);\
    len = strlen(buf);\
    if ((val) < 10) *(ptr++) = '0';\
    for (i = 0; i < len; i++) *(ptr++) = *(buf++);

#define TOSTR_YEAR\
    buf = itoa(_date.year);\
    len = strlen(buf);\
    if (_date.year >= 0 && _date.year <= 999) for (i = 0; i < 4 - len; i++) *(ptr++) = '0';\
    for (i = 0; i < len; i++) *(ptr++) = *(buf++);
    
#define TOSTR_MONTH TOSTR_VAL2(_date.mon+1)
#define TOSTR_DAY   TOSTR_VAL2(_date.mday)
#define TOSTR_HOUR  TOSTR_VAL2(_date.hour)
#define TOSTR_MIN   TOSTR_VAL2(_date.min)
#define TOSTR_SEC   TOSTR_VAL2(_date.sec)

#define TOSTR_AMPM\
    *(ptr++) = _date.hour < 12 ? 'A' : 'P';\
    *(ptr++) = 'M';

#define TOSTR_END\
    *(ptr++) = 0;\
    return ret;

namespace panda { namespace date {

void Date::esync () const { // w/o date normalization
    _has_epochMUT = true;
    _epochMUT = timeanyl(&_dateMUT, _zone);
}

void Date::dsync () const {
    _normalizedMUT = true;
    if (_has_epoch) { // no date -> calculate from epoch
        _has_dateMUT = true;
        anytime(_epoch, &_dateMUT, _zone);
    } else { // no epoch -> normalize from date (set epoch as a side effect as well)
        _has_epochMUT = true;
        _epochMUT = timeany(&_dateMUT, _zone);
    }
}

err_t Date::validate_range () {
    datetime old = _date;
    dsync();
    
    if (old.sec != _date.sec || old.min != _date.min || old.hour != _date.hour || old.mday != _date.mday ||
        old.mon != _date.mon || old.year != _date.year) {
        _error = E_RANGE;
        return E_RANGE;
    }
    
    return E_OK;
}

int Date::compare (const Date& operand) const {
    if (_has_epoch && operand._has_epoch) return epoch_cmp(_epoch, operand._epoch);
    else if (_zone != operand._zone) return epoch_cmp(epoch(), operand.epoch());
    else return date_cmp(date(), operand.date());
}

void Date::operator+= (const DateRel& operand) {
    if (operand.year() | operand.month() | operand.day()) {
        dcheck();
        _date.mday += operand.day();
        _date.mon  += operand.month();
        _date.year += operand.year();
        dchg_auto();
    }
    echeck();
    _epoch += operand.sec() + operand.min()*60 + operand.hour()*3600;
    echg();
}

void Date::operator-= (const DateRel& operand) {
    if (operand.year() | operand.month() | operand.day()) {
        dcheck();
        _date.mday -= operand.day();
        _date.mon  -= operand.month();
        _date.year -= operand.year();
        dchg_auto();
    }
    echeck();
    _epoch -= operand.sec() + operand.min()*60 + operand.hour()*3600;
    echg();
}

char* Date::strftime (const char* format, char* buf, size_t maxsize) const {
    dcheck();
    static char defbuf[1000];
    if (buf == NULL) {
        buf = defbuf;
        maxsize = 1000;
    }
    size_t reslen = panda::time::strftime(buf, maxsize, format, &_date);
    return reslen > 0 ? buf : NULL;
}

const char* Date::iso () const {
    TOSTR_START(50);
    TOSTR_YEAR; TOSTR_DEL('-'); TOSTR_MONTH; TOSTR_DEL('-'); TOSTR_DAY; TOSTR_DEL(' ');
    TOSTR_HOUR; TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(':'); TOSTR_SEC;
    TOSTR_END;
}

const char* Date::mysql () const {
    TOSTR_START(45);
    TOSTR_YEAR; TOSTR_MONTH; TOSTR_DAY; TOSTR_HOUR; TOSTR_MIN; TOSTR_SEC;
    TOSTR_END;
}

const char* Date::hms () const {
    TOSTR_START(8); TOSTR_HOUR; TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(':'); TOSTR_SEC; TOSTR_END;
}

const char* Date::ymd () const {
    TOSTR_START(41); TOSTR_YEAR; TOSTR_DEL('/'); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_DAY; TOSTR_END;
}

const char* Date::mdy () const {
    TOSTR_START(41); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_DAY; TOSTR_DEL('/'); TOSTR_YEAR; TOSTR_END;
}

const char* Date::dmy () const {
    TOSTR_START(41); TOSTR_DAY; TOSTR_DEL('/'); TOSTR_MONTH; TOSTR_DEL('/'); TOSTR_YEAR; TOSTR_END;
}

const char* Date::meridiam () const {
    TOSTR_START(8);
    int hour = _date.hour % 12;
    if (hour == 0) hour = 12;
    TOSTR_VAL2(hour); TOSTR_DEL(':'); TOSTR_MIN; TOSTR_DEL(' '); TOSTR_AMPM;
    TOSTR_END;
}

const char* Date::ampm () const {
    dcheck();
    return _date.hour < 12 ? "AM" : "PM";
}

const char* Date::errstr () const {
    switch (_error) {
        case E_OK:
            return NULL;
        case E_UNPARSABLE:
            return "can't parse date string";
        case E_RANGE:
            return "input date is out of range";
        default:
            return "unknown error";
    }
}

/////////   STATIC   ///////////////////////////////
char Date::_strfmt[] = "";
bool Date::_range_check = false;

const char* Date::string_format () {
    if (_strfmt[0] == '\0') return NULL;
    return _strfmt;
}

void Date::string_format (const char* fmt) {
    if (fmt == NULL) _strfmt[0] = '\0';
    else strncpy(_strfmt, fmt, MAX_FMT);
}

}}
