import TypeClass

defclass Witchcraft.Monoid do
  @moduledoc ~S"""
  Monoid extends the semigroup with the concept of an "empty" or "zero" element.

  ## Type Class

  An instance of `Witchcraft.Monoid` must also implement `Witchcraft.Semigroup`,
  and define `Witchcraft.Monoid.empty/1`.

      Semigroup  [append/2]
          ↓
       Monoid    [empty/1]
  """

  alias __MODULE__

  extend Witchcraft.Semigroup, alias: true

  use Witchcraft.Internal, deps: [Witchcraft.Semigroup]

  @type t :: any()

  where do
    @doc ~S"""
    An "emptied out" or "starting position" of the passed data.

    ## Example

        iex> empty(10)
        0

        iex> empty [1, 2, 3, 4, 5]
        []

    """
    def empty(sample)
  end

  defalias zero(sample), as: :empty

  @doc """
  Check if a value is the empty element of that type.

  ## Examples

      iex> empty?([])
      true

      iex> empty?([1])
      false

  """
  @spec empty?(Monoid.t()) :: boolean
  def empty?(monoid), do: empty(monoid) == monoid

  properties do
    def left_identity(data) do
      a = generate(data)

      if is_function(a) do
        equal?(Semigroup.append(Monoid.empty(a), a).("foo"), a.("foo"))
      else
        equal?(Semigroup.append(Monoid.empty(a), a), a)
      end
    end

    def right_identity(data) do
      a = generate(data)

      if is_function(a) do
        Semigroup.append(a, Monoid.empty(a)).("foo") == a.("foo")
      else
        Semigroup.append(a, Monoid.empty(a)) == a
      end
    end
  end
end

definst Witchcraft.Monoid, for: Function do
  def empty(sample) when is_function(sample), do: &Quark.id/1
end

definst Witchcraft.Monoid, for: Integer do
  def empty(_), do: 0
end

definst Witchcraft.Monoid, for: Float do
  def empty(_), do: 0.0
end

definst Witchcraft.Monoid, for: BitString do
  def empty(_), do: ""
end

definst Witchcraft.Monoid, for: List do
  def empty(_), do: []
end

definst Witchcraft.Monoid, for: Map do
  def empty(_), do: %{}
end

definst Witchcraft.Monoid, for: Tuple do
  custom_generator(_) do
    {}
  end

  def empty(sample), do: Witchcraft.Functor.map(sample, &Witchcraft.Monoid.empty/1)
end

definst Witchcraft.Monoid, for: MapSet do
  def empty(_), do: MapSet.new()
end

definst Witchcraft.Monoid, for: Witchcraft.Unit do
  require Witchcraft.Semigroup
  def empty(_), do: %Witchcraft.Unit{}
end
