#include "datetime.h"

#include <util/str_stl.h>
#include <util/ysaveload.h>

#include <util/system/atomic.h>
#include <util/system/fasttime.h>
#include <util/datetime/base.h>
#include <util/datetime/systime.h>
#include <util/stream/output.h>
#include <util/stream/mem.h>
#include <util/string/cast.h>
#include <util/string/printf.h>

namespace NDatetime {
    const ui32 MonthDays[2][12] = {
        {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, //nleap
        {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}  //leap
    };

    const ui32 MonthDaysNewYear[2][13] = {
        {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, //nleap
        {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}  //leap
    };

    void YDayToMonthAndDay(ui32 yday, bool isleap, ui32* month, ui32* mday) {
        const ui32* begin = MonthDaysNewYear[isleap] + 1;
        const ui32* end = begin + 12;
        // [31, ..., 365] or [31, ..., 366] (12 elements)

        const ui32* pos = UpperBound(begin, end, yday);
        Y_ENSURE(pos != end, "day no. " << yday << " does not exist in " << (isleap ? "leap" : "non-leap") << " year");

        *month = pos - begin;
        *mday = yday - *(pos - 1) + 1;

        Y_ASSERT((*month < 12) && (1 <= *mday) && (*mday <= MonthDays[isleap][*month]));
    }

    struct TTimeData {
        i32 IsDst = 0;
        i32 GMTOff = 0;

        TTimeData(time_t t) {
            struct ::tm tt;
            ::localtime_r(&t, &tt);
#ifndef _win_
            GMTOff = tt.tm_gmtoff;
#else
            TIME_ZONE_INFORMATION tz;
            switch (GetTimeZoneInformation(&tz)) {
                case TIME_ZONE_ID_UNKNOWN:
                    GMTOff = tz.Bias * -60;
                    break;
                case TIME_ZONE_ID_STANDARD:
                    GMTOff = (tz.Bias + tz.StandardBias) * -60;
                    break;
                case TIME_ZONE_ID_DAYLIGHT:
                    GMTOff = (tz.Bias + tz.DaylightBias) * -60;
                    break;
                default:
                    break;
            }
#endif
            IsDst = tt.tm_isdst;
        }
    };

    TSimpleTM TSimpleTM::CurrentUTC() {
        return New((time_t)TInstant::MicroSeconds(InterpolatedMicroSeconds()).Seconds());
    }

    TSimpleTM TSimpleTM::New(time_t t, i32 gmtoff, i8 isdst) {
        time_t tt = t + gmtoff + isdst * 3600;
        struct tm tmSys;
        Zero(tmSys);
        GmTimeR(&tt, &tmSys);
        tmSys.tm_isdst = isdst;
#ifndef _win_
        tmSys.tm_gmtoff = gmtoff;
#endif

        return New(tmSys);
    }

    TSimpleTM TSimpleTM::NewLocal(time_t t) {
        TTimeData d(t);
        return New(t, d.GMTOff, d.IsDst);
    }

    TSimpleTM TSimpleTM::New(const struct tm& t) {
        TSimpleTM res;
        res.IsDst = t.tm_isdst;
        res.Sec = t.tm_sec;
        res.Min = t.tm_min;
        res.Hour = t.tm_hour;
        res.WDay = t.tm_wday;
        res.Mon = t.tm_mon;
        res.MDay = t.tm_mday;
        res.Year = t.tm_year;
        res.YDay = t.tm_yday;
        res.IsLeap = LeapYearAD(res.Year + 1900);
#ifndef _win_
        res.GMTOff = t.tm_gmtoff;
#endif
        return res;
    }

    TSimpleTM& TSimpleTM::SetRealDate(ui32 year, ui32 mon, ui32 mday, ui32 hour, ui32 min, ui32 sec, i32 isdst) {
        mday = ::Max<ui32>(mday, 1);
        mon = ::Min<ui32>(::Max<ui32>(mon, 1), 12);
        year = ::Max<ui32>(year, 1900);

        IsLeap = LeapYearAD(year);
        Year = year - 1900;
        Mon = mon - 1;
        MDay = ::Min<ui32>(mday, MonthDays[IsLeap][Mon]);
        Hour = Max<ui32>() == hour ? Hour : ::Min<ui32>(hour, 23);
        Min = Max<ui32>() == min ? Min : ::Min<ui32>(min, 59);
        Sec = Max<ui32>() == sec ? Sec : ::Min<ui32>(sec, 60);
        IsDst = isdst;

        return RegenerateFields();
    }

    TSimpleTM& TSimpleTM::RegenerateFields() {
        return *this = New(AsTimeT(), GMTOff, IsDst);
    }

    TSimpleTM& TSimpleTM::Add(EField f, i32 amount) {
        if (!amount)
            return *this;

        switch (f) {
            default:
                return *this;
            case F_DAY:
                amount *= 24;
            /* no break */
            case F_HOUR:
                amount *= 60;
            /* no break */
            case F_MIN:
                amount *= 60;
            /* no break */
            case F_SEC: {
                return *this = New(AsTimeT() + amount, GMTOff, IsDst);
            }
            case F_YEAR: {
                i32 y = amount + (i32)Year;
                y = ::Min<i32>(Max<i32>(y, 0), 255 /*max year*/);

                // YDay may correspond to different MDay if it's March or greater and the years have different leap status
                if (Mon > 1)
                    YDay += (i32)LeapYearAD(RealYear()) - (i32)LeapYearAD(RealYear());

                Year = y;
                IsLeap = LeapYearAD(RealYear());
                return RegenerateFields();
            }
            case F_MON: {
                i32 m = amount + Mon;
                i32 y = (m < 0 ? (-12 + m) : m) / 12;
                m = m - y * 12;

                if (y) {
                    Add(F_YEAR, y);
                }

                if (m >= 0 && m < 12) {
                    MDay = ::Min<ui32>(MonthDays[IsLeap][m], MDay);
                    Mon = m;
                }

                return RegenerateFields();
            }
        }
    }

    TString TSimpleTM::ToString(const char* fmt) const {
        struct tm t = *this;
        return Strftime(fmt, &t);
    }

    time_t TSimpleTM::AsTimeT() const {
        struct tm t = AsStructTmLocal();
        return TimeGM(&t) - GMTOff - IsDst * 3600;
    }

    struct tm TSimpleTM::AsStructTmUTC() const {
        struct tm res;
        Zero(res);
        time_t t = AsTimeT();
        return *GmTimeR(&t, &res);
    }

    struct tm TSimpleTM::AsStructTmLocal() const {
        struct tm t;
        Zero(t);
        t.tm_isdst = IsDst;
        t.tm_sec = Sec;
        t.tm_min = Min;
        t.tm_hour = Hour;
        t.tm_wday = WDay;
        t.tm_mon = Mon;
        t.tm_mday = MDay;
        t.tm_year = Year;
        t.tm_yday = YDay;
#ifndef _win_
        t.tm_gmtoff = GMTOff;
#endif
        return t;
    }
}

template <>
void In<TMonth>(IInputStream& in, TMonth& t) {
    char buf[4];
    LoadPodArray(&in, buf, 4);
    t.Year = FromString<ui16>(buf, 4);
    LoadPodArray(&in, buf, 2);
    t.Month = ui8(FromString<ui16>(buf, 2)) - 1;
}

template <>
void Out<TMonth>(IOutputStream& o, const TMonth& t) {
    o << t.Year << Sprintf("%.2hu", (ui16)(t.Month + 1));
}

template <>
TMonth FromStringImpl<TMonth, char>(const char* s, size_t len) {
    TMonth res;
    TMemoryInput in(s, len);
    in >> res;
    return res;
}
