defmodule TakeANumberDeluxe.State do defstruct min_number: 1, max_number: 999, queue: nil, auto_shutdown_timeout: :infinity @type t :: %__MODULE__{} alias TakeANumberDeluxe.Queue @spec new(integer, integer, timeout) :: {:ok, TakeANumberDeluxe.State.t()} | {:error, atom()} def new(min_number, max_number, auto_shutdown_timeout \\ :infinity) do if min_and_max_numbers_valid?(min_number, max_number) and timeout_valid?(auto_shutdown_timeout) do {:ok, %__MODULE__{ min_number: min_number, max_number: max_number, queue: Queue.new(), auto_shutdown_timeout: auto_shutdown_timeout }} else {:error, :invalid_configuration} end end @spec queue_new_number(TakeANumberDeluxe.State.t()) :: {:ok, integer(), TakeANumberDeluxe.State.t()} | {:error, atom()} def queue_new_number(%__MODULE__{} = state) do case find_next_available_number(state) do {:ok, next_available_number} -> {:ok, next_available_number, %{state | queue: Queue.push(state.queue, next_available_number)}} {:error, error} -> {:error, error} end end @spec serve_next_queued_number(TakeANumberDeluxe.State.t(), integer() | nil) :: {:ok, integer(), TakeANumberDeluxe.State.t()} | {:error, atom()} def serve_next_queued_number(%__MODULE__{} = state, priority_number) do cond do Queue.empty?(state.queue) -> {:error, :empty_queue} is_nil(priority_number) -> {{:value, next_number}, new_queue} = Queue.out(state.queue) {:ok, next_number, %{state | queue: new_queue}} Queue.member?(state.queue, priority_number) -> {:ok, priority_number, %{state | queue: Queue.delete(state.queue, priority_number)}} true -> {:error, :priority_number_not_found} end end defp min_and_max_numbers_valid?(min_number, max_number) do is_integer(min_number) and is_integer(max_number) and min_number < max_number end defp timeout_valid?(timeout) do timeout == :infinity || (is_integer(timeout) && timeout >= 0) end defp find_next_available_number(state) do all_numbers_in_use = Queue.to_list(state.queue) all_numbers = Enum.to_list(state.min_number..state.max_number) case all_numbers_in_use do [] -> {:ok, state.min_number} list when length(list) == length(all_numbers) -> {:error, :all_possible_numbers_are_in_use} _ -> current_highest_number = Enum.max(all_numbers_in_use) next_available_number = if current_highest_number < state.max_number do current_highest_number + 1 else Enum.min(all_numbers -- all_numbers_in_use) end {:ok, next_available_number} end end end