{-# LANGUAGE CPP, TemplateHaskell, NamedFieldPuns #-}
-- | Blocking operations such as
--
-- > readMVar v
--
-- may throw an exception such as
--
-- > thread blocked indefinitely in an MVar operation
--
-- Unfortunately, this exception does not give any information of _where_ in
-- the code we are blocked indefinitely. Compiling with profiling info and
-- running with +RTC -xc can address this to some extent, but (1) it requires
-- that all profiling libraries are installed and (2) when we are running
-- multithreaded code the resulting stack trace is often difficult to read
-- (and still does not include line numbers). With this module you can replace
-- the above code with
--
-- > $readMVar v
--
-- and the exception that will be thrown is
--
-- > YourModule:lineNumber: thread blocked indefinitely in an MVar operation
--
-- which is a lot more informative. When the CPP flag DEBUGGING is turned off
-- then @$readMVar@ just turns into @readMVar@.
--
-- NOTE: The type of the exception changes when using DEBUGGING mode -- in order
-- to be able to add the line number, all exceptions are turned into
-- IOExceptions.
module IdeSession.Util.BlockingOps (
    -- * Generic debugging utilities
    lineNumber
  , traceOnException
  , mapExceptionIO
  , mapExceptionShow
    -- * Blocking MVar ops
  , putMVar
  , takeMVar
  , modifyMVar
  , modifyMVar_
  , withMVar
  , readMVar
  , swapMVar
    -- * Same for strict MVars
  , putStrictMVar
  , takeStrictMVar
  , modifyStrictMVar
  , modifyStrictMVar_
  , withStrictMVar
  , readStrictMVar
  , swapStrictMVar
    -- * Blocking Chan ops
  , readChan
    -- * Blocking Async ops
  , wait
  , waitCatch
  , waitAny
  , waitAnyCatchCancel
  ) where

import Language.Haskell.TH
import qualified Control.Concurrent as C
import qualified Control.Concurrent.Async as Async
import System.IO (hPutStrLn, stderr)
import qualified Control.Exception as Ex

import qualified IdeSession.Strict.MVar as StrictMVar

lineNumber :: ExpQ
lineNumber = do
  Loc{loc_module, loc_start=(line, _)} <- location
  [| loc_module ++ ":" ++ show (line :: Int) |]

mapExceptionIO :: (Ex.Exception e1, Ex.Exception e2)
               => (e1 -> e2) -> IO a -> IO a
mapExceptionIO f io = Ex.catch io (Ex.throwIO . f)

mapExceptionShow :: (String -> String) -> IO a -> IO a
mapExceptionShow f = mapExceptionIO (userError . f . showSomeException)
  where
    showSomeException :: Ex.SomeException -> String
    showSomeException = show

traceOnException :: String -> IO a -> IO a
traceOnException str io = Ex.catch io $ \e -> do
  hPutStrLn stderr (str ++ ": " ++ show e)
  Ex.throwIO (e :: Ex.SomeException)

#define DEBUGGING 0

#if DEBUGGING == 1

rethrowWithLineNumber1 :: ExpQ -> ExpQ
rethrowWithLineNumber1 expr =
  [| \arg1 -> mapExceptionShow (\e -> $lineNumber ++ ": " ++ e)
                               ($expr arg1)
   |]

rethrowWithLineNumber2 :: ExpQ -> ExpQ
rethrowWithLineNumber2 expr =
  [| \arg1 arg2 -> mapExceptionShow (\e -> $lineNumber ++ ": " ++ e)
                                    ($expr arg1 arg2)
   |]

{-------------------------------------------------------------------------------
  MVar
-------------------------------------------------------------------------------}

takeMVar :: ExpQ
takeMVar = rethrowWithLineNumber1 [| C.takeMVar |]

putMVar :: ExpQ
putMVar = rethrowWithLineNumber2 [| C.putMVar |]

readMVar :: ExpQ
readMVar = rethrowWithLineNumber1 [| C.readMVar |]

modifyMVar :: ExpQ
modifyMVar = rethrowWithLineNumber2 [| C.modifyMVar |]

modifyMVar_ :: ExpQ
modifyMVar_ = rethrowWithLineNumber2 [| C.modifyMVar_ |]

withMVar :: ExpQ
withMVar = rethrowWithLineNumber2 [| C.withMVar |]

swapMVar :: ExpQ
swapMVar = rethrowWithLineNumber2 [| C.swapMVar |]

{-------------------------------------------------------------------------------
  StrictMVar
-------------------------------------------------------------------------------}

takeStrictMVar :: ExpQ
takeStrictMVar = rethrowWithLineNumber1 [| StrictMVar.takeMVar |]

putStrictMVar :: ExpQ
putStrictMVar = rethrowWithLineNumber2 [| StrictMVar.putMVar |]

readStrictMVar :: ExpQ
readStrictMVar = rethrowWithLineNumber1 [| StrictMVar.readMVar |]

modifyStrictMVar :: ExpQ
modifyStrictMVar = rethrowWithLineNumber2 [| StrictMVar.modifyMVar |]

modifyStrictMVar_ :: ExpQ
modifyStrictMVar_ = rethrowWithLineNumber2 [| StrictMVar.modifyMVar_ |]

withStrictMVar :: ExpQ
withStrictMVar = rethrowWithLineNumber2 [| StrictMVar.withMVar |]

swapStrictMVar :: ExpQ
swapStrictMVar = rethrowWithLineNumber2 [| StrictMVar.swapMVar |]

{-------------------------------------------------------------------------------
  Chan
-------------------------------------------------------------------------------}

readChan :: ExpQ
readChan = rethrowWithLineNumber1 [| C.readChan |]

{-------------------------------------------------------------------------------
  Async
-------------------------------------------------------------------------------}

wait :: ExpQ
wait = rethrowWithLineNumber1 [| Async.wait |]

waitCatch :: ExpQ
waitCatch = rethrowWithLineNumber1 [| Async.waitCatch |]

waitAny :: ExpQ
waitAny = rethrowWithLineNumber1 [| Async.waitAny |]

waitAnyCatchCancel :: ExpQ
waitAnyCatchCancel = rethrowWithLineNumber1 [| Async.waitAnyCatchCancel |]

#else

{-------------------------------------------------------------------------------
  MVar
-------------------------------------------------------------------------------}

takeMVar :: ExpQ
takeMVar = [| C.takeMVar |]

putMVar :: ExpQ
putMVar = [| C.putMVar |]

readMVar :: ExpQ
readMVar = [| C.readMVar |]

modifyMVar :: ExpQ
modifyMVar = [| C.modifyMVar |]

modifyMVar_ :: ExpQ
modifyMVar_ = [| C.modifyMVar_ |]

withMVar :: ExpQ
withMVar = [| C.withMVar |]

swapMVar :: ExpQ
swapMVar = [| C.swapMVar |]

{-------------------------------------------------------------------------------
  StrictMVar
-------------------------------------------------------------------------------}

takeStrictMVar :: ExpQ
takeStrictMVar = [| StrictMVar.takeMVar |]

putStrictMVar :: ExpQ
putStrictMVar = [| StrictMVar.putMVar |]

readStrictMVar :: ExpQ
readStrictMVar = [| StrictMVar.readMVar |]

modifyStrictMVar :: ExpQ
modifyStrictMVar = [| StrictMVar.modifyMVar |]

modifyStrictMVar_ :: ExpQ
modifyStrictMVar_ = [| StrictMVar.modifyMVar_ |]

withStrictMVar :: ExpQ
withStrictMVar = [| StrictMVar.withMVar |]

swapStrictMVar :: ExpQ
swapStrictMVar = [| StrictMVar.swapMVar |]

{-------------------------------------------------------------------------------
  Chan
-------------------------------------------------------------------------------}

readChan :: ExpQ
readChan = [| C.readChan |]

{-------------------------------------------------------------------------------
  Async
-------------------------------------------------------------------------------}

wait :: ExpQ
wait = [| Async.wait |]

waitCatch :: ExpQ
waitCatch = [| Async.waitCatch |]

waitAny :: ExpQ
waitAny = [| Async.waitAny |]

waitAnyCatchCancel :: ExpQ
waitAnyCatchCancel = [| Async.waitAnyCatchCancel |]

#endif