defmodule Docker.Containers do
  @moduledoc false
  alias Docker.WaitStrategy

  def create(container_config, name \\ nil) do
    Docker.Api.create_container(container_config, name)
  end

  def run(container_config, name \\ nil) do
    case Docker.Api.create_container(container_config, name) do
      {:ok, container_id} ->
        start_and_wait(container_id, container_config)

      {:error, {:http_error, 404}} ->
        Docker.Images.pull(container_config.image)
        run(container_config)

      {:error, reason} ->
        {:error, reason}
    end
  end

  def start(container_id) do
    Docker.Api.start_container(container_id)
  end

  def stop(container_id, options \\ []) do
    Docker.Api.stop_container(container_id, options)
  end

  def info(container_id), do: Docker.Api.inspect_container(container_id)

  def mapped_port(container, container_port), do: mapped_port(container, container_port, 5)

  def mapped_port(_container, _container_port, 0), do: {:error, :missing_port}

  def mapped_port(container, container_port, retries) do
    container_port =
      container_port
      |> set_protocol_to_tcp_if_not_specified

    with {:ok, info} <- info(container) do
      port =
        info.mapped_ports
        |> Map.get(container_port)

      case port do
        nil ->
          # Port mappings are not configured immediately after the container is creating. If the
          # mapping is missing, sleeping and retrying is usually sufficient.
          :timer.sleep(10)
          mapped_port(container, container_port, retries - 1)

        port ->
          {:ok, String.to_integer(port)}
      end
    end
  end

  defp set_protocol_to_tcp_if_not_specified(port) when is_binary(port), do: port
  defp set_protocol_to_tcp_if_not_specified(port) when is_integer(port), do: "#{port}/tcp"

  defp start_and_wait(container_id, container_config) do
    :ok = Docker.Containers.start(container_id)

    if container_config.wait_strategy do
      :ok = WaitStrategy.wait_until_container_is_ready(container_config.wait_strategy, container_id)
    end

    {:ok, container_id}
  end
end
