defmodule Complexity do
  defmacro with_complexity(fun) do
    quote do
      arguments =
        binding()
        |> Keyword.values()
        |> Enum.map(&inspect/1)
        |> Enum.join(", ")

      {function, _arity} = __ENV__.function

      [{:current, current} | _] = Complexity.get()
      indentation = String.duplicate("  ", current)

      start()
      IO.puts("#{indentation}#{__MODULE__}.#{function}(#{arguments}) {")
      return = unquote(fun).()
      IO.puts("#{indentation}}=> #{return}")
      finish()
      return
    end
  end

  def start() do
    [current: current, steps: steps, space: space] = get()

    new_current = current + 1

    new_complexity = [
      current: new_current,
      steps: steps + 1,
      space: if(new_current > space, do: new_current, else: space)
    ]

    Process.put(:complexity, new_complexity)
  end

  def finish() do
    case get() do
      [{:current, 1}, {:steps, steps}, {:space, space}] ->
        IO.puts(String.duplicate("-", 80))
        IO.puts("Total steps (∝ time):    #{steps}")
        IO.puts("Maximum depth (∝ space): #{space}")
        IO.puts(String.duplicate("-", 80))
        Process.delete(:complexity)

      [{:current, current} | _] = complexity ->
        new_complexity = Keyword.put(complexity, :current, current - 1)
        Process.put(:complexity, new_complexity)
    end
  end

  def get() do
    Process.get(:complexity, current: 0, steps: 0, space: 0)
  end
end

defmodule Counter do
  import Complexity

  def count_change(amount) do
    do_count_change(amount, 5)
  end

  defp do_count_change(0, _kinds_of_coins) do
    with_complexity(fn -> 1 end)
  end

  defp do_count_change(amount, _kinds_of_coins) when amount < 0 do
    with_complexity(fn -> 0 end)
  end

  defp do_count_change(_amount, 0) do
    with_complexity(fn -> 0 end)
  end

  defp do_count_change(amount, kinds_of_coins) do
    with_complexity(fn ->
      do_count_change(amount, kinds_of_coins - 1) +
        do_count_change(amount - first_denomination(kinds_of_coins), kinds_of_coins)
    end)
  end

  defp first_denomination(1), do: 1
  defp first_denomination(2), do: 5
  defp first_denomination(3), do: 10
  defp first_denomination(4), do: 25
  defp first_denomination(5), do: 50
end