defmodule Tracer.Tool.CallSeq.Event do
  @moduledoc """
  Event generated by the CallSeq tool
  """
  alias __MODULE__

  defstruct type: nil,
            depth: 0,
            pid: nil,
            mod: nil,
            fun: nil,
            arity: nil,
            message: nil,
            return_value: nil

  defimpl String.Chars, for: Event do
    def to_string(%Event{type: :enter} = event) do
      String.duplicate(" ", event.depth) <>
        "-> #{inspect event.mod}.#{event.fun}/#{event.arity} " <>
        if is_nil(event.message), do: "",
      else: List.to_string(safe_inspect(event.message, event.depth + 5))
    end
    def to_string(%Event{type: :exit} = event) do
      String.duplicate(" ", event.depth) <>
        "<- #{inspect event.mod}.#{event.fun}/#{event.arity} " <>
        if is_nil(event.return_value), do: "",
      else: List.to_string(safe_inspect(event.return_value, event.depth + 5))
    end

    # defp message_to_string(nil, _depth), do: ""
    # defp message_to_string(term, depth) when is_list(term) do
    #   term
    #   |> Enum.map(fn
    #     [key, val] -> {key, val}
    #     other -> "#{inspect other}"
    #   end)
    #   |> safe_inspect(depth)
    # end

    # borrowed from https://github.com/fishcakez/dbg
    def safe_inspect(term, depth) do
      options = IEx.configuration()
      inspect_options = Keyword.get(options, :inspect, [])
      try do
        inspect(term, inspect_options)
      else
        formatted ->
          indent(formatted, depth)
      catch
        _, _ ->
          term
          |> inspect([records: false, structs: false] ++ inspect_options)
          |> indent(depth)
      end
    end

    defp indent(formatted, depth) do
      formatted
      |> :binary.split([<<?\n>>], [:global])
      |> Enum.map(&(["\n" <> String.duplicate(" ", depth) | &1]))
    end
  end

end
