{-# LANGUAGE MagicHash, UnboxedTuples, CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}

module Control.Monad.Par.Meta.Resources.Debugging ( dbg
                                                  , dbgTaggedMsg
                                                  , dbgDelay
                                                  , dbgCharMsg
                                                  , meaningless_alloc
                                                  , taggedmsg_global_mode
                                                  , verbosity
                                                  ) where

----------------------------------------
-- For tracing events:
-- import Foreign
import Foreign.C (CString)
import GHC.Exts (traceEvent#, Ptr(Ptr))
-- import GHC.IO hiding (liftIO)
import GHC.IO (IO(IO))
----------------------------------------

import qualified Data.ByteString.Char8 as BS
import Data.IORef             (IORef, newIORef, readIORef)
import Data.Monoid            (mappend, Monoid)
import Control.Monad          (when)
import Control.Concurrent     (myThreadId, threadDelay)
import System.IO              (hFlush, stderr)
import System.IO.Unsafe       (unsafePerformIO)
import System.Environment     (getEnvironment)


-----------------------------------------------------------------------------------
-- VERBOSITY and DEBUGGING
-----------------------------------------------------------------------------------
-- This controls how much output will be printed, 0-5.
-- 5 is "debug" mode and affects other aspects of execution (see dbgDelay)

#define TMPDBG
#define EVENTLOG

-- RRN [2012.02.28] -- Eventually for performance reasons this
-- decision will probably be made statically.  For now, in the heat of
-- debugging, it is nice to be able to change it dynamically.

-- Well, to change it truly DYNAMICALLY, it would have to be an IORef.
-- For now we just allow an environment variable to override the
-- setting at load time.

{-# NOINLINE verbosity #-}
verbosity :: Int
verbosity = unsafePerformIO$ do
--              putStrLn "GETTING ENV TO READ VERBOSITY..."
	      env <- getEnvironment
--              putStrLn$ " ENV LENGTH " ++ show (length env)
              case lookup "VERBOSITY" env of
                    -- if defined and not empty, default to 1
                    Just "" -> return 1
                    Just s  -> do let n = read s
                                  when (n >= 2)$ putStrLn "(!) Responding to VERBOSITY environment variable!"
                                  return n
#ifdef DEBUG
    	  	    Nothing -> return 5
#else
                    Nothing -> do -- putStrLn "DEFAULTING VERBOSITY TO 1" 
                                  return 1
#endif


{-# NOINLINE binaryEventLog #-}
binaryEventLog :: Bool
binaryEventLog = unsafePerformIO$ do
	      env <- getEnvironment
              case lookup "EVENTLOG" env of 
                    Nothing  -> return False
                    Just ""  -> return False                    
                    Just "0" -> return False                    
                    Just _   -> return True

-- When debugging is turned on we will do extra invariant checking:
dbg :: Bool
#ifdef DEBUG
dbg = True
#else
dbg = False
#endif

whenVerbosity :: Monad m => Int -> m () -> m ()
whenVerbosity n action = when (verbosity >= n) action

-- | dbgTaggedMsg is our routine for logging debugging output:
dbgTaggedMsg :: Int -> BS.ByteString -> IO ()
-- dbgTaggedMsg :: Int -> String -> IO ()
dbgTaggedMsg = if binaryEventLog then binaryLogMsg else textLogMsg

textLogMsg :: Int -> BS.ByteString -> IO ()
textLogMsg lvl s = 
   whenVerbosity lvl $ 
      do m   <- readIORef taggedmsg_global_mode
         tid <- myThreadId
	 BS.putStrLn$ " [distmeta" +++ m +++" "+++ sho tid +++"] "+++s

(+++) :: Monoid a => a -> a -> a
(+++) = mappend

sho :: Show a => a -> BS.ByteString
sho = BS.pack . show


binaryLogMsg :: Int -> BS.ByteString -> IO ()
binaryLogMsg lvl s = do 
--   meaningless_alloc  -- This works as well as a print for preventing the inf loop [2012.03.01]!
   whenVerbosity lvl $ do 
     m <- readIORef taggedmsg_global_mode
     tid <- myThreadId
     let msg = " [distmeta"+++m+++" "+++ sho tid+++"] "+++s
     BS.useAsCString msg myTraceEvent
     return ()

myTraceEvent :: CString -> IO ()
myTraceEvent (Ptr msg) = IO $ \s -> case traceEvent# msg s of s' -> (# s', () #)


-- | `dbgCharMsg` is for printing a small tag like '.' (with no line
--   termination) which produces a different kind of visual output.
-- dbgCharMsg :: Int -> String -> String -> IO ()
dbgCharMsg :: Int -> BS.ByteString -> BS.ByteString -> IO ()
dbgCharMsg lvl tag fullmsg = 
  if binaryEventLog 
  then dbgTaggedMsg lvl fullmsg -- It doesn't make sense to event-log a single character.
  else whenVerbosity lvl $ do BS.hPutStr stderr tag; hFlush stderr


-- When debugging it is helpful to slow down certain fast paths to a human scale:
dbgDelay :: BS.ByteString -> IO ()
dbgDelay _ = 
  if   dbg
  then threadDelay (200*1000)
  else return ()

meaningless_alloc :: IO ()
meaningless_alloc = 
   case length (fibls 5) of 
     0 -> return (error "Never happen!")
     _ -> return ()
 where 
  fibls :: Int -> [Int]
  fibls n | n <= 1 = [1::Int] 
  fibls n = fibls (n-1) ++ fibls (n-2)


{-# NOINLINE taggedmsg_global_mode #-}
-- Just for debugging, tracking global node as M (master) or S (slave):
taggedmsg_global_mode :: IORef BS.ByteString
taggedmsg_global_mode = unsafePerformIO$ newIORef "_M"