lib/ex_matchers/include.ex

defmodule ExMatchers.Include do
  @moduledoc false

  import ExUnit.Assertions
  import ExMatchers.Custom

  defprotocol IncludeMatcher do
    def to_match(value, key)
    def to_match(value, key, expected_value)
    def to_not_match(value, key)
    def to_not_match(value, key, expected_value)
  end

  defimpl IncludeMatcher, for: BitString do
    def to_match(value, substring) do
      assert String.contains?(value, substring)
    end
    def to_match(value, substring, expected_value) do
      flunk "Includes not supported from #{substring} in #{value} with #{expected_value}"
    end
    def to_not_match(value, substring) do
      refute String.contains?(value, substring)
    end
    def to_not_match(value, substring, expected_value) do
      flunk "Includes not supported from #{substring} in #{value} with #{expected_value}"
    end
  end

  defimpl IncludeMatcher, for: List do
    def to_match([{_k, _v} | _t] = value, key) do
      assert Keyword.has_key?(value, key)
    end
    def to_match(list, element) do
      assert Enum.member?(list, element)
    end
    def to_match([{_k, _v} | _t] = value, keys, expected_value) when is_list(keys) do
      assert get_in(value, keys) == expected_value
    end
    def to_match([{_k, _v} | _t] = value, key, expected_value) do
      assert value[key] == expected_value
    end
    def to_match(list, element, expected_value) do
      flunk "Includes not supported from #{element} in #{list} with #{expected_value}"
    end
    def to_not_match([{_k, _v} | _t] = value, key) do
      refute Keyword.has_key?(value, key)
    end
    def to_not_match(list, element) do
      refute Enum.member?(list, element)
    end
    def to_not_match([{_k, _v} | _t] = value, keys, expected_value) when is_list(keys) do
      refute get_in(value, keys) == expected_value
    end
    def to_not_match([{_k, _v} | _t] = value, key, expected_value) do
      refute value[key] == expected_value
    end
    def to_not_match(list, element, expected_value) do
      flunk "Includes not supported from #{element} in #{list} with #{expected_value}"
    end
  end

  defimpl IncludeMatcher, for: Range do
    def to_match(range, element) do
      assert Enum.member?(range, element)
    end
    def to_match(range, element, expected_value) do
      flunk "Includes not supported from #{element} in #{range} with #{expected_value}"
    end
    def to_not_match(range, element) do
      refute Enum.member?(range, element)
    end
    def to_not_match(range, element, expected_value) do
      flunk "Includes not supported from #{element} in #{range} with #{expected_value}"
    end
  end

  defimpl IncludeMatcher, for: Tuple do
    def to_match(tuple, element) do
      assert Tuple.to_list(tuple) |> Enum.member?(element)
    end
    def to_match(tuple, element, expected_value) do
      flunk "Includes not supported from #{element} in #{tuple} with #{expected_value}"
    end
    def to_not_match(tuple, element) do
      refute Tuple.to_list(tuple) |> Enum.member?(element)
    end
    def to_not_match(tuple, element, expected_value) do
      flunk "Includes not supported from #{element} in #{tuple} with #{expected_value}"
    end
  end

  defimpl IncludeMatcher, for: Map do
    def to_match(value, key) do
      assert Map.has_key?(value, key)
    end

    def to_match(value, keys, expected_value) when is_list(keys) do
      assert get_in(value, keys) == expected_value
    end

    def to_match(value, key, expected_value) do
      assert value[key] == expected_value
    end

    def to_not_match(value, key) do
      refute Map.has_key?(value, key)
    end

    def to_not_match(value, keys, expected_value) when is_list(keys) do
      refute get_in(value, keys) == expected_value
    end

    def to_not_match(value, key, expected_value) do
      refute value[key] == expected_value
    end
  end

  defmatcher include(key), with: value, matcher: IncludeMatcher
end