exercism/elixir/take-a-number-deluxe/lib/take_a_number_deluxe/state.ex

88 lines
2.7 KiB
Elixir

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