src/automata@cron@iterator.erl

-module(automata@cron@iterator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/automata/cron/iterator.gleam").
-export(['after'/2, step/1]).
-export_type([cron_iterator/0, step/0]).

-opaque cron_iterator() :: {cron_iterator,
        automata@cron@normalize:cron_plan(),
        automata@schedule@ast:date_time()}.

-type step() :: {yield,
        automata@schedule@ast:valid_date_time(),
        cron_iterator()} |
    done.

-file("src/automata/cron/iterator.gleam", 40).
-spec start_cursor(automata@schedule@ast:boundary()) -> automata@schedule@ast:date_time().
start_cursor(Boundary) ->
    case Boundary of
        {inclusive, Valid} ->
            Datetime = automata@schedule@ast:valid_datetime_value(Valid),
            case erlang:element(4, erlang:element(3, Datetime)) =:= 0 of
                true ->
                    Datetime;

                false ->
                    automata@internal@calendar:ceil_to_next_minute(Datetime)
            end;

        {exclusive, Valid@1} ->
            Datetime@1 = automata@schedule@ast:valid_datetime_value(Valid@1),
            case erlang:element(4, erlang:element(3, Datetime@1)) =:= 0 of
                true ->
                    automata@internal@calendar:add_minutes(Datetime@1, 1);

                false ->
                    automata@internal@calendar:ceil_to_next_minute(Datetime@1)
            end
    end.

-file("src/automata/cron/iterator.gleam", 22).
-spec 'after'(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:boundary()
) -> cron_iterator().
'after'(Plan, Boundary) ->
    {cron_iterator, Plan, start_cursor(Boundary)}.

-file("src/automata/cron/iterator.gleam", 99).
-spec day_matches(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:date_time()
) -> boolean().
day_matches(Plan, Candidate) ->
    Dom_match = automata@cron@evaluator:int_set_contains(
        erlang:element(4, Plan),
        erlang:element(4, erlang:element(2, Candidate))
    ),
    Dow_match = automata@cron@evaluator:int_set_contains(
        erlang:element(6, Plan),
        automata@internal@calendar:day_of_week_number(Candidate)
    ),
    Dom_any = case erlang:element(4, Plan) of
        {any_value, _, _} ->
            true;

        {values, _} ->
            false
    end,
    Dow_any = case erlang:element(6, Plan) of
        {any_value, _, _} ->
            true;

        {values, _} ->
            false
    end,
    case {Dom_any, Dow_any} of
        {true, true} ->
            true;

        {true, false} ->
            Dow_match;

        {false, true} ->
            Dom_match;

        {false, false} ->
            Dom_match orelse Dow_match
    end.

-file("src/automata/cron/iterator.gleam", 125).
-spec align_hour(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:date_time()
) -> gleam@option:option(automata@schedule@ast:date_time()).
align_hour(Plan, Candidate) ->
    case automata@cron@evaluator:next_value_at_or_after(
        erlang:element(3, Plan),
        erlang:element(2, erlang:element(3, Candidate))
    ) of
        none ->
            none;

        {some, Hour} ->
            case Hour =:= erlang:element(2, erlang:element(3, Candidate)) of
                true ->
                    {some,
                        {date_time,
                            erlang:element(2, Candidate),
                            {time,
                                Hour,
                                erlang:element(3, erlang:element(3, Candidate)),
                                0}}};

                false ->
                    {some,
                        {date_time,
                            erlang:element(2, Candidate),
                            {time,
                                Hour,
                                automata@cron@evaluator:first_value(
                                    erlang:element(2, Plan)
                                ),
                                0}}}
            end
    end.

-file("src/automata/cron/iterator.gleam", 148).
-spec align_minute(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:date_time()
) -> gleam@option:option(automata@schedule@ast:date_time()).
align_minute(Plan, Candidate) ->
    case automata@cron@evaluator:next_value_at_or_after(
        erlang:element(2, Plan),
        erlang:element(3, erlang:element(3, Candidate))
    ) of
        none ->
            none;

        {some, Minute} ->
            {some,
                {date_time,
                    erlang:element(2, Candidate),
                    {time,
                        erlang:element(2, erlang:element(3, Candidate)),
                        Minute,
                        0}}}
    end.

-file("src/automata/cron/iterator.gleam", 159).
-spec next_day_start(automata@schedule@ast:date_time()) -> automata@schedule@ast:date_time().
next_day_start(Candidate) ->
    automata@internal@calendar:next_day(Candidate).

-file("src/automata/cron/iterator.gleam", 163).
-spec next_hour_start(automata@schedule@ast:date_time()) -> automata@schedule@ast:date_time().
next_hour_start(Candidate) ->
    Next = automata@internal@calendar:add_minutes(
        Candidate,
        60 - erlang:element(3, erlang:element(3, Candidate))
    ),
    {date_time,
        erlang:element(2, Next),
        {time, erlang:element(2, erlang:element(3, Next)), 0, 0}}.

-file("src/automata/cron/iterator.gleam", 171).
-spec roll_to_next_matching_month(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:date_time()
) -> automata@schedule@ast:date_time().
roll_to_next_matching_month(Plan, Candidate) ->
    Next = automata@internal@calendar:next_month(Candidate),
    case automata@cron@evaluator:int_set_contains(
        erlang:element(5, Plan),
        erlang:element(3, erlang:element(2, Next))
    ) of
        true ->
            Next;

        false ->
            roll_to_next_matching_month(Plan, Next)
    end.

-file("src/automata/cron/iterator.gleam", 59).
-spec find_next(
    automata@cron@normalize:cron_plan(),
    automata@schedule@ast:date_time(),
    integer()
) -> gleam@option:option(automata@schedule@ast:date_time()).
find_next(Plan, Candidate, Guard) ->
    case Guard > 5000000 of
        true ->
            none;

        false ->
            case automata@cron@evaluator:int_set_contains(
                erlang:element(5, Plan),
                erlang:element(3, erlang:element(2, Candidate))
            ) of
                false ->
                    Next_month = roll_to_next_matching_month(Plan, Candidate),
                    find_next(Plan, Next_month, Guard + 1);

                true ->
                    case day_matches(Plan, Candidate) of
                        false ->
                            find_next(
                                Plan,
                                next_day_start(Candidate),
                                Guard + 1
                            );

                        true ->
                            case align_hour(Plan, Candidate) of
                                none ->
                                    find_next(
                                        Plan,
                                        next_day_start(Candidate),
                                        Guard + 1
                                    );

                                {some, Aligned_hour} ->
                                    case align_minute(Plan, Aligned_hour) of
                                        none ->
                                            find_next(
                                                Plan,
                                                next_hour_start(Aligned_hour),
                                                Guard + 1
                                            );

                                        {some, Aligned_minute} ->
                                            case automata@cron@evaluator:matches(
                                                Plan,
                                                Aligned_minute
                                            ) of
                                                true ->
                                                    {some, Aligned_minute};

                                                false ->
                                                    find_next(
                                                        Plan,
                                                        automata@internal@calendar:add_minutes(
                                                            Aligned_minute,
                                                            1
                                                        ),
                                                        Guard + 1
                                                    )
                                            end
                                    end
                            end
                    end
            end
    end.

-file("src/automata/cron/iterator.gleam", 26).
-spec step(cron_iterator()) -> step().
step(Iterator) ->
    case find_next(erlang:element(2, Iterator), erlang:element(3, Iterator), 0) of
        {some, At} ->
            {yield,
                automata@schedule@ast:unsafe_assume_valid(At),
                {cron_iterator,
                    erlang:element(2, Iterator),
                    automata@internal@calendar:add_minutes(At, 1)}};

        none ->
            done
    end.