module Control.Effect.Trace
  ( -- * Effects
    Trace(..)

    -- * Actions
  , trace
  , traceShow

    -- * Interpretations
  , runTraceList

  , runTraceListIO

  , runTracePrinting
  , runTraceToHandle

  , ignoreTrace

  , traceIntoTell

    -- * Simple variants of interprations
  , runTraceListIOSimple
  , runTraceToHandleSimple

    -- * Threading constraints
  , WriterThreads

    -- * Carriers
  , TraceListC
  , TracePrintingC
  , IgnoreTraceC
  , TraceIntoTellC
  ) where

import Data.IORef

import Control.Effect
import Control.Effect.Writer

import System.IO

-- For coercion purposes
import Control.Effect.Internal.Utils
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Compose
import Control.Effect.Carrier.Internal.Intro
import Control.Monad.Trans.Identity


-- | An effect for debugging by printing/logging strings.
data Trace m a where
  Trace :: String -> Trace m ()

-- | Log the provided string
trace :: Eff Trace m => String -> m ()
trace = send . Trace
{-# INLINE trace #-}

-- | 'show' the provided item and log it.
traceShow :: (Show a, Eff Trace m) => a -> m ()
traceShow = trace . show
{-# INLINE traceShow #-}

type TraceListC = CompositionC
 '[ TraceIntoTellC
  , TellListC String
  ]

-- | Run a 'Trace' effect purely by accumulating all 'trace'd strings
-- into a list.
runTraceList :: forall m a p
              . ( Threaders '[WriterThreads] m p
                , Carrier m
                )
             => TraceListC m a
             -> m ([String], a)
runTraceList =
     runTellList
  .# traceIntoTell
  .# runComposition
{-# INLINE runTraceList #-}

data TracePrintingH

instance Eff (Embed IO) m
      => Handler TracePrintingH Trace m where
  effHandler (Trace str) = embed $ hPutStrLn stderr str
  {-# INLINEABLE effHandler #-}

type TracePrintingC = InterpretC TracePrintingH Trace

-- | Run a 'Trace' effect by printing each 'trace'd string
-- to stderr.
runTracePrinting :: Eff (Embed IO) m
                 => TracePrintingC m a
                 -> m a
runTracePrinting = interpretViaHandler
{-# INLINE runTracePrinting #-}

-- | Run 'Trace' effect by providing each 'trace'd string
-- to the provided 'Handle'.
--
-- This has a higher-rank type, as it makes use of 'InterpretReifiedC'.
-- __This makes 'runTraceToHandle' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower 'runTraceToHandleSimple',
-- which doesn't have a higher-rank type.
runTraceToHandle :: Eff (Embed IO) m
                 => Handle
                 -> InterpretReifiedC Trace m a
                 -> m a
runTraceToHandle hdl = interpret $ \case
  Trace str -> embed $ hPutStrLn hdl str
{-# INLINE runTraceToHandle #-}

-- | Run 'Trace' effect by providing each 'trace'd string
-- to the provided 'Handle'.
--
-- This is a less performant version of 'runTraceToHandle' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
runTraceToHandleSimple :: forall m a p
                        . ( Eff (Embed IO) m
                          , Threaders '[ReaderThreads] m p
                          )
                       => Handle
                       -> InterpretSimpleC Trace m a
                       -> m a
runTraceToHandleSimple hdl = interpretSimple $ \case
  Trace str -> embed $ hPutStrLn hdl str
{-# INLINE runTraceToHandleSimple #-}

-- | Run a 'Trace' effect by accumulating all 'trace'd strings
-- into a list using atomic operations in IO.
--
-- This has a higher-rank type, as it makes use of 'InterpretReifiedC'.
-- __This makes 'runTraceListIO' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower 'runTraceListIOSimple',
-- which doesn't have a higher-rank type.
runTraceListIO :: Eff (Embed IO) m
               => InterpretReifiedC Trace m a
               -> m ([String], a)
runTraceListIO m = do
  ref <- embed (newIORef [])
  a   <- (`interpret` m) $ \case
    Trace o -> embed (atomicModifyIORef' ref (\s -> (o:s, ())))
  s   <- reverse <$> embed (readIORef ref)
  return (s, a)
{-# INLINE runTraceListIO #-}


-- | Run a 'Trace' effect by accumulating all 'trace'd strings
-- into a list using atomic operations in IO.
--
-- This is a less performant version of 'runTraceListIOSimple' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
runTraceListIOSimple :: forall m a p
                      . ( Eff (Embed IO) m
                        , Threaders '[ReaderThreads] m p
                        )
                     => InterpretSimpleC Trace m a
                     -> m ([String], a)
runTraceListIOSimple m = do
  ref <- embed (newIORef [])
  a   <- (`interpretSimple` m) $ \case
    Trace o -> embed (atomicModifyIORef' ref (\s -> (o:s, ())))
  s   <- reverse <$> embed (readIORef ref)
  return (s, a)
{-# INLINE runTraceListIOSimple #-}

data IgnoreTraceH

instance Carrier m
      => Handler IgnoreTraceH Trace m where
  effHandler (Trace _) = pure ()
  {-# INLINEABLE effHandler #-}

type IgnoreTraceC = InterpretC IgnoreTraceH Trace

-- | Run a 'Trace' effect by ignoring it, doing no logging at all.
ignoreTrace :: Carrier m
            => IgnoreTraceC m a
            -> m a
ignoreTrace = interpretViaHandler
{-# INLINE ignoreTrace #-}

data TraceToTellH

instance Eff (Tell String) m
      => Handler TraceToTellH Trace m where
  effHandler (Trace str) = tell str
  {-# INLINEABLE effHandler #-}

type TraceIntoTellC = ReinterpretC TraceToTellH Trace '[Tell String]

-- | Rewrite a 'Trace' effect into a @'Tell' String@ effect on top of the
-- effect stack.
traceIntoTell :: HeadEff (Tell String) m
              => TraceIntoTellC m a
              -> m a
traceIntoTell = reinterpretViaHandler
{-# INLINE traceIntoTell #-}