#pragma once
#include <cstdio>
#include <ctime>
#include <string>

namespace Util
{
    inline bool IsValidYear(int const year) { return year > 1900 && year < 9999; }

    inline bool IsValidMonth(int const month) { return month > 0 && month < 13; }

    inline bool IsValidDay(int const day) { return day > 0 && day < 32; }

    inline long GetEpoch(long year, unsigned month, unsigned day)
    {
        year -= month <= 2;
        const long era = (year >= 0 ? year : year - 399) / 400;
        const unsigned yoe = static_cast<unsigned>(year - era * 400); // [0, 399]
        const unsigned doy = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; // [0, 365]
        const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]

        // Multiply by the day length in seconds
        return (era * 146097 + doe - 719468) * (24l * 60l * 60l);
    }

    inline long ToEpoch(std::string const & date)
    {
        int year, month, day;
        if(std::sscanf(date.c_str(), "%d-%d-%d", &year, &month, &day) != 3)
        {
            return -1;
        }

        return GetEpoch(year, month, day);
    }

    inline unsigned ToSeconds(unsigned hours, unsigned minutes, unsigned seconds)
    {
        return (hours * 60 * 60) + (minutes * 60) + seconds;
    }

    inline int ToSecondsFromTimeString(char const * string)
    {
        int hour, minute, second;
        if(std::sscanf(string, "%d:%d:%d", &hour, &minute, &second) != 3)
        {
            return -1;
        }

        return ToSeconds(hour, minute, second);
    }

    inline std::string GetSqliteDate(time_t const epochTime)
    {
        auto const dateTime = *std::gmtime(&epochTime);

        char dateStringBuffer[64];
        std::sprintf(dateStringBuffer, "%i-%02i-%02i", dateTime.tm_year + 1900, dateTime.tm_mon + 1, dateTime.tm_mday);

        return std::string(dateStringBuffer);
    }

    inline std::string GetSqliteUtcTime(time_t const epochTime)
    {
        auto const dateTime = *std::gmtime(&epochTime);

        char timeStringBuffer[64];
        std::sprintf(timeStringBuffer, "%02i:%02i:%02i", dateTime.tm_hour, dateTime.tm_min, dateTime.tm_sec);

        return std::string(timeStringBuffer);
    }

    class Date {
    private:
        unsigned year;
        unsigned month;
        unsigned day;

    public:
        unsigned Year() const { return year; }
        unsigned Month() const { return month; }
        unsigned Day() const { return day; }

        void AddMonth()
        {
            ++month;
            if(month > 12)
            {
                month = 1;
                ++year;
            }
        }

        void SetDayToEndOfMonth() { day = 31; }

        bool IsValid() const { return IsValidYear(year) && IsValidMonth(month) && IsValidDay(day); }

        long ToEpoch() const { return GetEpoch(year, month, day); }

        bool IsBefore(unsigned const otherYear, unsigned const otherMonth, unsigned const otherDay) const
        {
            return (year < otherYear) || (year == otherYear && month < otherMonth)
                || (year == otherYear && month == otherMonth && day < otherDay);
        }

        bool IsBefore(Date const & other) const { return IsBefore(other.year, other.month, other.day); }

        bool IsAfter(unsigned const otherYear, unsigned const otherMonth, unsigned const otherDay) const
        {
            return (year > otherYear) || (year == otherYear && month > otherMonth)
                || (year == otherYear && month == otherMonth && day > otherDay);
        }

        std::string ToISOString() const
        {
            char buffer[11];
            std::sprintf(buffer, "%04i-%02i-%02i", year, month, day);

            return std::string(buffer);
        }

        bool TryParse(std::string const & date)
        {
            Date result;

            if(sscanf(date.c_str(), "%d-%d-%d", &year, &month, &day) != 3)
            {
                result.year = 0;
                result.month = 0;
                result.day = 0;
                return false;
            }

            return true;
        }

        Date() : year(0), month(0), day(0) { }

        Date(std::string const & date) : year(0), month(0), day(0) { TryParse(date); }

        Date(long const epoch)
        {
            std::tm dateTime = *std::gmtime(&epoch);
            year = 1900 + dateTime.tm_year;
            month = 1 + dateTime.tm_mon;
            day = dateTime.tm_mday;
        }

        static Date UtcNow() { return Date(std::time(nullptr)); }
    };
}