defmodule Membrane.Testing.MockResourceGuard do
  @moduledoc """
  Mock for `Membrane.ResourceGuard`.

  Informs the test process about registered cleanup functions and tags.
  Works with `Membrane.Testing.Assertions`, for example
  `Membrane.Testing.Assertions.assert_resource_guard_register/4`:

      iex> guard = #{inspect(__MODULE__)}.start_link_supervised!()
      ...> Membrane.ResourceGuard.register(guard, fn -> :abc end, tag: :some_tag)
      ...> import Membrane.Testing.Assertions
      ...> assert_resource_guard_register(guard, function, :some_tag)
      ...> function.()
      :abc

  """
  use GenServer

  require Membrane.Core.Message, as: Message
  require Membrane.Core.Utils, as: Utils

  @type options :: [test_process: pid]

  @spec child_spec(test_process: pid()) :: Supervisor.child_spec()
  def child_spec(options) do
    super(options) |> Map.merge(%{restart: :transient, id: {__MODULE__, make_ref()}})
  end

  @spec start_link(options) :: {:ok, pid}
  def start_link(options \\ []) do
    options = Keyword.put_new(options, :test_process, self())
    GenServer.start_link(__MODULE__, options)
  end

  @spec start_link_supervised!(options) :: pid
  def start_link_supervised!(options \\ []) do
    options = Keyword.put_new(options, :test_process, self())
    {:ok, pid} = ex_unit_start_supervised({__MODULE__, options})
    pid
  end

  @impl true
  def init(options) do
    Utils.log_on_error do
      {:ok, Map.new(options)}
    end
  end

  @impl true
  def handle_info(msg, state) do
    Utils.log_on_error do
      do_handle_info(msg, state)
    end
  end

  defp do_handle_info(Message.new(:register, [function, options]), state) do
    tag = Keyword.fetch!(options, :tag)
    send_to_test_process(state, :register, {function, tag})
    {:noreply, state}
  end

  defp do_handle_info(Message.new(:unregister, tag), state) do
    send_to_test_process(state, :unregister, tag)
    {:noreply, state}
  end

  defp do_handle_info(Message.new(:cleanup, tag), state) do
    send_to_test_process(state, :cleanup, tag)
    {:noreply, state}
  end

  defp ex_unit_start_supervised(child_spec) do
    # It's not a 'normal' call to keep dialyzer quiet
    apply(ExUnit.Callbacks, :start_supervised, [child_spec])
  end

  defp send_to_test_process(%{test_process: test_process}, type, args) do
    send(test_process, {__MODULE__, self(), {type, args}})
  end
end
