lib/utils.ex

defmodule Matcher.Utils do
  def matched?({:ok, _}), do: true
  def matched?(_), do: false

  def is_enumerable(value) do
    Enumerable.impl_for(value) != nil
  end

  def compare_maps(expected, actual, allow_unexpected?: allow_unexpected?)
      when is_map(expected) and is_map(actual) do
    expected_results =
      Enum.map(expected, fn {key, matcher} ->
        find_value(key, matcher, actual)
      end)

    unexpected = Map.keys(actual) -- Map.keys(expected)

    if Enum.all?(expected_results, fn {_, _, _, match} ->
         matched?(match)
       end) and (Enum.empty?(unexpected) or allow_unexpected?) do
      {:ok, nil}
    else
      {:error, %{}}
    end
  end

  def compare_maps(e, a, _),
    do: {:error, %{error: "expected #{inspect(e)}, but got #{inspect(a)}"}}

  defp find_value(key, matcher, actual_map) do
    actual_value = Map.get(actual_map, key)
    match = Matcher.match(matcher, actual_value)
    {key, matcher, actual_value, match}
  end

  def permutations([]), do: [[]]

  def permutations(list),
    do: for(elem <- list, rest <- permutations(list -- [elem]), do: [elem | rest])

  def compare_lists([], []), do: {:ok, %{}}
  def compare_lists([_ | _], []), do: {:error, %{}}
  def compare_lists([], [_ | _]), do: {:error, %{}}

  def compare_lists([eh | et], [ah | at]) do
    case Matcher.match(eh, ah) do
      {:ok, _} -> Matcher.match(et, at)
      error -> error
    end
  end

  def compare_lists(e, a), do: {:error, %{error: "expected #{inspect(e)}, but got #{inspect(a)}"}}
end