{-# LANGUAGE FlexibleContexts      #-}

module Control.Concurrent.Async.Timer.Unsafe
  ( Timer
  , defaultTimerConf
  , timerConfSetInitDelay
  , timerConfSetInterval
  , withAsyncTimer
  , timerWait
  ) where

import           Control.Concurrent.Async.Lifted
import           Control.Concurrent.Async.Timer.Internal
import           Control.Concurrent.Lifted
import           Control.Exception.Safe
import           Control.Monad.Trans.Control

-- | Spawn a timer thread based on the provided timer configuration
-- and then run the provided IO action, which receives the new timer
-- as an argument and call 'timerWait' on it for synchronization. When
-- the provided IO action has terminated, the timer thread will be
-- terminated also.
withAsyncTimer :: (MonadBaseControl IO m, MonadMask m)
               => TimerConf -> (Timer -> m b) -> m b
withAsyncTimer conf io = do
  -- This MVar will be our synchronization mechanism.
  mVar <- newEmptyMVar
  let timer         = Timer { timerMVar = mVar }
      initDelay     = _timerConfInitDelay conf
      intervalDelay = _timerConfInterval  conf
  withAsync (timerThread initDelay intervalDelay mVar) $ \asyncHandle -> do
    -- This guarantees that we will be informed right away if our
    -- timer thread disappears, for example because of an async
    -- exception:
    link asyncHandle
    -- This guarantees that we will throw the TimerEnd exception to
    -- the timer thread after the provided IO action has ended
    -- (w/ or w/o an exception):
    io timer `finally` cancelWith asyncHandle TimerEnd