defmodule Commanded.Application.Config do
  @moduledoc false

  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def associate(pid, application, value) when is_pid(pid) and is_atom(application) do
    GenServer.call(__MODULE__, {:associate, pid, application, value})
  end

  def get(application, setting) when is_atom(application) and is_atom(setting) do
    application |> lookup() |> Keyword.get(setting)
  end

  @doc """
  Put a value into the application's config. This is test induced damage and you
  probably shouldn't be using it.
  """
  def __put__(application, setting, value) when is_atom(application) and is_atom(setting) do
    GenServer.call(__MODULE__, {:put, application, setting, value})
  end

  @impl GenServer
  def init(:ok) do
    table = :ets.new(__MODULE__, [:named_table, read_concurrency: true])

    {:ok, table}
  end

  @impl GenServer
  def handle_call({:associate, pid, application, config}, _from, table) do
    ref = Process.monitor(pid)
    true = :ets.insert(table, {application, pid, ref, config})

    {:reply, :ok, table}
  end

  @impl GenServer
  def handle_call({:put, application, settings, value}, _from, table) do
    config =
      lookup(application)
      |> Keyword.put(settings, value)

    true = :ets.update_element(table, application, {4, config})

    {:reply, :ok, table}
  end

  @impl GenServer
  def handle_info({:DOWN, ref, _type, pid, _reason}, table) do
    [[application]] = :ets.match(table, {:"$1", pid, ref, :_})
    :ets.delete(table, application)

    {:noreply, table}
  end

  defp lookup(application) do
    :ets.lookup_element(__MODULE__, application, 4)
  rescue
    ArgumentError ->
      raise "could not lookup #{inspect(application)} because it was not started or it does not exist"
  end
end
