#include "cookie.h"
#include "expires.h"

#include <util/datetime/systime.h>
#include <util/generic/buffer.h>
#include <util/generic/xrange.h>
#include <util/string/cast.h>

#include <array>
#include <string_view>

namespace NSrvKernel {
    using namespace std::literals;

    namespace {
        constexpr std::array<TStringBuf, 7> WDays {
            "Sun"sv, "Mon"sv, "Tue"sv, "Wed"sv, "Thu"sv, "Fri"sv, "Sat"sv,
        };
        constexpr std::array<TStringBuf, 61> Dig2 {
            "00"sv, "01"sv, "02"sv, "03"sv, "04"sv, "05"sv, "06"sv, "07"sv, "08"sv, "09"sv,
            "10"sv, "11"sv, "12"sv, "13"sv, "14"sv, "15"sv, "16"sv, "17"sv, "18"sv, "19"sv,
            "20"sv, "21"sv, "22"sv, "23"sv, "24"sv, "25"sv, "26"sv, "27"sv, "28"sv, "29"sv,
            "30"sv, "31"sv, "32"sv, "33"sv, "34"sv, "35"sv, "36"sv, "37"sv, "38"sv, "39"sv,
            "40"sv, "41"sv, "42"sv, "43"sv, "44"sv, "45"sv, "46"sv, "47"sv, "48"sv, "49"sv,
            "50"sv, "51"sv, "52"sv, "53"sv, "54"sv, "55"sv, "56"sv, "57"sv, "58"sv, "59"sv,
            "60"sv,
        };
        constexpr std::array<TStringBuf, 12> Mons {
            "Jan"sv, "Feb"sv, "Mar"sv, "Apr"sv, "May"sv, "Jun"sv, "Jul"sv, "Aug"sv, "Sep"sv, "Oct"sv, "Nov"sv, "Dec"sv,
        };
        // TODO(velavokr): I really hope this crap won't live that long
        constexpr int YearMin = 2020;
        constexpr int YearSz = 40;
        constexpr std::array<TStringBuf, YearSz> Years {
            "2020"sv, "2021"sv, "2022"sv, "2023"sv, "2024"sv, "2025"sv, "2026"sv, "2027"sv, "2028"sv, "2029"sv,
            "2030"sv, "2031"sv, "2032"sv, "2033"sv, "2034"sv, "2035"sv, "2036"sv, "2037"sv, "2038"sv, "2039"sv,
            "2040"sv, "2041"sv, "2042"sv, "2043"sv, "2044"sv, "2045"sv, "2046"sv, "2047"sv, "2048"sv, "2049"sv,
            "2050"sv, "2051"sv, "2052"sv, "2053"sv, "2054"sv, "2055"sv, "2056"sv, "2057"sv, "2058"sv, "2059"sv,
        };

        inline ui32 CalcWDay(const TExpires& exp) noexcept {
            const bool leap = (exp.Year % 4 == 0 && (exp.Year % 100 != 0 || exp.Year % 400 == 0));
            constexpr std::array<std::array<ui32, 12>, 2> monCorr {
                std::array<ui32, 12> {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5},
                std::array<ui32, 12> {0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6},
            };
            return (
                exp.MDay +
                monCorr[leap][exp.Month - 1] +
                5 * ((exp.Year - 1) % 4) + 4 * ((exp.Year - 1) % 100) + 6 * ((exp.Year - 1) % 400)
            ) % 7;
        }
    }

    bool TExpires::IsValid() const noexcept {
        if (Year < 1970 || Year > 9999) {
            return false;
        }

        if (Month < 1 || Month > 12) {
            return false;
        }

        const bool leap = (Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0));

        constexpr std::array<std::array<ui32, 12>, 2> maxDay {
            std::array<ui32, 12>{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
            std::array<ui32, 12>{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
        };

        if (MDay < 1 || MDay > maxDay[leap][Month - 1]) {
            return false;
        }

        // RFC 2616 does not allow leap seconds
        if (Hour > 23 || Minute > 59 || Second > 59) {
            return false;
        }

        return true;
    }

    TMaybe<TExpires> TExpires::FromInstant(TInstant when) noexcept {
        struct tm tm;
        Zero(tm);
        const time_t t = when.Seconds();
        // TODO(velavokr): GmTimeR is slow as hell
        GmTimeR(&t, &tm);
        if (tm.tm_year > 8099) {
            return Nothing();
        }
        return TExpires{
            .Year = (ui32)tm.tm_year + 1900,
            .Month = (ui32)tm.tm_mon + 1,
            .MDay = (ui32)tm.tm_mday,
            .Hour = (ui32)tm.tm_hour,
            .Minute = (ui32)tm.tm_min,
            .Second = std::min<ui32>(tm.tm_sec, 59),
        };
    }

    TMaybe<TInstant> TExpires::ToInstant() const noexcept {
        if (!IsValid()) {
            return Nothing();
        }
        struct tm tm;
        Zero(tm);
        tm.tm_year = Year - 1900;
        tm.tm_mon = Month - 1;
        tm.tm_mday = MDay;
        tm.tm_hour = Hour;
        tm.tm_min = Minute;
        tm.tm_sec = Second;
        return TInstant::Seconds(TimeGM(&tm));
    }

    TBlob TExpires::Render(char dateSep, bool singleThreadedResult) const noexcept {
        // All the range checks are done there
        if (!IsValid()) {
            return {};
        }

        TBuffer attr;
        attr.Resize(PastDate.size());
        char* pos = attr.Data();
        auto appendSBuf = [&](TStringBuf token) {
            memcpy(pos, token.data(), token.size());
            pos += token.size();
        };
        auto appendChar = [&](char c) {
            *pos++ = c;
        };

        const auto wday = CalcWDay(*this);
        appendSBuf(WDays[wday]);
        appendSBuf(TStringBuf(", "));
        appendSBuf(Dig2[MDay]);
        appendChar(dateSep);
        appendSBuf(Mons[Month - 1]);
        appendChar(dateSep);
        if (Year >= YearMin && Year - YearMin < YearSz) {
            appendSBuf(Years[Year - YearMin]);
        } else {
            char buf[4] = {};
            const auto len = IntToString<10>(Year, buf, sizeof(buf));
            appendSBuf({buf, len});
        }
        appendChar(' ');
        appendSBuf(Dig2[Hour]);
        appendChar(':');
        appendSBuf(Dig2[Minute]);
        appendChar(':');
        appendSBuf(Dig2[Second]);
        appendSBuf(TStringBuf(" GMT"));
        Y_VERIFY(pos == attr.data() + attr.size());
        return singleThreadedResult ? TBlob::FromBufferSingleThreaded(attr) : TBlob::FromBuffer(attr);
    }
}

template <>
void Out<NSrvKernel::TExpires>(IOutputStream& out, const NSrvKernel::TExpires& c) {
    out << NSrvKernel::NCookie::ToStringBuf(c.Render());
}

template <>
void Out<NSrvKernel::TMaybeExpires>(IOutputStream& out, const NSrvKernel::TMaybeExpires& c) {
    out << c.Maybe();
}
