/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sebastian Ullrich
-/
prelude
import init.lean.format init.data.rbmap init.lean.position init.lean.name init.lean.options

universe u

namespace lean

inductive message
| from_format (fmt : format)

instance : has_coe format message :=
⟨message.from_format⟩

inductive trace
| mk (msg : message) (subtraces : list trace)

def trace.pp : trace → format
| (trace.mk (message.from_format fmt) subtraces) :=
fmt ++ format.nest 2 (format.join $ subtraces.map (λ t, format.line ++ t.pp))

namespace trace

def trace_map := rbmap position trace (<)

structure trace_state :=
(opts : options)
(roots : trace_map)
(cur_pos : option position)
(cur_traces : list trace)

def trace_t (m : Type → Type u) := state_t trace_state m
local attribute [reducible] trace_t

instance (m) [monad m] : monad (trace_t m) := infer_instance

class monad_tracer (m : Type → Type u) :=
(trace_root {α} : position → name → message → thunk (m α) → m α)
(trace_ctx {α} : name → message → thunk (m α) → m α)

export monad_tracer (trace_root trace_ctx)

def trace {m} [monad m] [monad_tracer m] (cls : name) (msg : message) : m unit :=
trace_ctx cls msg (pure () : m unit)

instance (m) [monad m] : monad_tracer (trace_t m) :=
{ trace_root := λ α pos cls msg ctx, do {
    st ← get,
    if st.opts.get_bool cls = some tt then do {
      modify $ λ st, {cur_pos := pos, cur_traces := [], ..st},
      a ← ctx.get,
      modify $ λ (st : trace_state), {roots := st.roots.insert pos ⟨msg, st.cur_traces⟩, ..st},
      pure a
    } else ctx.get
  },
  trace_ctx := λ α cls msg ctx, do {
    st ← get,
    -- tracing enabled?
    some _ ← pure st.cur_pos | ctx.get,
    -- trace class enabled?
    if st.opts.get_bool cls = some tt then do {
      put {cur_traces := [], ..st},
      a ← ctx.get,
      modify $ λ (st' : trace_state), {cur_traces := st.cur_traces ++ [⟨msg, st'.cur_traces⟩], ..st'},
      pure a
    } else
      -- disable tracing inside 'ctx'
      adapt_state'
        (λ _, {cur_pos := none, ..st})
        (λ st', {cur_pos := st.cur_pos, ..st'})
        ctx.get
  }
}

unsafe def trace_t.run {m α} [monad m] (opts : options) (x : trace_t m α) : m (α × trace_map) :=
do (a, st) ← state_t.run x {opts := opts, roots := mk_rbmap _ _ _, cur_pos := none, cur_traces := []},
   pure (a, st.roots)

end trace
end lean
