module System.Timeout.Monad
    ( timeoutR
    , timeoutM
    , timeoutVoid
    ) where

import Control.Monad.IO.Class
import Control.Monad.Runnable
import System.Timeout

-- | Run an action that is runnable in the IO monad within a time limit
--
-- The time limit is specified in microseconds, and is subject to the same sas 'System.Timeout.timeout'.  
-- Finally, the action to be run itself is passed.  The result is an IO action, which is
-- normally lifted with 'liftIO' to be run in the same IO (see 'timeoutM').  The action,
-- when run, returns the resulting monadic value if it was run in time.  Conceptually,
-- one can think of this as running a local monad, perhaps a copy, in the IO monad;
-- the effects of running in the IO monad are unavoidably irreversible, but the resulting monadic
-- value can be optionally ignored, in which case it is not evaluated in the parent monad
-- and no effects are present in the outer layers of the monad.
timeoutR :: (Integral i, Monad m, MonadRunnable m) => i -> m a -> RunData m -> IO (Maybe (m a))
timeoutR i m d = do
    c <- timeout (fromIntegral i) . rcToIO $ run m d
    case c of
        (Just c') -> do
            return . Just $ construct c'
        (Nothing) -> do
            return $ Nothing

-- | Equivalent to 'timeoutM', but the result is the same monad, making passing the data needed to run the monad unnecessary
timeoutM :: (Integral i, Monad m, MonadIO m, MonadRunnable m) => i -> m a -> m (Maybe (m a))
timeoutM i m = liftIO . timeoutR i m =<< runData

-- | If the void action runs in time, it is run and 'True' is returned; otherwise 'False' is returned when running the action in time does not succeed
--
-- For example, to run a void action within one second,
-- one might write
--
-- > succeeded <- timeoutVoid
--
-- The effects of "action" will be fully executed if succeeded
-- is 'True'; if succeeded is 'False', the action could not run
-- in time, and only global IO actions that were performed while
-- the local monad was being evaluated will be run.
timeoutVoid :: (Integral i, Monad m, MonadIO m, MonadRunnable m) => i -> m () -> m Bool
timeoutVoid i m = maybe (return False) (>> return True) =<< timeoutM i m