defmodule Mailman do
  @moduledoc """
  Provides functions to send email.
  """

  defprotocol Adapter do
    @moduledoc """
    Protocol for implementing different medium of emails delivery.
    """
    def deliver(context, email, message)
  end

  defprotocol Composer do
    @moduledoc """
    Protocol for implementing different template systems for compiling email bodies.
    """
    def compile_part(config, mode, email)
  end

  @doc """
  Delivers given email to all addresses and returns a list of Tasks.
  """
  def deliver(email, context, :send_cc_and_bcc, extra_headers) do
    bcc_list = email.bcc
    cleaned_email = %Mailman.Email{email | bcc: []}
    config = Mailman.Context.get_config(context)
    message = Mailman.Render.render(cleaned_email, context.composer, extra_headers)

    to_task = [Adapter.deliver(config, email, message)]

    cc_tasks = email.cc |> Enum.map(fn(address) ->
      cc_envelope = %Mailman.Email{email | to: [address]}
      Adapter.deliver(config, cc_envelope, message)
    end)

    bcc_tasks = bcc_list |> Enum.map(fn(address) ->
      bcc_envelope = %Mailman.Email{email | to: [address]}
      bcc_message = %Mailman.Email{email | bcc: [address]}
      message = Mailman.Render.render(bcc_message, context.composer)
      Adapter.deliver(config, bcc_envelope, message)
    end)

    to_task ++ cc_tasks ++ bcc_tasks
  end

  def deliver(email, context, :send_cc_and_bcc) do
    deliver(email, context, :send_cc_and_bcc, [])
  end

  @doc """
  Delivers given email and returns a Task.
  """
  def deliver(email, context, extra_headers) do
    message = Mailman.Render.render(email, context.composer, extra_headers)
    Adapter.deliver(Mailman.Context.get_config(context), email, message)
  end

  def deliver(email, context) do
    deliver(email, context, [])
  end
end
