src/automata@rrule@evaluator.erl

-module(automata@rrule@evaluator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/automata/rrule/evaluator.gleam").
-export([day_matches/2, next_time_on_day/3, first_time_on_day/2, next_occurrence/3, matches/2, yielded_before/2]).
-export_type([nth_scope/0, occurrence/0]).

-type nth_scope() :: month_scope | year_scope.

-type occurrence() :: {occurrence, integer(), integer()}.

-file("src/automata/rrule/evaluator.gleam", 102).
-spec date_to_datetime(automata@schedule@ast:date()) -> automata@schedule@ast:date_time().
date_to_datetime(Date) ->
    {date_time, Date, {time, 23, 59, 59}}.

-file("src/automata/rrule/evaluator.gleam", 90).
-spec within_until(
    automata@rrule@validator:end_condition(),
    automata@schedule@ast:date_time()
) -> boolean().
within_until(End_condition, At) ->
    case End_condition of
        {until, Until} ->
            case Until of
                {until_date, Date} ->
                    automata@internal@calendar:less_or_equal(
                        At,
                        date_to_datetime(Date)
                    );

                {until_date_time, Limit} ->
                    automata@internal@calendar:less_or_equal(At, Limit)
            end;

        _ ->
            true
    end.

-file("src/automata/rrule/evaluator.gleam", 183).
-spec month_day_matches(
    gleam@option:option(list(integer())),
    automata@schedule@ast:date()
) -> boolean().
month_day_matches(Values, Date) ->
    case Values of
        none ->
            true;

        {some, Items} ->
            Maximum = automata@internal@calendar:days_in_month(
                erlang:element(2, Date),
                erlang:element(3, Date)
            ),
            gleam@list:any(Items, fun(Item) -> case Item > 0 of
                        true ->
                            Item =:= erlang:element(4, Date);

                        false ->
                            ((Maximum + Item) + 1) =:= erlang:element(4, Date)
                    end end)
    end.

-file("src/automata/rrule/evaluator.gleam", 261).
-spec nth_scope(automata@rrule@normalize:r_rule_plan()) -> nth_scope().
nth_scope(Plan) ->
    case {erlang:element(3, Plan), erlang:element(7, Plan)} of
        {yearly, none} ->
            year_scope;

        {_, _} ->
            month_scope
    end.

-file("src/automata/rrule/evaluator.gleam", 276).
-spec weekday_occurrences_in_month_loop(
    automata@schedule@ast:weekday(),
    integer(),
    integer(),
    integer(),
    list(occurrence())
) -> list(occurrence()).
weekday_occurrences_in_month_loop(Weekday, Year, Month, Day, Acc) ->
    Maximum = automata@internal@calendar:days_in_month(Year, Month),
    case Day > Maximum of
        true ->
            lists:reverse(Acc);

        false ->
            Datetime = {date_time, {date, Year, Month, Day}, {time, 0, 0, 0}},
            case automata@internal@calendar:weekday(Datetime) =:= Weekday of
                true ->
                    weekday_occurrences_in_month_loop(
                        Weekday,
                        Year,
                        Month,
                        Day + 1,
                        [{occurrence, Month, Day} | Acc]
                    );

                false ->
                    weekday_occurrences_in_month_loop(
                        Weekday,
                        Year,
                        Month,
                        Day + 1,
                        Acc
                    )
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 268).
-spec weekday_occurrences_in_month(
    automata@schedule@ast:weekday(),
    integer(),
    integer()
) -> list(occurrence()).
weekday_occurrences_in_month(Weekday, Year, Month) ->
    weekday_occurrences_in_month_loop(Weekday, Year, Month, 1, []).

-file("src/automata/rrule/evaluator.gleam", 310).
-spec weekday_occurrences_in_year_loop(
    automata@schedule@ast:weekday(),
    integer(),
    integer(),
    list(occurrence())
) -> list(occurrence()).
weekday_occurrences_in_year_loop(Weekday, Year, Month, Acc) ->
    case Month > 12 of
        true ->
            lists:reverse(Acc);

        false ->
            Month_occurrences = weekday_occurrences_in_month(
                Weekday,
                Year,
                Month
            ),
            weekday_occurrences_in_year_loop(
                Weekday,
                Year,
                Month + 1,
                gleam@list:fold(
                    Month_occurrences,
                    Acc,
                    fun(Carry, Item) -> [Item | Carry] end
                )
            )
    end.

-file("src/automata/rrule/evaluator.gleam", 306).
-spec weekday_occurrences_in_year(automata@schedule@ast:weekday(), integer()) -> list(occurrence()).
weekday_occurrences_in_year(Weekday, Year) ->
    weekday_occurrences_in_year_loop(Weekday, Year, 1, []).

-file("src/automata/rrule/evaluator.gleam", 227).
-spec nth_weekday_matches(
    automata@rrule@normalize:r_rule_plan(),
    integer(),
    automata@schedule@ast:weekday(),
    automata@schedule@ast:date()
) -> boolean().
nth_weekday_matches(Plan, Ordinal, Weekday, Date) ->
    Occurrences = case nth_scope(Plan) of
        year_scope ->
            weekday_occurrences_in_year(Weekday, erlang:element(2, Date));

        month_scope ->
            weekday_occurrences_in_month(
                Weekday,
                erlang:element(2, Date),
                erlang:element(3, Date)
            )
    end,
    case Ordinal > 0 of
        true ->
            case gleam@list:drop(Occurrences, Ordinal - 1) of
                [Match | _] ->
                    (erlang:element(2, Match) =:= erlang:element(3, Date))
                    andalso (erlang:element(3, Match) =:= erlang:element(
                        4,
                        Date
                    ));

                [] ->
                    false
            end;

        false ->
            case gleam@list:drop(lists:reverse(Occurrences), (0 - Ordinal) - 1) of
                [Match@1 | _] ->
                    (erlang:element(2, Match@1) =:= erlang:element(3, Date))
                    andalso (erlang:element(3, Match@1) =:= erlang:element(
                        4,
                        Date
                    ));

                [] ->
                    false
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 214).
-spec weekday_specifier_matches(
    automata@rrule@normalize:r_rule_plan(),
    automata@rrule@validator:weekday_specifier(),
    automata@schedule@ast:weekday(),
    automata@schedule@ast:date()
) -> boolean().
weekday_specifier_matches(Plan, Specifier, Actual, Date) ->
    case Specifier of
        {every_weekday, Expected} ->
            Expected =:= Actual;

        {nth_weekday, Ordinal, Expected@1} ->
            (Expected@1 =:= Actual) andalso nth_weekday_matches(
                Plan,
                Ordinal,
                Expected@1,
                Date
            )
    end.

-file("src/automata/rrule/evaluator.gleam", 198).
-spec weekday_matches(
    automata@rrule@normalize:r_rule_plan(),
    gleam@option:option(list(automata@rrule@validator:weekday_specifier())),
    automata@schedule@ast:date_time()
) -> boolean().
weekday_matches(Plan, Values, At) ->
    case Values of
        none ->
            true;

        {some, Items} ->
            Actual = automata@internal@calendar:weekday(At),
            gleam@list:any(
                Items,
                fun(Item) ->
                    weekday_specifier_matches(
                        Plan,
                        Item,
                        Actual,
                        erlang:element(2, At)
                    )
                end
            )
    end.

-file("src/automata/rrule/evaluator.gleam", 52).
-spec day_matches(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date()
) -> boolean().
day_matches(Plan, Date) ->
    Datetime = {date_time, Date, {time, 0, 0, 0}},
    Month_match = case erlang:element(7, Plan) of
        none ->
            true;

        {some, Values} ->
            gleam@list:any(
                Values,
                fun(Item) -> Item =:= erlang:element(3, Date) end
            )
    end,
    Month_day_match = month_day_matches(erlang:element(8, Plan), Date),
    Weekday_match = weekday_matches(Plan, erlang:element(6, Plan), Datetime),
    Month_match andalso case {erlang:element(8, Plan), erlang:element(6, Plan)} of
        {{some, _}, {some, _}} ->
            Month_day_match andalso Weekday_match;

        {{some, _}, none} ->
            Month_day_match;

        {none, {some, _}} ->
            Weekday_match;

        {none, none} ->
            true
    end.

-file("src/automata/rrule/evaluator.gleam", 351).
-spec find_minute(
    integer(),
    list(integer()),
    automata@schedule@ast:date(),
    integer(),
    automata@schedule@ast:date_time()
) -> gleam@option:option(automata@schedule@ast:date_time()).
find_minute(Hour, Minutes, Date, Second, Search) ->
    case Minutes of
        [] ->
            none;

        [Minute | Rest] ->
            Candidate = {date_time, Date, {time, Hour, Minute, Second}},
            case automata@internal@calendar:less_than(Candidate, Search) of
                true ->
                    find_minute(Hour, Rest, Date, Second, Search);

                false ->
                    {some, Candidate}
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 330).
-spec find_time(
    list(integer()),
    list(integer()),
    automata@schedule@ast:date(),
    integer(),
    automata@schedule@ast:date_time()
) -> gleam@option:option(automata@schedule@ast:date_time()).
find_time(Hours, Minutes, Date, Second, Search) ->
    case Hours of
        [] ->
            none;

        [Hour | Rest_hours] ->
            case Hour < erlang:element(2, erlang:element(3, Search)) of
                true ->
                    find_time(Rest_hours, Minutes, Date, Second, Search);

                false ->
                    case find_minute(Hour, Minutes, Date, Second, Search) of
                        {some, Found} ->
                            {some, Found};

                        none ->
                            find_time(Rest_hours, Minutes, Date, Second, Search)
                    end
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 44).
-spec next_time_on_day(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date(),
    automata@schedule@ast:date_time()
) -> gleam@option:option(automata@schedule@ast:date_time()).
next_time_on_day(Plan, Date, Search) ->
    find_time(
        erlang:element(9, Plan),
        erlang:element(10, Plan),
        Date,
        erlang:element(11, Plan),
        Search
    ).

-file("src/automata/rrule/evaluator.gleam", 371).
-spec list_first(list(integer())) -> integer().
list_first(Values) ->
    case Values of
        [First | _] ->
            First;

        [] ->
            0
    end.

-file("src/automata/rrule/evaluator.gleam", 33).
-spec first_time_on_day(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date()
) -> automata@schedule@ast:date_time().
first_time_on_day(Plan, Date) ->
    {date_time,
        Date,
        {time,
            list_first(erlang:element(9, Plan)),
            list_first(erlang:element(10, Plan)),
            erlang:element(11, Plan)}}.

-file("src/automata/rrule/evaluator.gleam", 386).
-spec months_between(
    automata@schedule@ast:date_time(),
    automata@schedule@ast:date_time()
) -> integer().
months_between(From, To) ->
    ((erlang:element(2, erlang:element(2, To)) * 12) + erlang:element(
        3,
        erlang:element(2, To)
    ))
    - ((erlang:element(2, erlang:element(2, From)) * 12) + erlang:element(
        3,
        erlang:element(2, From)
    )).

-file("src/automata/rrule/evaluator.gleam", 398).
-spec weekday_offset(automata@schedule@ast:weekday()) -> integer().
weekday_offset(Day) ->
    case Day of
        monday ->
            0;

        tuesday ->
            1;

        wednesday ->
            2;

        thursday ->
            3;

        friday ->
            4;

        saturday ->
            5;

        sunday ->
            6
    end.

-file("src/automata/rrule/evaluator.gleam", 391).
-spec start_of_week(automata@schedule@ast:date_time()) -> automata@schedule@ast:date_time().
start_of_week(Datetime) ->
    automata@internal@calendar:add_days(
        {date_time, erlang:element(2, Datetime), {time, 0, 0, 0}},
        0 - weekday_offset(automata@internal@calendar:weekday(Datetime))
    ).

-file("src/automata/rrule/evaluator.gleam", 428).
-spec days_before_month_loop(integer(), integer(), integer(), integer()) -> integer().
days_before_month_loop(Year, Target, Month, Acc) ->
    case Month >= Target of
        true ->
            Acc;

        false ->
            days_before_month_loop(
                Year,
                Target,
                Month + 1,
                Acc + automata@internal@calendar:days_in_month(Year, Month)
            )
    end.

-file("src/automata/rrule/evaluator.gleam", 424).
-spec days_before_month(integer(), integer()) -> integer().
days_before_month(Year, Month) ->
    days_before_month_loop(Year, Month, 1, 0).

-file("src/automata/rrule/evaluator.gleam", 441).
-spec modulo(integer(), integer()) -> integer().
modulo(Dividend, Divisor) ->
    case gleam@int:modulo(Dividend, Divisor) of
        {ok, Result} ->
            Result;

        {error, _} ->
            0
    end.

-file("src/automata/rrule/evaluator.gleam", 448).
-spec quotient(integer(), integer()) -> integer().
quotient(Dividend, Divisor) ->
    case gleam@int:divide(Dividend, Divisor) of
        {ok, Result} ->
            Result;

        {error, _} ->
            0
    end.

-file("src/automata/rrule/evaluator.gleam", 416).
-spec days_before_year(integer()) -> integer().
days_before_year(Year) ->
    Previous = Year - 1,
    (((Previous * 365) + quotient(Previous, 4)) - quotient(Previous, 100)) + quotient(
        Previous,
        400
    ).

-file("src/automata/rrule/evaluator.gleam", 410).
-spec days_since_epoch(automata@schedule@ast:date()) -> integer().
days_since_epoch(Date) ->
    (days_before_year(erlang:element(2, Date)) + days_before_month(
        erlang:element(2, Date),
        erlang:element(3, Date)
    ))
    + erlang:element(4, Date).

-file("src/automata/rrule/evaluator.gleam", 378).
-spec days_between(
    automata@schedule@ast:date_time(),
    automata@schedule@ast:date_time()
) -> integer().
days_between(From, To) ->
    days_since_epoch(erlang:element(2, To)) - days_since_epoch(
        erlang:element(2, From)
    ).

-file("src/automata/rrule/evaluator.gleam", 382).
-spec weeks_between(
    automata@schedule@ast:date_time(),
    automata@schedule@ast:date_time()
) -> integer().
weeks_between(From, To) ->
    quotient(days_between(start_of_week(From), start_of_week(To)), 7).

-file("src/automata/rrule/evaluator.gleam", 78).
-spec aligned(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time()
) -> boolean().
aligned(Plan, At) ->
    case erlang:element(3, Plan) of
        daily ->
            modulo(
                days_between(erlang:element(2, Plan), At),
                erlang:element(4, Plan)
            )
            =:= 0;

        weekly ->
            modulo(
                weeks_between(erlang:element(2, Plan), At),
                erlang:element(4, Plan)
            )
            =:= 0;

        monthly ->
            modulo(
                months_between(erlang:element(2, Plan), At),
                erlang:element(4, Plan)
            )
            =:= 0;

        yearly ->
            modulo(
                erlang:element(2, erlang:element(2, At)) - erlang:element(
                    2,
                    erlang:element(2, erlang:element(2, Plan))
                ),
                erlang:element(4, Plan)
            )
            =:= 0
    end.

-file("src/automata/rrule/evaluator.gleam", 70).
-spec base_match(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time()
) -> boolean().
base_match(Plan, At) ->
    (((aligned(Plan, At) andalso day_matches(Plan, erlang:element(2, At)))
    andalso gleam@list:any(
        erlang:element(9, Plan),
        fun(Hour) -> Hour =:= erlang:element(2, erlang:element(3, At)) end
    ))
    andalso gleam@list:any(
        erlang:element(10, Plan),
        fun(Minute) -> Minute =:= erlang:element(3, erlang:element(3, At)) end
    ))
    andalso (erlang:element(4, erlang:element(3, At)) =:= erlang:element(
        11,
        Plan
    )).

-file("src/automata/rrule/evaluator.gleam", 137).
-spec next_occurrence(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time(),
    integer()
) -> gleam@option:option(automata@schedule@ast:date_time()).
next_occurrence(Plan, Search, Guard) ->
    case Guard > 4000000 of
        true ->
            none;

        false ->
            Start = case automata@internal@calendar:less_than(
                Search,
                erlang:element(2, Plan)
            ) of
                true ->
                    erlang:element(2, Plan);

                false ->
                    Search
            end,
            case within_until(erlang:element(5, Plan), Start) of
                false ->
                    none;

                true ->
                    case day_matches(Plan, erlang:element(2, Start)) andalso aligned(
                        Plan,
                        Start
                    ) of
                        true ->
                            case next_time_on_day(
                                Plan,
                                erlang:element(2, Start),
                                Start
                            ) of
                                {some, Found} ->
                                    case within_until(
                                        erlang:element(5, Plan),
                                        Found
                                    ) of
                                        true ->
                                            {some, Found};

                                        false ->
                                            none
                                    end;

                                none ->
                                    next_occurrence(
                                        Plan,
                                        automata@internal@calendar:next_day(
                                            Start
                                        ),
                                        Guard + 1
                                    )
                            end;

                        false ->
                            next_occurrence(
                                Plan,
                                automata@internal@calendar:next_day(Start),
                                Guard + 1
                            )
                    end
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 106).
-spec occurrence_index(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time(),
    integer(),
    automata@schedule@ast:date_time()
) -> integer().
occurrence_index(Plan, Target, Count, Search) ->
    case next_occurrence(Plan, Search, 0) of
        none ->
            Count;

        {some, Found} ->
            case automata@internal@calendar:compare(Found, Target) of
                lt ->
                    occurrence_index(
                        Plan,
                        Target,
                        Count + 1,
                        automata@internal@calendar:add_seconds(Found, 1)
                    );

                eq ->
                    Count + 1;

                gt ->
                    Count
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 13).
-spec matches(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time()
) -> boolean().
matches(Plan, At) ->
    case automata@internal@calendar:less_than(At, erlang:element(2, Plan)) of
        true ->
            false;

        false ->
            case within_until(erlang:element(5, Plan), At) of
                false ->
                    false;

                true ->
                    case base_match(Plan, At) of
                        false ->
                            false;

                        true ->
                            case erlang:element(5, Plan) of
                                {count, Limit} ->
                                    occurrence_index(
                                        Plan,
                                        At,
                                        0,
                                        erlang:element(2, Plan)
                                    )
                                    =< Limit;

                                _ ->
                                    true
                            end
                    end
            end
    end.

-file("src/automata/rrule/evaluator.gleam", 129).
-spec yielded_before(
    automata@rrule@normalize:r_rule_plan(),
    automata@schedule@ast:date_time()
) -> integer().
yielded_before(Plan, Cursor) ->
    case automata@internal@calendar:less_than(Cursor, erlang:element(2, Plan)) of
        true ->
            0;

        false ->
            occurrence_index(
                Plan,
                automata@internal@calendar:add_seconds(Cursor, -1),
                0,
                erlang:element(2, Plan)
            )
    end.