lib/azan/isha_time.ex

defmodule Azan.IshaTime do
  @moduledoc """
  Documentation for `IshaTime`.
  """

  use TypedStruct

  alias Azan.{
    CalculationParameter,
    DateUtils,
    SafeIsha,
    SolarTime,
    TimeComponent
  }

  typedstruct do
    field :solar_time, SolarTime.t()
    field :date, Date.t()
    field :calculation_parameter, CalculationParameter.t()
    field :sunset_time, DateTime.t()
    field :latitude, number()
    field :night, number()
  end

  def find(
        solar_time,
        sunset_time,
        night,
        latitude,
        date,
        %CalculationParameter{} = calculation_parameter
      ) do
    {:ok,
     %__MODULE__{
       calculation_parameter: calculation_parameter,
       sunset_time: sunset_time,
       solar_time: solar_time,
       night: night,
       latitude: latitude,
       date: date
     }
     |> __MODULE__.find!()}
  end

  def find!(%__MODULE__{
        sunset_time: sunset_time,
        calculation_parameter: %CalculationParameter{isha_interval: isha_interval}
      })
      when isha_interval > 0 do
    sunset_time |> Timex.shift(minutes: isha_interval)
  end

  def find!(
        %__MODULE__{
          calculation_parameter: calculation_parameter,
          night: night,
          latitude: latitude,
          date: date,
          sunset_time: sunset_time
        } = isha_time_struct
      ) do
    naive_isha_time = isha_time_struct |> find_naive_time()

    safe_isha_time =
      %SafeIsha{
        calculation_parameter: calculation_parameter,
        night: night,
        latitude: latitude,
        date: date,
        sunset_time: sunset_time
      }
      |> SafeIsha.find()

    naive_isha_time |> naive_or_safe_isha(safe_isha_time)
  end

  def find_naive_time(%__MODULE__{
        calculation_parameter: %CalculationParameter{method: :moonsighting_committee},
        sunset_time: sunset_time,
        night: night,
        latitude: latitude
      })
      when latitude >= 55 do
    night_fraction = night / 7
    sunset_time |> DateUtils.shift_by_seconds(night_fraction)
  end

  def find_naive_time(%__MODULE__{
        calculation_parameter: %CalculationParameter{isha_angle: isha_angle},
        date: date,
        solar_time: solar_time
      }) do
    case solar_time |> SolarTime.hour_angle(-1 * isha_angle, true) do
      {:error, _} ->
        {:error, :invalid_datetime}

      corrected_hour_angle ->
        corrected_hour_angle
        |> TimeComponent.new()
        |> TimeComponent.create_utc_datetime(date)
    end
  end

  def naive_or_safe_isha({:error, _}, safe_isha), do: safe_isha

  def naive_or_safe_isha(naive_isha_time, safe_isha_time) do
    case DateTime.compare(safe_isha_time, naive_isha_time) do
      :lt ->
        safe_isha_time

      _ ->
        naive_isha_time
    end
  end

  def adjust(datetime, %CalculationParameter{
        adjustments: %{isha: adjustment},
        method_adjustments: %{isha: method_adjustment},
        rounding: rounding
      }) do
    adjustment = adjustment |> DateUtils.sum_adjustment(method_adjustment)
    datetime |> DateUtils.rounded_time(adjustment, rounding)
  end
end