/***************************** 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 <ctime>
#include <iostream>
#include <sstream>
#include <vector>

#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 = nullptr;
    static MvRequest r;  // declare locally to avoid the destructor removing it

    if (p == nullptr) {
        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()
{
    request* p = pref();
    const char* s = no_quotes(get_value(p, "STRING_DATE_FORMAT", 0));
    return s ? s : stringFormat;
}

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


// dates in the format JAN-16, NOV-21, MAR-02, etc
// we just convert to a number that can be used in a sorting algorithm
int MvDate::climDateNumber(const char* climDate)
{
    int number = 0;

    if (strlen(climDate) == 6) {
        for (int i = 0; i < 12; i++) {
            if (!strncasecmp(climDate, _month[i][0], 3)) {
                number = (i + 1) * 100;
                break;
            }
        }

        if (number == 0)  // could not parse the month
            return 0;

        int day = atoi((&climDate[4]));
        number += day;
        return number;
    }

    // wrong length for a clim date
    else {
        return 0;
    }
}


void MvDate::Print() const
{
    char buf[1024];
    Format(StringFormat(), buf);
    std::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 std::string& dd, std::string& res)
{
    res.clear();
    res = dd;
    if (dd.size() < 8) {
        std::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 std::string& tt, std::string& res)
{
    res.clear();
    bool retVal = false;
    if (tt.size() < 8) {
        std::string hh, mm = "00", ss = "00";

        std::vector<std::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)
                return false;
        }
        if (tok.size() > 1) {
            mm = tok[1];
            if (mm.size() != 2)
                return false;
        }

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

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

    return retVal;
}

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

    sec = 0;
    int ival = 0;

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

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

    sec += ival * 3600;

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

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

    sec += ival * 60;

    // Second
    std::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;
    }

    // std::cout << "hh=" << hh << " mm=" << mm << " ss=" << ss << std::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 std::string& tt, std::string& res)
{
    res.clear();

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

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

    std::vector<std::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 = 0, i = 0;
        std::istringstream ish(hh);
        ish >> i;
        sec = i * 3600;
        std::istringstream ism(mm);
        ism >> i;
        sec += i * 60;
        std::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 std::string& tt, std::string& res)
{
    res.clear();

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

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

    std::vector<std::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 = 0;
    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;
            default:
                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;
}

double MvDate::dayOfTheYear() const
{
    auto y = Year();
    auto first = MvDate(y * 1E4 + 101);
    return first.time_interval_days(*this);
}


//=============================================================================
// 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 {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() const
{
    return Hour() * 10000 + Minute() * 100 + Second();
}

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

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

std::string
MvDate::ISO8601() const
{
    static char buf[32];
    Format("yyyy-mm-ddTHH:MM:00Z", buf);
    return {buf};
}


std::string MvDate::current(const std::string& fmt)
{
    auto t = std::time(nullptr);
    std::string dt(20,0);
    dt.resize(std::strftime(&dt[0], dt.size(),
        fmt.c_str(), std::localtime(&t)));

    return dt;
}

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