---------------------------------------------------------------------
-- |
-- Module      : Debug.Tracer
-- Copyright   : (c) Raphael 'kena' Poss 2014
-- License     : BSD3
--
-- Maintainer  : kena@vodka-pomme.net
-- Stability   : experimental
-- Portability : portable
--
-- Transformers for 'Functor', 'Applicative' and 'Monad' types that add
-- tracing facilities for computation steps and applicative nesting.
---------------------------------------------------------------------

module Debug.Tracer (
       -- * General interfaces
         TracerTrans(runTracerT)
       , Tracer(label, trace, enter)
       -- * Position types
       , Pos
       , PosShort, PosRel, PosStack
       -- * Tracer transformer
       , TracerT
       -- * Utilities
       , PureTracer
       , runTracer, lift
       , MaybeTracer, IOTracer
) where

import qualified Debug.Trace

import Control.Applicative (Applicative(..))
import Control.Monad.Identity (Identity, runIdentity)
import Control.Monad.Trans (MonadTrans(..))

-------------------------------------------------------------------
-- General interfaces
--

-- |
-- Tracer transformers are Applicative transformers.
-- All resulting tracers can be evaluated to trace the evaluation
-- of the applicative effects that they encapsulate.

class TracerTrans t where

  -- |
  -- Evaluate the tracer, which forces the application
  -- and reports the progress according to the uses of
  -- 'trace', 'label' and 'enter' in the composition.
  runTracerT :: (Applicative m) => String -> t m a -> m a


-- |
-- A tracer structure internally tracks the progress of 'Applicative'
-- computations performed “within it”.
--
class (Applicative m) => Tracer m where

  -- |
  -- Emit the current progress followed by a message. The progress
  -- is emitted using the standard 'Debug.Trace.trace' function.
  trace :: String -> m ()

  -- |
  -- Label the current computation step, so that subsequent uses
  -- of 'trace' will report the relative progress since this step.
  label :: String -> m ()

  -- |
  -- Mark a computation as a “call” (nesting), so that any
  -- relative progress counters are restored when the nested
  -- computation ends.
  enter :: m a -> m a



---------------------------------------------------------------------
-- Position types


-- |
-- The class of position holders for 'TracerT' instances.  There are
-- multiple types in this class in order with different levels of
-- complexity, so that user programs can control the amount of
-- overhead involved during tracing.

class Pos p where
   -- initial position
   pinitial :: String -> p
   -- set current label
   plabel   :: String -> p -> p
   -- step: make one step forward
   pstep    :: p -> p
   -- rewind: make one step back
   prewind  :: p -> p
   -- enter a scope
   ppush    :: p -> p
   -- restore after a scope leaves. 1st argument
   -- is current (caller) position, 2nd argument
   -- is final position in callee.
   ppop     :: p -> p -> p
   -- trace: output message with position as prefix
   ptrace   :: p -> String -> a -> a

-- |
-- A lightweight position type that only records the global
-- number of steps.

data PosShort = Ps !Int

-- |
-- A position type that extends 'PosShort' by also tracking
-- the relative number of steps
-- since the beginning of the current application group.

data PosRel = Pr !Int !Int

-- |
-- A position type that extends 'PosRel' by also tracking the name
-- of labels and the stacking of application levels.
--
-- It involves more run-time overhead due to string manipulations.

data PosStack = Pst !Int String String !Int


-- The Pos instances follow.

dotrace :: String -> String -> a -> a
dotrace pref msg = Debug.Trace.trace (pref ++ ": " ++ msg)

instance Pos PosShort where
   pinitial _      = Ps 0
   plabel   _      = id
   pstep    (Ps n) = Ps (n+1)
   prewind  (Ps n) = Ps (n-1)
   ppush    (Ps n) = Ps (n+1)
   ppop     _      = id
   ptrace   (Ps n) = dotrace (show n)

instance Pos PosRel where
   pinitial _                  = Pr 0 0
   plabel   _                  = id
   pstep    (Pr n i)           = Pr (n+1) (i+1)
   prewind  (Pr n i)           = Pr (n-1) (i-1)
   ppush    (Pr n _)           = Pr (n+1) 0
   ppop     (Pr _ i) (Pr n' _) = Pr n' i
   ptrace   (Pr n i)           = dotrace ((show n) ++ " +" ++ (show i))

instance Pos PosStack where
   pinitial  w                            = Pst 0 w "" 0
   plabel    w (Pst n c _ _)              = Pst n c w 0
   pstep     (Pst n c l i)                = Pst (n+1) c l (i+1)
   prewind   (Pst n c l i)                = Pst (n-1) c l (i-1)
   ppush     (Pst n c l i)                = Pst (n+1) (c ++ " " ++ l ++ "+" ++ (show i) ++ ">") "" 0
   ppop      (Pst _ c l i) (Pst n' _ _ _) = Pst n' c l i
   ptrace    (Pst n c l i)                = dotrace ((show n) ++ " " ++ c ++ " " ++ l ++ "+" ++ (show i))


---------------------------------------------------------------------
-- Tracer transformers.

-- |
-- Equips an underlying 'Functor', 'Applicative' or 'Monad' type
-- with tracing facilities.

newtype TracerT p m a = TracerT (p -> m (a, p))

-- |
-- Provides 'fmap' with tracing.

instance (Functor m, Pos p) => Functor (TracerT p m) where
  -- fmap :: (a -> b) -> m a -> m b
  fmap f (TracerT x) = TracerT $ \l -> let next = x l
                                           trans (v, l') = (f v, pstep l')
                                       in  fmap trans next

-- |
-- Provides sequencing with tracing ('<*>', '*>' and '<*').

instance (Applicative m, Pos p) => Applicative (TracerT p m) where
  -- pure :: a -> m a
  pure x                      = TracerT $ seq x $ \l -> pure (x, pstep l)

  -- (<*>) :: f (a -> b) -> f a -> f b
  (TracerT f) <*> (TracerT x) = TracerT $ seq (seq f x) $ \l ->
        let fnext                   = f (ppush l)          -- :: m (a -> b, p)

            trans (f', l') (x',l'') = (f' x', ppop l' (pstep l''))
                                                           -- :: (a->b,p) -> ((a,p)->(b,p))

            fenc                    = fmap trans fnext     -- :: m ((a,p)->(b,p))

            xnext                   = x (ppush l)          -- :: m (a, p)
        in  fenc <*> xnext

-- |
-- Provides do-notation with tracing.

instance (Monad m, Pos p) => Monad (TracerT p m) where
  return x          = TracerT $ seq x $ \p -> return (x, (pstep p))
  (TracerT x) >>= f = TracerT $         \p -> do
                                              (v, p') <- x (pstep p)
                                              (TracerT x') <- return $ f v
                                              x'  (pstep p')




-- |
-- Provides 'lift', to trace actions from the underlying monad.

instance (Pos p) => MonadTrans (TracerT p) where
  lift x = TracerT $ seq x $ \p -> do
                                    v <- x
                                    return (v, pstep p)

{-
  -- FIXME: is this right?
  -- tmap :: (forall a. m a -> n a) -> (forall b. n b -> m b) -> t m c -> t n c
  tmap f _ x = TracerT $ \p -> do
                                 (v, p') <- x p
                                 (TracerT x') <- return $ f v
                                 x' (pstep p')
-}

-- |
-- Provides 'label', 'trace' and 'enter'.
instance (Pos p, Applicative m) => Tracer (TracerT p m) where
  label lbl = TracerT $ \p -> pure ( (), plabel lbl p)
  trace msg = TracerT $ \p -> ptrace p msg $ pure ( (), prewind p )
  enter x   = (pure id) <*> x

-- |
-- Provides 'runTracerT'.
instance (Pos p) => TracerTrans (TracerT p) where
  runTracerT w (TracerT x) = fmap fst (x (pinitial w))


-- |
-- Simple tracer for pure computations.
--
-- For this tracer, 'runTracerT' has the following type:
--
-- > runTracerT :: (Pos p) => String -> PureTracer p a -> Identity a
type PureTracer p a = TracerT p Identity a

-- |
-- Evaluates a traced pure computation encapsulated in a 'PureTracer',
-- emit its trace and
-- return the computation's result.
runTracer :: (Pos p) => String -> PureTracer p a -> a
runTracer w = runIdentity . (runTracerT w)

-- |
-- Simple tracer for 'Maybe' computations.
--
-- For this tracer, 'runTracerT' has the following type:
--
-- > runTracerT :: (Pos p) => String -> MaybeTracer p a -> Maybe a
type MaybeTracer p a = TracerT p Maybe a

-- |
-- Simple tracer for 'IO' computations.
--
-- For this tracer, 'runTracerT' has the following type:
--
-- > runTracerT :: (Pos p) => String -> IOTracer p a -> IO a
type IOTracer p a = TracerT p IO a
