{-# LANGUAGE GeneralizedNewtypeDeriving, FunctionalDependencies, UndecidableInstances, StandaloneDeriving, ExistentialQuantification #-}
-- |
-- Module      : Control.Monad.CTrace
-- Copyright   : (c) Taku Terao, 2017 
-- License     : BSD3 
-- Maintainer  : autotaker@gmail.com 
-- Stability   : experimental 
-- Portability : GHC
-- Contextual tracing monad transformer. transformers-compatible.
module Control.Monad.Trans.CTrace(TracerT, mapTracerT, runTracerT, zoom, update, noTracerT, ioTracerT) where

import Control.Monad.Cont.Class
import Control.Monad.Reader
import Control.Monad.Writer.Class
import Control.Monad.Error.Class
import Control.Monad.State.Class
import Control.Monad.RWS.Class
import Lens.Micro
import Data.IORef

-- | Contextual tracing monad transformer type.
--   Tracing context c can be modified through this monad.
newtype TracerT c m a = TracerT (ReaderT ((c -> c) -> IO ()) m a)
   deriving(Functor,Monad,Applicative, MonadIO, MonadFix)


-- | Perform modification on the tracing context
update :: MonadIO m => (c -> c) -> TracerT c m ()
update f = TracerT $ ReaderT $ \tracer -> liftIO (tracer f)
{-# INLINE update #-}

-- | Transform the base monad
mapTracerT :: (m a -> n b) -> TracerT c m a -> TracerT c n b
mapTracerT f (TracerT v) = TracerT $ mapReaderT f v
{-# INLINE mapTracerT #-}

-- | Change the tracing context. 
zoom :: ASetter' c c' -> TracerT c' m a -> TracerT c m a
zoom l (TracerT m) = TracerT $ ReaderT $ \action ->
    runReaderT m (\updateFunc -> action (over l updateFunc))
{-# INLINE zoom #-}

instance MonadTrans (TracerT c) where
    lift = TracerT . lift
    {-# INLINE lift #-}

-- | Run the tracer monad with the specified update action.
runTracerT :: ((c -> c) -> IO ()) -> TracerT c m a -> m a
runTracerT action (TracerT m) = runReaderT m action
{-# INLINE runTracerT #-}

-- | Run the tracer monad without tracing.
noTracerT :: Monad m => TracerT c m a -> m a
noTracerT = runTracerT (const (return ()))
{-# INLINE noTracerT #-}

-- | Run the tracer monad with update action implemented by 'IORef'
ioTracerT :: MonadIO m => c -> TracerT c m a -> m (a,c)
ioTracerT init m = do
    r <- liftIO $ newIORef init
    v <- runTracerT (modifyIORef' r) m
    c <- liftIO $ readIORef r
    return (v,c)
{-# INLINE ioTracerT #-}

instance MonadReader r m => MonadReader r (TracerT c m) where
    ask = lift ask
    reader = lift . reader
    local f (TracerT m) = TracerT (ReaderT $ local f . runReaderT m)

deriving instance MonadWriter w m => MonadWriter w (TracerT c m) 
deriving instance MonadError e m => MonadError e (TracerT c m) 
deriving instance MonadState s m => MonadState s (TracerT c m) 
deriving instance MonadRWS r w s m => MonadRWS r w s (TracerT c m)
deriving instance MonadCont m => MonadCont (TracerT c m)