defmodule ExPomodoro do @moduledoc """ Documentation for `ExPomodoro`. """ alias ExPomodoro.{ Pomodoro, PomodoroServer, PomodoroSupervisor } @type success_response :: {:ok, Pomodoro.t()} @type started_response :: {:ok, {:started, pid()}} @type already_started_response :: {:ok, {:already_started, Pomodoro.t()}} @type already_finished_response :: {:ok, {:already_finished, Pomodoro.t()}} @type resumed_response :: {:ok, {:resumed, Pomodoro.t()}} @type not_found_response :: {:error, :not_found} @doc """ Returns the `#{ExPomodoro}` child spec. It is intended for appliations to add an `#{ExPomodoro}` child spec to their application trees to have an `#{ExPomodoro.Supervisor}` started before interacting with the rest of the `#{ExPomodoro}` commands. """ @spec child_spec(keyword) :: Supervisor.child_spec() defdelegate child_spec(options \\ []), to: ExPomodoro.Supervisor @doc """ This is the main function to start a pomodoro. Given an `id` and an optional keyword of options returns a successful response if a Pomodoro has been started or resumed. Every successful responses returns the current `#{Pomodoro}` struct. ### Options * `exercise_duration`: The duration in milliseconds of the exercise duration, `non_negative_integer()`. * `break_duration`: The duration in milliseconds of the break duration, `non_negative_integer()`. * `rounds`: The number of rounds until a long break, `non_negative_integer()`. ### Examples: # Start a pomodoro with default options. iex> ExPomodoro.start("some id") {:ok, %ExPomodoro.Pomodoro{ id: "some id", activity: :exercise, exercise_duration: 1_500_000, break_duration: 300_000, rounds: 4 }} # Start a pomodoro with some options. iex> ExPomodoro.start("some id", [ ...> exercise_duration: 150_000, ...> break_duration: 25_000, ...> rounds: 8 ...> ]) {:ok, %ExPomodoro.Pomodoro{ id: "some id", activity: :exercise, exercise_duration: 150_000, break_duration: 25_000, rounds: 8 }} # Start a pomodoro that is already running. iex> ExPomodoro.start("some_id") {:ok, {:already_started, %Pomodoro{id: "some id"}}} # Start a pomodoro that already finished. iex> ExPomodoro.start("some_id") {:ok, {:already_finished, %Pomodoro{id: "some id"}}} # Start a pomodoro that was paused or finished a break. iex> ExPomodoro.start("some_id") {:ok, {:resumed, %Pomodoro{id: "some id"}}} """ @spec start(Pomodoro.id(), Pomodoro.opts()) :: success_response() | already_started_response() | already_finished_response() | resumed_response() def start(id, opts \\ []) do with {:ok, {:started, pid}} <- start_child(id, opts), {:ok, {^pid, %Pomodoro{} = pomodoro}} <- get_by_id(id) do {:ok, pomodoro} end end @doc """ Generally this function is used to check whether a Pomodoro exists or not. Given an `id`, if a Pomodoro exists, a `#{Pomodoro}` struct is returned, othwerise returns an error tuple. ### Examples: # Return a pomodoro. iex> ExPomodoro.get("some id") {:ok, %ExPomodoro.Pomodoro{id: "some id"}} # Get a pomodoro that does not exist. iex> ExPomodoro.get("some other id") {:error, :not_found} """ @spec get(Pomodoro.id()) :: success_response() | not_found_response() def get(id) do with {:ok, {_pid, %Pomodoro{} = pomodoro}} <- get_by_id(id) do {:ok, pomodoro} end end @doc """ The Pomodoro timer can be paused using this function. While this function will cause a pomodoro to pause, it can still finish by timeout, defined in the `#{ExPomodoro.PomodoroServer}` implementation. Given an `id`, returns a `#{Pomodoro}` struct or an error tuple if the pomodoro does not exist. ### Examples: # Pause a pomodoro and returns the remaining timeleft to complete the # current activity. iex> ExPomodoro.pause("some id") {:ok, %Pomodoro{id: "some id", activity: :idle, current_duration: timeleft}} # Pause a pomodoro that does not exist. iex> ExPomodoro.pause("some id") {:error, :not_found} """ @spec pause(Pomodoro.id()) :: success_response() | not_found_response() def pause(id) do with {:ok, {pid, %Pomodoro{}}} <- get_by_id(id), {:ok, %{id: ^id, pomodoro: %Pomodoro{} = pomodoro}} <- PomodoroServer.pause(pid) do {:ok, pomodoro} end end @spec get_by_id(Pomodoro.id()) :: {:ok, {pid(), Pomodoro.t()}} | not_found_response() defp get_by_id(id) do with {pid, %Pomodoro{id: ^id}} <- PomodoroSupervisor.get_child(id), %Pomodoro{} = pomodoro <- PomodoroServer.get_state(pid) do {:ok, {pid, pomodoro}} else nil -> {:error, :not_found} end end @spec start_child(Pomodoro.id(), Pomodoro.opts()) :: started_response() | already_started_response() | already_finished_response() | resumed_response() defp start_child(id, opts) do case get_by_id(id) do {:ok, {pid, %Pomodoro{activity: :idle}}} -> {:ok, %Pomodoro{} = pomodoro} = PomodoroServer.resume(pid) {:ok, {:resumed, pomodoro}} {:ok, {_pid, %Pomodoro{activity: :finished} = pomodoro}} -> {:ok, {:already_finished, pomodoro}} {:ok, {_pid, %Pomodoro{activity: activity} = pomodoro}} when activity in [:exercise, :break] -> {:ok, {:already_started, pomodoro}} {:error, :not_found} -> {:ok, pid} = PomodoroSupervisor.start_child( PomodoroSupervisor, Keyword.merge([id: id], opts) ) {:ok, {:started, pid}} end end end