lib/time/timem.ex


defmodule Time.Timem do
    @moduledoc"""
    It expands functionalities that Timex library does not present
    """

    import Stuff, only: [integer_to_string_with_2_digits: 1]


    #
    # Url where the information of the holidays was extracted
    #     2021 => "https://www.chile.gob.cl/buenos-aires/feriados-2021",
    #     2022 => "https://www.feriados.cl/index.php",
    #     2023 => "https://www.feriados.cl/2023.htm",
    #
    @holidays %{
        2021 => %{
            january:    [1],
            february:   [15, 16],
            march:      [24],
            april:      [2],
            may:        [21, 24, 25],
            june:       [21],
            july:       [9],
            august:     [16],
            september:  [17],
            october:    [8, 11],
            november:   [22],
            december:   [8]
        },
        2022 => %{
            january:    [1],
            february:   [],
            march:      [],
            april:      [15, 16],
            may:        [1, 21],
            june:       [21, 27],
            july:       [16],
            august:     [15],
            september:  [4, 16, 18, 19],
            october:    [10, 31],
            november:   [1],
            december:   [8, 25]
        },
        2023 => %{
            january:    [1, 2],
            february:   [],
            march:      [],
            april:      [7, 8],
            may:        [1, 21],
            june:       [21, 26],
            july:       [16],
            august:     [15],
            september:  [18, 19],
            october:    [9, 27],
            november:   [1],
            december:   [8, 25]
        },

    }



    @doc"""
    Search the time defined in the configuration, for sending notifications

    ### Parameter:

        - info. Map. Information about the time. It is expected to have the format
            ```%{
                day: value_0,
                hours: value_1,
                minute: value_2,
                second: value_3,
            }
            ```
            If no time measurement is found or they are invalid, it will be taken as 0.

    ### Return:

        - Integer. Time in milliseconds. The smallest possible return value is 1000 (1 second).
    """
    def notification_frequency(info) do
        # amount of         hours,                minutes,                 seconds
        (((get_day(info) * 24 + get_hour(info)) * 60 + get_minute(info)) * 60 + get_second(info))
        |> max(1)
        |> Kernel.*(1000)

    end

    defp get_day(%{day: day})
        when is_integer(day) and 0 <= day
        do
            day
    end

    defp get_day(_) do
        0
    end

    defp get_hour(%{hour: hour})
        when is_integer(hour) and 0 <= hour and hour <= 59
        do
            hour
    end

    defp get_hour(_) do
        0
    end

    defp get_minute(%{minute: minute})
        when is_integer(minute) and 0 <= minute and minute <= 59
        do
            minute
    end

    defp get_minute(_) do
        0
    end

    defp get_second(%{second: second})
        when is_integer(second) and 0 <= second and second <= 59
        do
            second
    end

    defp get_second(_) do
        0
    end

    @doc"""
    Determine given the month and day, if it is a holiday

    ### Parameter:

        - date: Timex.DateTime.

    # Return:

        - boolean. True if day is a holiday, false otherwise.

    """
    def is_holiday?(%{year: year, month: month, day: day} = _date) do
        year
        |> get_holidays_of_year()
        |> get_holidays_of_month(month)
        |> Enum.any?(fn elem -> day == elem end)
    end

    #
    # Gets the holidays of the defined year
    #
    # ### Parameter:
    #
    #     - year: Integer. Year.
    #
    # ### Return:
    #
    #     - Map where the keys are the months and the associated value is the list of holidays. If it is null, then the holidays of the defined year are not known.
    #
    defp get_holidays_of_year(year) do
        Map.get(@holidays, year, %{})
    end

    #
    # Returns the list of holidays of the defined month
    #
    # ### Parameters:
    #
    #     - data: Map. Holidays of the year, divided by each month.
    #
    #     - month. Integer | String. Month. It can be identified by a number or name, in Spanish or English.
    #
    # ### Return:
    #
    #     - List.
    #
    defp get_holidays_of_month(data, month) when is_binary(month) do
        month
        |> String.downcase()
        |> case do
            "enero"     ->      get_holidays_of_month(data, 1)
            "january"   ->      get_holidays_of_month(data, 1)

            "febrero"   ->      get_holidays_of_month(data, 2)
            "february"  ->      get_holidays_of_month(data, 2)

            "marzo"     ->      get_holidays_of_month(data, 3)
            "march"     ->      get_holidays_of_month(data, 3)

            "abril"     ->      get_holidays_of_month(data, 4)
            "april"     ->      get_holidays_of_month(data, 4)

            "mayo"      ->      get_holidays_of_month(data, 5)
            "may"       ->      get_holidays_of_month(data, 5)

            "junio"     ->      get_holidays_of_month(data, 6)
            "june"      ->      get_holidays_of_month(data, 6)

            "julio"     ->      get_holidays_of_month(data, 7)
            "july"      ->      get_holidays_of_month(data, 7)

            "agosto"    ->      get_holidays_of_month(data, 8)
            "august"    ->      get_holidays_of_month(data, 8)

            "septiembre" ->     get_holidays_of_month(data, 9)
            "september"  ->     get_holidays_of_month(data, 9)

            "octubre"   ->      get_holidays_of_month(data, 10)
            "october"   ->      get_holidays_of_month(data, 10)

            "noviembre" ->      get_holidays_of_month(data, 11)
            "november"  ->      get_holidays_of_month(data, 11)

            "diciembre" ->      get_holidays_of_month(data, 12)
            "december"  ->      get_holidays_of_month(data, 12)

            _ -> get_holidays_of_month(data, 0)
        end
    end

    defp get_holidays_of_month(data, month) when is_integer(month) do
        month =
            case month do
                1 ->    :january
                2 ->    :february
                3 ->    :march
                4 ->    :april
                5 ->    :may
                6 ->    :june
                7 ->    :july
                8 ->    :august
                9 ->    :september
                10 ->   :october
                11 ->   :november
                12 ->   :december
                _ ->    nil
            end
        Map.get(data, month, [])
    end

    @doc"""
    Returns the current time in YYYY-MM-DDformat

    ### Return:

        - String

    """
    def get_date_with_string_format() do
        %{day: day, month: month, year: year} = Timex.now()
        day = day |> integer_to_string_with_2_digits()
        month = month |> integer_to_string_with_2_digits()
        year = year |> integer_to_string_with_2_digits()

        "#{year}-#{month}-#{day}"
    end

    @doc"""
    Returns the current date in HH:MM:SS format

    ### Return:

        - String

    """
    def get_time_with_string_format() do
        %{hour: hour, minute: minute, second: second} = Timex.now()
        hour = hour |> integer_to_string_with_2_digits()
        minute = minute |> integer_to_string_with_2_digits()
        second = second |> integer_to_string_with_2_digits()

        "#{hour}:#{minute}:#{second}"
    end

    @doc"""
    Given a date range, construct consecutive non-overlapping intervals

    ### Parameters:

        - start_date: Timex.DateTime. Interval start date.

        - end_date: Timex.DateTime. End date of the interval.

        -step: Integer. Number of days covered by a subinterval.

    ### Returns:

        - {:error, Atom (reason)} | {:ok, [{Timex.DateTime, Timex.DateTime}, ...]}

    """
    def by_intervals(start_date, end_date, step)
        when is_integer(step) and step > 0 do

            Timex.is_valid?(start_date) and Timex.is_valid?(end_date)
            |> Kernel.not()
            |> if do
                {:error, :invalid_date}
            else
                {:ok, by_intervals(start_date, end_date, step, [])}
            end
    end

    defp by_intervals(end_date, end_date, _step, acc) do
        acc
    end

    defp by_intervals(start_date, end_date, step, acc) do
        partial_end_date = Timex.shift(start_date, days: step)
        partial_end_date =
            if Timex.diff(partial_end_date, end_date, :second) <= 0 do
                partial_end_date
            else
                end_date
            end

        by_intervals(partial_end_date, end_date, step, acc ++ [{start_date, partial_end_date}])
    end




end