lib/kevo.ex

defmodule Kevo do
  @moduledoc """
  Top-level interface and supervisor for Kevo's API and websocket connections.
  """

  use Supervisor

  require Logger

  ## Initialization

  def child_spec(opts) do
    %{
      id: name(opts),
      start: {__MODULE__, :start_link, [opts]}
    }
  end

  @doc """
  Starts an instance of kevo_ex.

  ## Configuration
  * `:name` - Alias of the top-level supervisor. Can be provided if you intend to run multiple instances of `kevo_ex` in your app. Defaults to `Kevo`.

  * `:username` - Your Kevo username (required).

  * `:password` - Your Kevo password (required).

  * `:ws_callback_module` - Websocket callback module. Defaults to `nil`.
  """
  @spec start_link(opts :: keyword()) :: Supervisor.on_start()
  def start_link(opts \\ []) do
    Logger.add_translator({Kevo.StateMachineTranslator, :translate})

    name = name(opts)

    config = %{
      username: username!(opts),
      password: password!(opts),
      api_name: api_name(name),
      socket_name: socket_name(name),
      ws_callback_module: ws_callback_module(opts)
    }

    Supervisor.start_link(__MODULE__, config, name: name)
  end

  @impl true
  def init(config) do
    %{
      username: username,
      password: password,
      api_name: api_name,
      socket_name: socket_name,
      ws_callback_module: ws_callback_module
    } = config

    api =
      {Kevo.Api.Client, [username: username, password: password, name: api_name]}

    socket =
      case ws_callback_module do
        nil ->
          []

        module ->
          [{Kevo.Socket, [callback_module: module, name: socket_name, api_name: api_name]}]
      end

    Supervisor.init([api | socket], strategy: :one_for_one)
  end

  defp name(opts) do
    Keyword.get(opts, :name, Kevo)
  end

  defp api_name(name), do: :"#{name}.Api.Client"
  defp socket_name(name), do: :"#{name}.Socket"

  defp username!(opts) do
    Keyword.get(opts, :username) || raise(ArgumentError, "must supply a username")
  end

  defp password!(opts) do
    Keyword.get(opts, :password) || raise(ArgumentError, "must supply a password")
  end

  defp ws_callback_module(opts) do
    Keyword.get(opts, :ws_callback_module)
  end

  ## Developer API

  @doc """
  Retrieves all locks visible to the logged in user.
  """
  @spec get_locks(atom()) :: {:ok, list(map())} | {:error, Kevo.ApiError.t()}
  defdelegate get_locks(name \\ Kevo), to: Kevo.Api

  @doc """
  Retrieves the given lock's state.
  """
  @spec get_lock(String.t(), atom()) ::
          {:ok, map()} | {:error, Kevo.ApiError.t()}
  defdelegate get_lock(lock_id, name \\ Kevo), to: Kevo.Api

  @doc """
  Locks the given lock.
  """
  @spec lock(String.t(), atom()) :: :ok | {:error, Kevo.ApiError.t()}
  defdelegate lock(lock_id, name \\ Kevo), to: Kevo.Api

  @doc """
  Unlocks the given lock.
  """
  @spec unlock(String.t(), atom()) :: :ok | {:error, Kevo.ApiError.t()}
  defdelegate unlock(lock_id, name \\ Kevo), to: Kevo.Api

  @doc """
  Gets the provided lock's event history. Follows the frontend's paging behavior.
  """
  @spec get_events(String.t(), integer(), integer(), atom()) ::
          {:ok, list(map())} | {:error, Kevo.ApiError.t()}
  defdelegate get_events(lock_id, page \\ 1, page_size \\ 10, name \\ Kevo), to: Kevo.Api
end