-- | Combinators using @CVar@s. These provide many of the helpful
-- functions found in Control.Concurrent.MVar, but for @CVar@s.
module Control.Concurrent.CVar
 ( -- *@CVar@s
  CVar
 , newEmptyCVar
 , newCVar
 , takeCVar
 , putCVar
 , readCVar
 , swapCVar
 , tryTakeCVar
 , tryPutCVar
 , isEmptyCVar
 , withCVar
 , withCVarMasked
 , modifyCVar_
 , modifyCVar
 , modifyCVarMasked_
 , modifyCVarMasked

 -- * Binary semaphores
 -- | A common use of @CVar@s is in making binary semaphores to
 -- control mutual exclusion over a resource, so a couple of helper
 -- functions are provided.
 , lock
 , unlock
 ) where

import Control.Monad (liftM)
import Control.Monad.Catch (mask_, onException)
import Control.Monad.Conc.Class

-- | Create a new @CVar@ containing a value.
newCVar :: MonadConc m => a -> m (CVar m a)
newCVar a = do
  cvar <- newEmptyCVar
  putCVar cvar a
  return cvar

-- | Swap the contents of a @CVar@, and return the value taken. This
-- function is atomic only if there are no other producers fro this
-- @CVar@.
swapCVar :: MonadConc m => CVar m a -> a -> m a
swapCVar cvar a = mask_ $ do
  old <- takeCVar cvar
  putCVar cvar a
  return old

-- | Check if a @CVar@ is empty.
isEmptyCVar :: MonadConc m => CVar m a -> m Bool
isEmptyCVar cvar = do
  val <- tryTakeCVar cvar
  case val of
    Just val' -> putCVar cvar val' >> return True
    Nothing   -> return False

-- | Operate on the contents of a @CVar@, replacing the contents after
-- finishing. This operation is exception-safe: it will replace the
-- original contents of the @CVar@ if an exception is raised. However,
-- it is only atomic if there are no other producers for this @CVar@.
{-# INLINE withCVar #-}
withCVar :: MonadConc m => CVar m a -> (a -> m b) -> m b
withCVar cvar f = mask $ \restore -> do
  val <- takeCVar cvar
  out <- restore (f val) `onException` putCVar cvar val
  putCVar cvar val

  return out

-- | Like 'withCVar', but the @IO@ action in the second argument is
-- executed with asynchronous exceptions masked.
{-# INLINE withCVarMasked #-}
withCVarMasked :: MonadConc m => CVar m a -> (a -> m b) -> m b
withCVarMasked cvar f = mask_ $ do
  val <- takeCVar cvar
  out <- f val `onException` putCVar cvar val
  putCVar cvar val

  return out

-- | An exception-safe wrapper for modifying the contents of a @CVar@.
-- Like 'withCVar', 'modifyCVar' will replace the original contents of
-- the @CVar@ if an exception is raised during the operation. This
-- function is only atomic if there are no other producers for this
-- @CVar@.
{-# INLINE modifyCVar_ #-}
modifyCVar_ :: MonadConc m => CVar m a -> (a -> m a) -> m ()
modifyCVar_ cvar f = modifyCVar cvar $ liftM (\a -> (a,())) . f

-- | A slight variation on 'modifyCVar_' that allows a value to be
-- returned (@b@) in addition to the modified value of the @CVar@.
{-# INLINE modifyCVar #-}
modifyCVar :: MonadConc m => CVar m a -> (a -> m (a, b)) -> m b
modifyCVar cvar f = mask $ \restore -> do
  val <- takeCVar cvar
  (val', out) <- restore (f val) `onException` putCVar cvar val
  putCVar cvar val'
  return out

-- | Like 'modifyCVar_', but the @IO@ action in the second argument is
-- executed with asynchronous exceptions masked.
{-# INLINE modifyCVarMasked_ #-}
modifyCVarMasked_ :: MonadConc m => CVar m a -> (a -> m a) -> m ()
modifyCVarMasked_ cvar f = modifyCVarMasked cvar $ liftM (\a -> (a,())) . f

-- | Like 'modifyCVar', but the @IO@ action in the second argument is
-- executed with asynchronous exceptions masked.
{-# INLINE modifyCVarMasked #-}
modifyCVarMasked :: MonadConc m => CVar m a -> (a -> m (a, b)) -> m b
modifyCVarMasked cvar f = mask_ $ do
  val <- takeCVar cvar
  (val', out) <- f val `onException` putCVar cvar val
  putCVar cvar val'
  return out

-- | Put a @()@ into a @CVar@, claiming the lock. This is atomic.
lock :: MonadConc m => CVar m () -> m ()
lock = flip putCVar ()

-- | Empty a @CVar@, releasing the lock. This is atomic.
unlock :: MonadConc m => CVar m () -> m ()
unlock = takeCVar