 defmodule Protocol.DSL do
   @moduledoc false
 
+  # We need to use :lists because Enum is not available yet
+  require :lists, as: L
+
   def args_and_body(module, name, arity) do
     # Generate arguments according the arity. The arguments
     # are named xa, xb and so forth. We cannot use string

        unquote(block)

        # Define callbacks and meta information
        { conversions, fallback, any } = Protocol.conversions_for(__MODULE__, @only, @except)
        Protocol.impl_for(conversions, fallback, any, __ENV__)
        Protocol.meta(@functions, conversions, fallback, any, __ENV__)
      end
    end
  end


  # Defines meta information about the protocol and internal callbacks.
  @doc false
  def meta(functions, conversions, fallback, returns_nil, env) do
    any = L.keyfind(Any, 1, conversions) != false

    meta = quote do
      unless Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
        @type t :: unquote(generate_type(conversions, any))
      end

      def __protocol__(:name),      do: __MODULE__
      def __protocol__(:functions), do: unquote(:lists.sort(functions))
    end

    impl_for = if L.keyfind(Record, 1, conversions) do
      quote do
        def __impl_for__(arg) do
          case __raw_impl__(arg) do
            __MODULE__.Record ->
              target = Module.concat(__MODULE__, :erlang.element(1, arg))
              try do
                target.__impl__
                target
              catch
                :error, :undef, [[{ ^target, :__impl__, [], _ }|_]|_] ->
                  unquote(fallback)
              end
            other ->
              other
          end
        end
      end
    else
      quote do
        def __impl_for__(arg), do: __raw_impl__(arg)
      end
    end

    impl_bang = if returns_nil do
      quote do
        def __impl_for__!(arg) do
          __impl_for__(arg) || raise(Protocol.UndefinedError, protocol: __MODULE__, structure: arg)
        end
      end
    else
      quote do
        def __impl_for__!(arg), do: __impl_for__(arg)
      end
    end

    Module.eval_quoted env, [meta, impl_for, impl_bang]
  end

  # Returns the default conversions according to the given
