{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE BangPatterns #-}
-- | Functions to support the constant space traversal of a heap.
module GHC.Debug.Trace ( traceFromM, TraceFunctions(..) ) where

import           GHC.Debug.Types
import GHC.Debug.Client.Monad
import           GHC.Debug.Client.Query

import qualified Data.IntMap as IM
import Data.Array.BitArray.IO
import Control.Monad.Reader
import Data.IORef
import Data.Word

newtype VisitedSet = VisitedSet (IM.IntMap (IOBitArray Word16))

data TraceState = TraceState { visited :: !VisitedSet, n :: !Int }


getKeyPair :: ClosurePtr -> (Int, Word16)
getKeyPair cp =
  let BlockPtr raw_bk = applyBlockMask cp
      bk = fromIntegral raw_bk `div` 8
      offset = getBlockOffset cp `div` 8
  in (bk, fromIntegral offset)

checkVisit :: ClosurePtr -> IORef TraceState -> IO (Maybe Int, Bool)
checkVisit cp mref = do
  st <- readIORef mref
  let VisitedSet v = visited st
      num_visited = n st
      (bk, offset) = getKeyPair cp
  case IM.lookup bk v of
    Nothing -> do
      na <- newArray (0, fromIntegral (blockMask `div` 8)) False
      writeArray na offset True
      writeIORef mref (TraceState (VisitedSet (IM.insert bk na v)) (num_visited + 1))
      return (Just num_visited, False)
    Just bm -> do
      res <- readArray bm offset
      unless res (writeArray bm offset True)
      return (Nothing, res)



data TraceFunctions m =
      TraceFunctions { papTrace :: !(GenPapPayload ClosurePtr -> m DebugM ())
      , stackTrace :: !(GenStackFrames ClosurePtr -> m DebugM ())
      , closTrace :: !(ClosurePtr -> SizedClosure -> m DebugM () -> m DebugM ())
      , visitedVal :: !(ClosurePtr -> (m DebugM) ())
      , conDescTrace :: !(ConstrDesc -> m DebugM ())
      }




type C m = (MonadTrans m, Monad (m DebugM))

-- | A generic heap traversal function which will use a small amount of
-- memory linear in the heap size. Using this function with appropiate
-- accumulation functions you should be able to traverse quite big heaps in
-- not a huge amount of memory.
traceFromM :: C m => TraceFunctions m-> [ClosurePtr] -> m DebugM ()
traceFromM k cps = do
  st <- lift (unsafeLiftIO (newIORef (TraceState (VisitedSet IM.empty) 1)))
  runReaderT (mapM_ (traceClosureFromM k) cps) st
{-# INLINE traceFromM #-}
{-# INLINE traceClosureFromM #-}

traceClosureFromM :: C m
                  => TraceFunctions m
                  -> ClosurePtr
                  -> ReaderT (IORef TraceState) (m DebugM) ()
traceClosureFromM !k = go
  where
    go cp = do
      mref <- ask
      (mnum_visited, b) <- lift $ lift $ unsafeLiftIO (checkVisit cp mref)
      forM_ mnum_visited $ \num_visited ->
        lift $ lift $ when (num_visited `mod` 10_000 == 0) $ traceMsg ("Traced: " ++ show num_visited)
      if b
        then lift $ visitedVal k cp
        else do
        sc <- lift $ lift $ dereferenceClosure cp
        ReaderT $ \st -> closTrace k cp sc
         (runReaderT (() <$ quadtraverse gop gocd gos go sc) st)


    gos st = do
      st' <- lift $ lift $ dereferenceStack st
      lift $ stackTrace k st'
      () <$ traverse go st'

    gocd d = do
      cd <- lift $ lift $ dereferenceConDesc d
      lift $ conDescTrace k cd

    gop p = do
      p' <- lift $ lift $ dereferencePapPayload p
      lift $ papTrace k p'
      () <$ traverse go p'
