#pragma once

#include <util/generic/deque.h>
#include <util/draft/date.h>

namespace NWebmaster {

struct TMonthRangeConfig {
    static TMonthRangeConfig Today() {
        return Next(TDate::Today());
    }

    static TMonthRangeConfig Next(const TDate &date) {
        TMonthRangeConfig obj;
        obj.RangeStart = TDate(date.GetYear(), date.GetMonth(), 1);
        obj.RangeEnd = GetNextRangeDate(obj.RangeStart);
        obj.Current = obj.In(TDate::Today());
        return obj;
    }

    static TMonthRangeConfig Next(const TMonthRangeConfig &rc) {
        return Next(rc.RangeEnd);
    }

    static TMonthRangeConfig Prev(const TDate &date) {
        TMonthRangeConfig obj;
        obj.RangeEnd = TDate(date.GetYear(), date.GetMonth(), 1);
        obj.RangeStart = GetPrevRangeDate(obj.RangeEnd);
        obj.Current = obj.In(TDate::Today());
        return obj;
    }

    static TMonthRangeConfig Prev(const TMonthRangeConfig &rc) {
        return Prev(rc.RangeStart);
    }

    static TDate GetNextRangeDate(const TDate &date) {
        unsigned month = date.GetMonth();
        unsigned year = date.GetYear();
        month++;
        if (month == 13) {
            month = 1;
            year++;
        }
        return TDate(year, month, 1);
    }

    static TDate GetPrevRangeDate(const TDate &date) {
        unsigned month = date.GetMonth();
        unsigned year = date.GetYear();
        month--;
        if (month == 0) {
            month = 12;
            year--;
        }
        return TDate(year, month, 1);
    }

    void GetDays(TDeque<TDate> &days) const {
        TDate date = RangeStart;
        while (date < RangeEnd) {
            days.push_back(date);
            date += 1;
        }
    }

    unsigned GetDaysCount() const {
        return (RangeEnd.GetStart() - RangeStart.GetStart()) / SECONDS_IN_DAY;
    }

    bool In(const TDate &date) const {
        return date >= RangeStart && date < RangeEnd;
    }

    bool operator<(const TMonthRangeConfig &rhs) const {
        return RangeStart < rhs.RangeStart;
    }

    TString Name() const {
        return RangeStart.ToStroka() + "_" + RangeEnd.ToStroka();
    }

    friend IOutputStream& operator<<(IOutputStream& left, const TMonthRangeConfig& right);

public:
    TDate RangeStart;
    TDate RangeEnd;
    bool Current = false;
};

struct TWeekRangeConfig {
    static TWeekRangeConfig Today() {
        return Next(TDate::Today());
    }

    static TWeekRangeConfig Next(const TDate &date) {
        TWeekRangeConfig obj;
        obj.RangeStart = GetStartOfRange(date);
        obj.RangeEnd = GetNextRangeDate(obj.RangeStart);
        obj.Current = obj.In(TDate::Today());
        return obj;
    }

    static TWeekRangeConfig Next(const TWeekRangeConfig &rc) {
        return Next(rc.RangeEnd);
    }

    static TWeekRangeConfig Prev(const TDate &date) {
        TWeekRangeConfig obj;
        obj.RangeEnd = GetStartOfRange(date);
        obj.RangeStart = GetPrevRangeDate(obj.RangeEnd);
        obj.Current = obj.In(TDate::Today());
        return obj;
    }

    static TWeekRangeConfig Prev(const TWeekRangeConfig &rc) {
        return Prev(rc.RangeStart);
    }

    static TDate GetStartOfRange(const TDate &date) {
        const static int WEEK_START_OFFSET_DAYS = 6;
        int weekDay = (GetWeekDay(date) + WEEK_START_OFFSET_DAYS) % 7;
        return TDate(date.GetStart()) - weekDay;
    }

    static unsigned GetWeekDay(const TDate &date) {
        const time_t ts = date.GetStart();
        struct tm *ti = localtime(&ts);
        return ti->tm_wday;
    }

    static TDate GetNextRangeDate(const TDate &date) {
        return date + 7;
    }

    static TDate GetPrevRangeDate(const TDate &date) {
        return date - 7;
    }

    void GetDays(TDeque<TDate> &days) const {
        TDate date = RangeStart;
        while (date < RangeEnd) {
            days.push_back(date);
            date += 1;
        }
    }

    unsigned GetDaysCount() const {
        return (RangeEnd.GetStart() - RangeStart.GetStart()) / SECONDS_IN_DAY;
    }

    bool In(const TDate &date) const {
        return date >= RangeStart && date < RangeEnd;
    }

    bool operator<(const TWeekRangeConfig &rhs) const {
        return RangeStart < rhs.RangeStart;
    }

    TString Name() const {
        return RangeStart.ToStroka() + "_" + RangeEnd.ToStroka();
    }

    friend IOutputStream& operator<<(IOutputStream& left, const TWeekRangeConfig& right);

public:
    TDate RangeStart;
    TDate RangeEnd;
    bool Current = false;
};

} //namespace NWebmaster
