/***************************** 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 *************************************/

#include "inc_iostream.h"
#include "MvApplication.h"
#include "MvDate.h"

#include "Tokenizer.h"

const char* MvDate::stringFormat = "yyyy-mm-dd HH:MM:SS";
const char* MvDate::numberFormat = "yyyymmdd";

const double cSecEpsilon = 0.000001157;  //-- 0.1 sec

const char* _month[12][2] = {
    {
        "Jan",
        "January",
    },
    {
        "Feb",
        "February",
    },
    {
        "Mar",
        "March",
    },
    {
        "Apr",
        "April",
    },
    {
        "May",
        "May",
    },
    {
        "Jun",
        "June",
    },
    {
        "Jul",
        "July",
    },
    {
        "Aug",
        "August",
    },
    {
        "Sep",
        "September",
    },
    {
        "Oct",
        "October",
    },
    {
        "Nov",
        "November",
    },
    {
        "Dec",
        "December",
    },
};

const char* _day[7][2] = {
    {
        "Mon",
        "Monday",
    },
    {
        "Tue",
        "Tuesday",
    },
    {
        "Wed",
        "Wednesday",
    },
    {
        "Thu",
        "Thursday",
    },
    {
        "Fri",
        "Friday",
    },
    {
        "Sat",
        "Saturday",
    },
    {
        "Sun",
        "Sunday",
    },
};


//============================================================================

int MvDate::Julian() const
{
    MvDate jan1(Year() * 10000 + 100 + 1);
    return (int)(*this - jan1 + 1);
}

static request* pref()
{
    static request* p = 0;
    static MvRequest r;  // declare locally to avoid the destructor removing it

    if (p == 0) {
        r = MvApplication::getPreferences();
        p = r;
    }
    return p;
}

const char* MvDate::MonthName(int n, boolean full)
{
    request* p    = pref();
    const char* s = get_value(p,
                              full ? "MONTH_LONG_NAMES" : "MONTH_SHORT_NAMES", n - 1);
    return s ? s : _month[n - 1][int(full)];
}

const char* MvDate::DayName(int n, boolean full)
{
    request* p    = pref();
    const char* s = get_value(p, full ? "DAY_LONG_NAMES" : "DAY_SHORT_NAMES", n - 1);
    return s ? s : _day[n - 1][int(full)];
}

const char* MvDate::StringFormat(void)
{
    request* p    = pref();
    const char* s = no_quotes(get_value(p, "STRING_DATE_FORMAT", 0));
    return s ? s : stringFormat;
}

const char* MvDate::NumberFormat(void)
{
    request* p    = pref();
    const char* s = no_quotes(get_value(p, "NUMBER_DATE_FORMAT", 0));
    return s ? s : numberFormat;
}

void MvDate::Print() const
{
    char buf[1024];
    Format(StringFormat(), buf);
    cout << buf;
}


MvDate::MvDate(double n)
{
    julian   = (long)n;
    double s = n - (double)julian;

    if (s < 0.0) {  //-- 'n' is a negative decimal value; decimal part thus
        //-- protrudes into the previous julian day; also we need
        //-- to "reverse" decimal part, e.g.: -1.25 => -2 + 0.75
        julian--;
        second = (long)((1.0 - (-s) + cSecEpsilon) * 86400.0);
    }
    else {  //-- 'n' is an integer, or a positive decimal value
        second = (long)((n - julian + cSecEpsilon) * 86400.0);
    }

    julian = mars_date_to_julian(julian);

    while (second < 0) {
        second += 86400;
        julian++;
    }
    while (second > 86399) {
        second -= 86400;
        julian--;
    }
}

MvDate::MvDate(const char* s) :
    second(0)
{
    if (isdate(s)) {
        boolean dum;
        parsedate(s, &julian, &second, &dum);
    }
    else {
        julian = atol(s);
        julian = mars_date_to_julian(julian);
    }
}

bool MvDate::parseYYYYMMDD(const string& dd, string& res)
{
    res.clear();
    res = dd;
    if (dd.size() < 8) {
        istringstream iss(dd);
        double d;
        iss >> d;
        char buf[9];
        MvDate md(mars_julian_to_date(today() + d, 1));
        md.Format("yyyymmdd", buf);
        res = std::string(buf);
    }
    return (res.size() == 8) ? true : false;
}

//Convert hh:mm:ss format to hhmmss
bool MvDate::timeToHHMMSS(const string& tt, string& res)
{
    res.clear();
    bool retVal = false;
    if (tt.size() < 8) {
        retVal = true;
        string hh, mm = "00", ss = "00";

        std::vector<string> tok;
        Tokenizer parse(":");
        parse(tt, tok);

        if (tok.size() > 0) {
            if (tok[0].size() == 1)
                hh = "0" + tok[0];
            else
                hh = tok[0];

            if (hh.size() != 2)
                retVal = false;
        }
        if (tok.size() > 1) {
            mm = tok[1];
            if (mm.size() != 2)
                retVal = false;
        }

        if (tok.size() > 2) {
            ss = tok[2];
            if (ss.size() != 2)
                retVal = false;
        }

        res    = hh + mm + ss;
        retVal = (res.size() == 6) ? true : false;
    }

    return retVal;
}

//Convert hhmmss format to seconds
bool MvDate::HHMMSStoSec(const string& tt, int& sec)
{
    if (tt.size() != 6)
        return false;

    sec = 0;
    int ival;

    //Hour
    istringstream hh(tt.substr(0, 1));
    hh >> ival;

    if (ival < 0 || ival > 23)
        return false;

    sec += ival * 3600;

    //Minute
    istringstream mm(tt.substr(2, 3));
    mm >> ival;

    if (ival < 0 || ival > 60)
        return false;

    sec += ival * 60;

    //Second
    istringstream ss(tt.substr(4, 5));
    ss >> ival;

    if (ival < 0 || ival > 60)
        return false;

    sec += ival;

    return true;
}

//Convert hhmmss format to seconds
bool MvDate::HHMMSStoSec(int tt, int& sec)
{
    if (tt < 0 || tt > 235959)
        return false;

    sec    = 0;
    int hh = 0, mm = 0, ss = 0;

    if (tt > 5959) {
        hh = tt / 10000;
        mm = (tt - hh * 10000) / 100;
        ss = tt - hh * 10000 - mm * 100;
    }
    else if (tt > 59) {
        mm = tt / 100;
        ss = tt - mm * 100;
    }
    else {
        ss = tt;
    }

    //cout << "hh=" << hh << " mm=" << mm << " ss=" << ss << endl;

    if (hh < 0 || hh > 23)
        return false;

    if (mm < 0 || mm > 59)
        return false;

    if (ss < 0 || ss > 59)
        return false;

    sec = hh * 3600 + mm * 60 + ss;

    if (sec < 0 || sec >= 86400)
        return false;

    return true;
}

//Convert hhh:mm:ss format to seconds
bool MvDate::timeToLenInSec(const string& tt, string& res)
{
    res.clear();

    if (tt.size() > 9)
        return false;

    string hh, mm = "00", ss = "00";
    bool retVal = true;

    std::vector<string> tok;
    Tokenizer parse(":");
    parse(tt, tok);

    if (tok.size() > 0) {
        hh = tok[0];
        if (hh.size() == 0 || hh.size() > 4)
            retVal = false;
    }
    if (tok.size() > 1) {
        mm = tok[1];
        if (mm.size() != 2)
            retVal = false;
    }

    if (tok.size() > 2) {
        ss = tok[2];
        if (ss.size() != 2)
            retVal = false;
    }

    if (retVal) {
        int sec, i;
        istringstream ish(hh);
        ish >> i;
        sec = i * 3600;
        istringstream ism(mm);
        ism >> i;
        sec += i * 60;
        istringstream iss(ss);
        iss >> i;
        sec += i;

        std::stringstream sst;
        sst << sec;
        res    = sst.str();
        retVal = true;
    }

    return retVal;
}

//Convert hhh:mm:ss format to hhhmmss format
bool MvDate::timeToLenAsHHHMMSS(const string& tt, string& res)
{
    res.clear();

    if (tt.size() > 9)
        return false;

    string hh, mm = "00", ss = "00";
    bool retVal = true;

    std::vector<string> tok;
    Tokenizer parse(":");
    parse(tt, tok);

    if (tok.size() > 0) {
        hh = tok[0];
        if (hh.size() == 0 || hh.size() > 4)
            retVal = false;
        else {
            if (hh.size() == 1)
                hh = "00" + hh;
            if (hh.size() == 2)
                hh = "0" + hh;
        }
    }
    if (tok.size() > 1) {
        mm = tok[1];
        if (mm.size() != 2)
            retVal = false;
    }

    if (tok.size() > 2) {
        ss = tok[2];
        if (ss.size() != 2)
            retVal = false;
    }

    if (retVal) {
        res    = hh + mm + ss;
        retVal = (res.size() == 7);
    }

    return retVal;
}


MvDate& MvDate::operator+=(double n)
{
    long j = (long)n;
    long s = (long)((n - j) * 86400.0);

    julian = julian + j;
    second = second + s;

    while (second < 0) {
        second += 86400;
        julian--;
    }
    while (second > 86399) {
        second -= 86400;
        julian++;
    }

    return *this;
}

MvDate& MvDate::operator-=(double n)
{
    return operator+=(-n);
}

MvDate MvDate::operator+(double n) const
{
    MvDate date;
    double secEps = n < 0.0 ? -cSecEpsilon : cSecEpsilon;

    long j = (long)n;
    long s = (long)(((n - (double)j) + secEps) * 86400.0);

    date.julian = julian + j;
    date.second = second + s;

    while (date.second < 0) {
        date.second += 86400;
        date.julian--;
    }
    while (date.second > 86399) {
        date.second -= 86400;
        date.julian++;
    }

    return date;
}

MvDate MvDate::operator-(double n) const
{
    double m = -n;
    return operator+(m);
}

double MvDate::operator-(const MvDate& d) const
{
    return (double)(julian - d.julian) + (double)(second - d.second) / 86400.0;
}

double MvDate::YyyyMmDd_r() const
{
    double d1 = (((double)Hour() * 60. + (double)Minute()) * 60.) / 86400.;

    return (double)YyyyMmDd() + d1;
}

//=============================================================================

static int collect(const char*& f, char* buf, int& i, char& c)
{
    int n;
    c = 0;
    while (*f)
        switch (*f) {
            case 'y':
            case 'm':
            case 'd':
            case 'D':
            case 'H':
            case 'M':
            case 'S':
                c = *f;
                n = 1;
                while (*++f == c)
                    n++;
                return n;
                // break;

            default:
                buf[i++] = *f++;
                break;
        }
    return -1;
}

static void copy(char* buf, int& i, int n, char c)
{
    for (int j = 0; j < n; j++)
        buf[i++] = c;
}

static void copy(char* buf, int& i, const char* f, int n)
{
    char tmp[20];
    char* p = tmp;

    sprintf(tmp, f, n);

    while (*p)
        buf[i++] = *p++;
}

static void copy(char* buf, int& i, const char* p)
{
    while (*p)
        buf[i++] = *p++;
}

void MvDate::Format(const char* f, char* buf) const
{
    int i  = 0;
    char c = 0;

    for (;;) {
        int n = collect(f, buf, i, c);
        switch (c) {
            case 'y':
                switch (n) {
                    case 2:
                        copy(buf, i, "%02d", Year() % 100);
                        break;

                    case 4:
                        copy(buf, i, "%04d", Year());
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'm':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Month());
                        break;

                    case 2:
                        copy(buf, i, "%02d", Month());
                        break;

                    case 3:
                        copy(buf, i, MonthName(Month(), false));
                        break;

                    case 4:
                        copy(buf, i, MonthName(Month(), true));
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'd':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Day());
                        break;

                    case 2:
                        copy(buf, i, "%02d", Day());
                        break;

                    case 3:
                        copy(buf, i, DayName(DayOfWeek(), false));
                        break;

                    case 4:
                        copy(buf, i, DayName(DayOfWeek(), true));
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'D':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Julian());
                        break;

                    case 3:
                        copy(buf, i, "%03d", Julian());
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'H':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Hour());
                        break;

                    case 2:
                        copy(buf, i, "%02d", Hour());
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'M':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Minute());
                        break;

                    case 2:
                        copy(buf, i, "%02d", Minute());
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;

            case 'S':
                switch (n) {
                    case 1:
                        copy(buf, i, "%d", Second());
                        break;

                    case 2:
                        copy(buf, i, "%02d", Second());
                        break;

                    default:
                        copy(buf, i, n, c);
                        break;
                }
                break;
        }

        if (n < 0)
            break;
    }

    buf[i] = 0;
}

double MvDate::time_interval_days(const MvDate& d) const
{
    return (double)(d.julian - julian) + (double)(d.second - second) / 86400.0;
}

double MvDate::time_interval_hours(const MvDate& d) const
{
    return time_interval_days(d) * 24.0;
}

double MvDate::time_interval_mins(const MvDate& d) const
{
    return time_interval_hours(d) * 60.0;
}


//=============================================================================
// Magics style dates.

int MvDate::daysInMonth() const
{
    MvDate d1(Year() * 10000 + Month() * 100 + 1);
    MvDate d2 = d1 + 31;
    MvDate d3(d2.Year() * 10000 + d2.Month() * 100 + 1);
    return (int)(d3 - d1);
}

MvDate MvDate::magicsAdd(long n) const
{
    int yyyy = Year();
    int mm   = Month() + n / 10000;
    int dd   = Day() + (n / 100) % 100;
    int MM   = Minute();
    int SS   = Second();
    int HH   = Hour() + n % 100;

    return MvDate(yyyy * 10000 + mm * 100 + dd + (HH * 3600 + MM * 60 + SS) / 86400.0);
}

//-- It seems that the old code of 'magicsSub' has some bugs;
//-- to get the fixed code, set MvMAGSUBFIX to 1
#define MvMAGSUBFIX 1
//-- If the fix turns out to be wrong, then
//-- to get the old code, set MvMAGSUBFIX to 0

long MvDate::magicsSub(const MvDate& d) const
{
    MvDate d1 = *this;
    MvDate d2 = d;
    double x  = d1 - d2;

    if (x < 0) {
        x        = -x;
        MvDate d = d1;
        d1       = d2;
        d2       = d;
    }

    int dd = int(x);
#if MvMAGSUBFIX
    int HH = int((x - double(dd)) * 24.0);
#else
    int HH = int(x * 24) % 100;
#endif
    int mm = 0;

#if MvMAGSUBFIX
    while (dd > d2.daysInMonth())
#else
    while (dd >= 100)
#endif
    {
        int z = d2.daysInMonth();
        dd -= z;
        d2 += z;
        mm++;
    }

    return mm * 10000 + dd * 100 + HH;
}

int MvDate::hhmmss(void) const
{
    return Hour() * 10000 + Minute() * 100 + Second();
}

int MvDate::yyyyddd(void) const
{
    return Year() * 1000 + Julian();
}

string
MvDate::magicsDate() const
{
    static char buf[32];
    Format("yyyy-mm-dd HH:MM", buf);
    return string(buf);
}

ostream& operator<<(ostream& aStream, const MvDate& d)
{
    char buf[512];
    d.Format(d.StringFormat(), buf);
    aStream << buf;
    return aStream;
}
