defmodule Livebook.Runtime.ErlDist.IOForwardGL do
  # An IO device process forwarding all requests to sender's group
  # leader.
  #
  # We register this device as the `:standard_error` in the runtime
  # node, so that all evaluation warnings are treated as stdout.
  #
  # The process implements [The Erlang I/O Protocol](https://erlang.org/doc/apps/stdlib/io_protocol.html)
  # and can be thought of as a virtual IO device.

  use GenServer

  @doc """
  Starts the IO device.

  ## Options

    * `:name` - the name to register the process under. Optional.
      If the name is already used, it will be unregistered before
      starting the process and registered back when the server
      terminates

  """
  @spec start_link(keyword()) :: GenServer.on_start()
  def start_link(opts \\ []) do
    name = opts[:name]

    if previous = name && Process.whereis(name) do
      Process.unregister(name)
    end

    GenServer.start_link(__MODULE__, {name, previous}, opts)
  end

  @impl true
  def init({name, previous}) do
    Process.flag(:trap_exit, true)
    {:ok, %{name: name, previous: previous}}
  end

  @impl true
  def handle_info({:io_request, from, reply_as, req}, state) do
    case Process.info(from, :group_leader) do
      {:group_leader, group_leader} ->
        # Forward the request to sender's group leader and instruct
        # it to get back to the sender.
        send(group_leader, {:io_request, from, reply_as, req})

      _ ->
        send(from, {:io_reply, reply_as, {:error, :terminated}})
    end

    {:noreply, state}
  end

  @impl true
  def terminate(_, %{name: name, previous: previous}) do
    if name && previous do
      Process.unregister(name)
      Process.register(previous, name)
    end

    :ok
  end
end
