defmodule FLAME.Backend do
  @moduledoc """
  Defines a behavior for a FLAME backend.

  A FLAME backend is responsible for booting remote compute resources,
  connecting them back to the parent node, and executing functions on them.

  The default `FLAME.LocalBackend` simply runs your code locally, allowing
  you to develop and test your application using `FLAME.call/3` without
  running an external backend.

  ## Messaging

  The `FLAME.Terminator` process runs on remote nodes automatically and is
  responsible for connecting back to the parent node, notifying the parent, and
  handling termination of remote processes started via `FLAME.call/3`, `FLAME.cast/3`,
  and `FLAME.place_child/3`. When the terminator starts on a newly booted remote
  node, it sends the following message to the parent runner process:

      {ref, {:remote_up, remote_terminator_pid}}

  Where ref is the reference generated by the backend and encoded into the
  `FLAME.Parent.encode/1` string.

  When the remote terminator is going away gracefully, it sends the following message:

      {ref, {:remote_shutdown, :idle}}

  Backend implementations can react to these messages to handle the remotely
  provisioned instance booting up or shutting down.

  See `FLAME.FlyBackend` for an example implementation of this behavior.
  """
  @callback init(opts :: Keyword.t()) :: {:ok, state :: term()} | {:error, term()}
  @callback remote_spawn_monitor(state :: term, func :: function() | term) ::
              {:ok, {pid, reference()}} | {:error, reason :: term}
  @callback system_shutdown() :: no_return()
  @callback remote_boot(state :: term) ::
              {:ok, remote_terminator_pid :: pid(), new_state :: term} | {:error, term}
  @callback handle_info(msg :: term, state :: term) ::
              {:noreply, new_state :: term} | {:stop, term, new_state :: term}

  @optional_callbacks handle_info: 2

  def init(opts), do: impl().init(opts)

  def remote_spawn_monitor(state, func) do
    impl().remote_spawn_monitor(state, func)
  end

  def system_shutdown do
    impl().system_shutdown()
  end

  def remote_boot(state) do
    impl().remote_boot(state)
  end

  def handle_info(msg, state) do
    impl().handle_info(msg, state)
  end

  def impl, do: Application.get_env(:flame, :backend, FLAME.LocalBackend)
end
