defmodule Amrita do
  @moduledoc """
  A polite, well mannered and thoroughly upstanding testing framework for Elixir.
  """

  @doc """
  Start Amrita for a test run.

  This should be called in your test_helper.exs file.

  Supports optional config:

      # Use a custom formatter. Defaults to Progress formatter.
      Amrita.start(formatter: Amrita.Formatter.Documentation)

  """
  def start(_type, _args) do
#    IO.inspect opts
     import Supervisor.Spec
     
     children = [worker(ExUnit.Server, []), worker(ExUnit.OnExitHandler, [])]
     opts = [strategy: :one_for_one, name: ExUnit.Supervisor]
     Supervisor.start_link(children, opts)
    #
    #
    # Amrita.Engine.Start.now formatter: formatter
    # {:ok, []}
  end


   def start(options \\ []) do
     Amrita.Engine.Start.now(options)
   end

  @doc """
  Polite version of start.
  """
  def please_start(opts \\ []) do
    start(opts)
  end

  defmodule Sweet do
    @moduledoc """
    Responsible for loading Amrita within a test module.

    ## Example:
        defmodule TestsAboutSomething do
          use Amrita.Sweet
        end
    """

    @doc false
    defmacro __using__(opts \\ []) do
      async = Keyword.get(opts, :async, false)
      quote do
        if !Enum.any?(__ENV__.requires, fn(x) -> x == ExUnit.Case end) do
          use ExUnit.Case, async: unquote(async)
        end

        import ExUnit.Callbacks
        import ExUnit.Assertions
        import ExUnit.Case
        @ex_unit_case true

        use Amrita.Facts
        use Amrita.Mocks

        import Amrita.Checkers.Helper
        import Amrita.Checkers.Simple
        import Amrita.Checkers.Collections
        import Amrita.Checkers.Exceptions
        import Amrita.Checkers.Messages
        import Amrita.Syntax.Describe
      end
    end
  end

  defmodule Facts do
    @moduledoc """
    Express facts about your code.
    """

    @doc false
    defmacro __using__(_) do
      quote do
        @name_stack []
        import Amrita.Facts
      end
    end

    defp fact_name(name) do
      cond do
        is_binary(name) -> name
        is_tuple(name)  -> name
        true -> "#{name}"
      end
    end

    @doc """
    A fact is the container of your test logic.

    ## Example
        fact "about addition" do
          ...
        end

    If you are using mocks you can define them as part of your fact.

    ## Example
        fact "about mock", provided: [Flip.flop(:ok) |> true] do
          Flip.flop(:ok) |> truthy
        end

    You can optionally examine meta data passed to each fact. Useful when used
    with callbacks:

    ## Example
        setup do
          {:ok, ping: "pong"}
        end

        fact "with meta data", meta do
          meta[:pong] |> "pong"
        end
    """
    defmacro fact(description, provided \\ [], var \\ quote(do: _), contents) do
      var = case provided do
        [provided: _] -> var
        []            -> var
        _             -> provided
      end

      quote do
        deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)), unquote(var) do
          import Kernel, except: [|>: 2]
          import Amrita.Elixir.Pipeline

          unquote do
            if is_list(provided) && !Enum.empty?(provided) && match?({:provided, _}, Enum.at(provided, 0)) do
              { :provided, mocks } = Enum.at(provided, 0)
              quote do
                provided unquote(mocks) do
                  unquote(contents)
                end
              end
            else
              quote do
                unquote(contents)
              end
            end
          end
        end
      end
    end

    @doc """
    A fact without a body is a pending fact. Much like a TODO.
    It prints a reminder when the tests are run.

    ## Example
        fact "something thing I need to implement at somepoint"

    """
    defmacro fact(description) do
      quote do
        deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)) do
          Amrita.Message.pending unquote(description)
        end
      end
    end

    @doc """
    A future_fact is a pending fact. Its body is *NEVER* run.
    Instead it simply prints an reminder that it is yet to be run.

    ## Example:
        future_fact "about something that does not work yet" do
          ..
        end
    """
    defmacro future_fact(description, _ \\ quote(do: _), _) do
      quote do
        deffact Enum.join(@name_stack, "") <> unquote(fact_name(description)) do
          Amrita.Message.pending unquote(description)
        end
      end
    end

    @doc """
    facts are used to group with a name a number of fact tests.
    You can nest as many facts as you feel you need.

    ## Example
        facts "about arithmetic" do
          fact "about addition" do
            ...
          end
        end
    """
    defmacro facts(description, _ \\ quote(do: _), contents) do
      quote do
        @name_stack Enum.concat(@name_stack, [unquote(fact_name(description)) <> " - "])
        unquote(contents)
        if Enum.count(@name_stack) > 0 do
          @name_stack Enum.take(@name_stack, Enum.count(@name_stack) - 1)
        end
      end
    end

    @doc false
    defmacro deffact(message, var \\ quote(do: _), contents) do
      contents =
       case contents do
         [do: _] ->
           quote do
             unquote(contents)
             :ok
           end
         _ ->
           quote do
             try(unquote(contents))
             :ok
           end
       end

      var      = Macro.escape(var)
      contents = Macro.escape(contents, unquote: true)

      quote bind_quoted: binding do
       message = if is_binary(message) do
         :"test #{message}"
       else
         :"test_#{message}"
       end
       
       ExUnit.Case.__on_definition__(__ENV__, message)

       def unquote(message)(unquote(var))  do
         unquote(contents)
       end

       def unquote(:"__#{message}__")(), do: [file: __ENV__.file, line: __ENV__.line]
      end
    end
  end
end
