defmodule PhoenixLiveState.StateServer do
  @moduledoc """
  Client API for the state server implementation.
  """
  @moduledoc since: "0.1.0"
  alias PhoenixLiveState.StateServer.Settings
  alias PhoenixLiveState.StateSpec

  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [opts]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  @doc """
  Starts a new state server with the given key and options.
  """
  @doc since: "0.1.0"
  def start_link(start_opts) when is_list(start_opts) do
    key = Keyword.fetch!(start_opts, :key)
    state_spec = Keyword.fetch!(start_opts, :state_spec)

    init_opts =
      if is_atom(state_spec) do
        case StateSpec.mount(state_spec, key) do
          {:ok, spec} ->
            [
              key: key,
              initial_state: spec.initial_state || %{},
              state_spec: state_spec,
              settings: Settings.new(spec)
            ]

          error ->
            raise "Could not initialize state server: #{inspect(error)}"
        end
      else
        start_opts
      end

    GenServer.start_link(__MODULE__.ServerImpl, init_opts, name: process_name(key))
  end

  @doc """
  Attaches the current pid to the given state server.
  """
  @doc since: "0.1.0"
  def attach(key) do
    key
    |> process_name()
    |> GenServer.call(:attach)
  end

  @doc """
  Returns the value of the given property from the state server.

  If no property is given, returns the entire state.
  """
  @doc since: "0.1.0"
  def get(key, property \\ nil) do
    key
    |> process_name()
    |> GenServer.call({:get, property})
  end

  @doc """
  Puts a new key-value pair into the state server.

  If the key already exists, the value will be updated.
  """
  @doc since: "0.1.0"
  def put(key, property_name, property_value) do
    key
    |> process_name()
    |> GenServer.call({:put, property_name, property_value})
  end

  @doc """
  Deletes the given property from the state server.

  If no property is given, deletes the entire state.
  """
  @doc since: "0.1.0"
  def delete(key, property_name) do
    key
    |> process_name()
    |> GenServer.call({:delete, property_name})
  end

  @doc """
  Resets the server to the initial state, if a state init function is available.
  """
  @doc since: "0.1.0"
  def reset(key) do
    key
    |> process_name()
    |> GenServer.call(:reset)
  end

  defp process_name(key) do
    {:global, {__MODULE__, key}}
  end
end
