defmodule Paddle.Attributes do
  @moduledoc ~S"""
  Module used internally by Paddle to manipulate / convert LDAP attributes.
  """

  @spec get(Paddle.Class.t) :: {:ok, map} | {:error, :missing_required_attributes, [atom]}

  @doc ~S"""
  Get the given and the generated attributes of a given class object.

  Examples:

      iex> Paddle.Attributes.get(%MyApp.PosixGroup{})
      {:error, :missing_required_attributes, [:cn]}

      iex> Paddle.Attributes.get(%MyApp.PosixGroup{cn: "myGroup"})
      {:ok,
       %{cn: "myGroup", description: nil, gidNumber: 4, memberUid: nil,
         objectClass: "posixGroup", userPassword: nil}}
  """
  def get(class_object) do
    given_attributes = Map.from_struct(class_object)
    generated_attributes = generate_defaults(class_object)

    attributes = Map.merge(generated_attributes, given_attributes,
                           &choose_value/3)

    required_attributes = Paddle.Class.required_attributes(class_object)

    missing_req_attributes = get_missing_req(attributes, required_attributes)
    case missing_req_attributes do
      [] -> {:ok, attributes}
      _ -> {:error, :missing_required_attributes, missing_req_attributes}
    end
  end

  defp generate_defaults(class_object) do
    for {attribute, func} <- Paddle.Class.generators(class_object), into: %{} do
      {attribute, func.(class_object)}
    end
    |> Map.merge(%{objectClass: Paddle.Class.object_classes(class_object)})
  end

  defp choose_value(_key, generated_value, given_value) do
    case given_value do
      nil -> generated_value
      _ -> given_value
    end
  end

  defp get_missing_req(attributes, required_attributes) do
    attributes
    |> Enum.filter_map(fn {attribute, value} ->
      attribute in required_attributes and value == nil
    end,
    fn {attribute, _value} -> attribute end)
  end

end
