defmodule ESpec.Context do
  @moduledoc """
  Defines macros 'context', 'describe', and 'example_group'.
  Defines macros for 'skip' and 'focus' example groups
  """

  @aliases ~w(describe example_group)a
  @skipped ~w(xcontext xdescribe xexample_group)a
  @focused ~w(fcontext fdescribe fexample_group)a

  @doc """
  Context has description, line, and options.
  Available options are:
  - [skip: true] or [skip: "Reason"] - skips examples in the context;
  - [focus: true] - sets focus to run with `--focus ` option.
  """
  defstruct description: "", module: nil, line: nil, opts: []

  @doc "Add context with description and opts to 'example context'."
  defmacro context(description, opts, do: block) do
    quote do
      tail = @context

      head = %ESpec.Context{
        description: unquote(description),
        module: __MODULE__,
        line: __ENV__.line,
        opts: unquote(opts)
      }

      @context [head | tail]
      unquote(block)
      @context tail
    end
  end

  defmacro context(opts, do: block) when is_list(opts) do
    quote do: context("", unquote(opts), do: unquote(block))
  end

  defmacro context(description, do: block) do
    quote do
      context(unquote(description), [], do: unquote(block))
    end
  end

  defmacro context(do: block) do
    quote do: context("", [], do: unquote(block))
  end

  defmacro context(_description) do
    quote do: context("", [], do: true)
  end

  @doc "Aliases for `context`."
  Enum.each(@aliases, fn func ->
    defmacro unquote(func)(description, opts, do: block) do
      quote do: context(unquote(description), unquote(opts), do: unquote(block))
    end

    defmacro unquote(func)(description_or_opts, do: block) do
      quote do: context(unquote(description_or_opts), do: unquote(block))
    end

    defmacro unquote(func)(do: block) do
      quote do: context(do: unquote(block))
    end

    defmacro unquote(func)(_description) do
      quote do: context(_description)
    end
  end)

  @doc "Macros for skipped contexts"
  Enum.each(@skipped, fn func ->
    defmacro unquote(func)(description, opts, do: block) do
      reason = "`#{unquote(func)}`"

      quote do:
              context(
                unquote(description),
                Keyword.put(unquote(opts), :skip, unquote(reason)),
                do: unquote(block)
              )
    end

    defmacro unquote(func)(opts, do: block) when is_list(opts) do
      reason = "`#{unquote(func)}`"
      quote do: context(Keyword.put(unquote(opts), :skip, unquote(reason)), do: unquote(block))
    end

    defmacro unquote(func)(description, do: block) do
      reason = "`#{unquote(func)}`"
      quote do: context(unquote(description), [skip: unquote(reason)], do: unquote(block))
    end

    defmacro unquote(func)(do: block) do
      reason = "`#{unquote(func)}`"
      quote do: context([skip: unquote(reason)], do: unquote(block))
    end
  end)

  @doc "Macros for focused contexts"
  Enum.each(@focused, fn func ->
    defmacro unquote(func)(description, opts, do: block) do
      quote do:
              context(
                unquote(description),
                Keyword.put(unquote(opts), :focus, true),
                do: unquote(block)
              )
    end

    defmacro unquote(func)(opts, do: block) when is_list(opts) do
      quote do: context(Keyword.put(unquote(opts), :focus, true), do: unquote(block))
    end

    defmacro unquote(func)(description, do: block) do
      quote do: context(unquote(description), [focus: true], do: unquote(block))
    end

    defmacro unquote(func)(do: block) do
      quote do: context([focus: true], do: unquote(block))
    end
  end)
end
