defmodule Docker.Container do
  @moduledoc """
  Specification for a _container_.
  """
  alias Docker.{BindMount, Container}

  @enforce_keys [:image]

  defstruct [
    :image,
    cmd: nil,
    environment: %{},
    exposed_ports: [],
    wait_strategy: nil,
    privileged: false,
    bind_mounts: [],
    labels: %{},
    auto_remove: true
  ]

  @doc """
  Creates a _container_ from the given image.

  ## Options

  - `bind_mounts` sets the files or the directories on the _host machine_ to mount into the _container_.
  - `cmd` sets the command to run in the container
  - `environment` sets the environment variables for the container
  - `exposed_ports` sets the ports to expose to the host
  - `privileged` indicates whether the container should run in privileged mode (default false)
  - `wait_strategy` sets the strategy to adopt to determine whether the container is ready for use
  - `auto_remove` whether to automatically remove the container after it's stopped (default true)

  """
  def new(image, opts \\ []) do
    exposed_ports =
      Keyword.get(opts, :exposed_ports, [])
      |> Enum.map(&set_protocol_to_tcp_if_not_specified/1)

    %Container{
      image: image,
      bind_mounts: opts[:bind_mounts] || [],
      cmd: opts[:cmd],
      environment: opts[:environment] || %{},
      exposed_ports: exposed_ports,
      privileged: opts[:privileged] || false,
      wait_strategy: opts[:wait_strategy],
      auto_remove: opts[:auto_remove] || true
    }
  end

  @doc """
  Sets an _environment variable_ to the _container_.
  """
  def with_environment(config, key, value) do
    %Container{config | environment: Map.put(config.environment, key, value)}
  end

  @doc """
  Adds a _port_ to be exposed on the _container_.
  """
  def with_exposed_port(config, port) do
    %Container{config | exposed_ports: [port | config.exposed_ports]}
  end

  @doc """
  Sets a file or the directory on the _host machine_ to be mounted into a _container_.
  """
  def with_bind_mount(config, host_src, container_dest, options \\ "ro") do
    new_bind_mount = %BindMount{host_src: host_src, container_dest: container_dest, options: options}
    %Container{config | bind_mounts: [new_bind_mount | config.bind_mounts]}
  end

  @doc """
  Sets a label to apply to the container object in docker.
  """
  def with_label(config, key, value) do
    %Container{config | labels: Map.put(config.labels, key, value)}
  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"
end
