lib/x32_remote.ex

defmodule X32Remote do
  @default_port 10023

  @moduledoc """
  The X32Remote application.

  By default, on startup, this will run `X32Remote.Supervisor.start_link/1` to
  start an `X32Remote.Session` subscribed to an `ExOSC.Client`.  To disable
  this behaviour, set the `start` configuration property to `false` in
  `config/config.exs`:

      config :x32_remote, start: false

  ## Mixer IP address

  To find the mixer, you will need to specify an IP address.  (You may also
  need to specify a port if your device does not use the default of
  `#{@default_port}`.)

  If you know the IP address of your X32 mixer at compile time, you can set it
  in your `config/config.exs`.  Alternatively, if you need to perform some
  lookup at runtime in order to find your mixer, you can set it in your
  `config/runtime.exs`.  Either way, the syntax is the same:

      # port is optional:
      config :x32_remote, ip: {192, 168, 2, 3}, port: 10023
      # this also works:
      config :x32_remote, ip: "192.168.2.3"

  You can also set the `X32R_IP` environment variable:

      export X32R_IP=192.168.2.3
      # can also optionally specify port:
      export X32R_PORT=10023
      # now run your program as normal

  If you do not specify an IP address, and do not disable the automatic startup
  behaviour, then an error will be raised on application start.
  """

  use Application
  require Logger

  @spec start(Application.start_type(), term) ::
          {:ok, pid()} | {:ok, pid(), Application.state()} | {:error, reason :: term()}
  @impl true
  def start(_type, args) do
    if Application.get_env(:x32_remote, :start, true) do
      ip = get_ip(args)
      port = get_port(args)

      Logger.info("X32Remote starting, using mixer at #{format_ip(ip)}:#{port}.")
      X32Remote.Supervisor.start_link(ip: ip, port: port)
    else
      Supervisor.start_link([], strategy: :one_for_one)
    end
  end

  defp get_ip(args) do
    cond do
      from_args = Keyword.get(args, :ip) ->
        from_args |> to_ip_tuple()

      from_sys_env = System.get_env("X32R_IP") ->
        from_sys_env |> to_ip_tuple()

      from_app_env = Application.get_env(:x32_remote, :ip) ->
        from_app_env |> to_ip_tuple()

      true ->
        raise "x32_remote: Must specify `:ip` in X32Remote.start/2 args, `:ip` in app environment, or via `X32R_IP` environment variable"
    end
  end

  defp get_port(args) do
    cond do
      from_args = Keyword.get(args, :port) -> from_args
      from_sys_env = System.get_env("X32R_PORT") -> from_sys_env |> String.to_integer()
      from_app_env = Application.get_env(:x32_remote, :port) -> from_app_env
      true -> @default_port
    end
  end

  defp to_ip_tuple(ip) when is_tuple(ip), do: ip

  defp to_ip_tuple(ip) when is_binary(ip) do
    {:ok, ip} = ip |> String.to_charlist() |> :inet.parse_address()
    ip
  end

  defp format_ip({a, b, c, d}), do: "#{a}.#{b}.#{c}.#{d}"

  defp format_ip({_, _, _, _, _, _, _, _} = ip6) do
    ip6
    |> Tuple.to_list()
    |> Enum.map(&String.to_integer(&1, 16))
    |> Enum.join(":")
  end
end