-- |
-- Description: Simple Lock
--
-- A simple semaphore
module Reactor.BSem (
   BSem,
   newBSem,
   newLockedBSem,

   tryAcquireBSems,
   tryAcquireBSemsWithError,
   tryAcquireBSemsWithError1,
   ) where

import Data.Maybe

import Control.Concurrent.MVar

import Util.Computation
import Events.Synchronized
import Reactor.Lock

-- --------------------------------------------------------------------------
-- Type
-- --------------------------------------------------------------------------

-- | A simple lock.
newtype BSem = BSem (MVar ()) deriving Eq

-- --------------------------------------------------------------------------
-- Instances
-- --------------------------------------------------------------------------

instance Lock BSem where
   acquire (BSem sem) = takeMVar sem
   release (BSem sem) = putMVar sem ()
   tryAcquire (BSem sem) =
      do
         success <- tryTakeMVar sem
         return (isJust success)

instance Synchronized BSem where
   synchronize (BSem sem) c =
      do
         takeMVar sem
         ans <- try c
         putMVar sem ()
         propagate ans

-- --------------------------------------------------------------------------
-- Commands
-- --------------------------------------------------------------------------

-- | Create a new unlocked BSem
newBSem :: IO BSem
newBSem = newMVar () >>= return . BSem

-- | Create a new locked BSem
newLockedBSem   :: IO BSem
newLockedBSem = newEmptyMVar >>= return . BSem


-- --------------------------------------------------------------------------
-- Utilities
-- --------------------------------------------------------------------------

-- | tryAcquireBSems attempts to acquire a list of BSems.  If successful it
-- returns the action to release them all again.  If unsuccessful it
-- returns Nothing, and leaves all the BSems released.
tryAcquireBSems :: [BSem] -> IO (Maybe (IO ()))
tryAcquireBSems bSems =
   do
      let
         toMess _ = return ""
      actWithError <- tryAcquireBSemsWithError id toMess bSems
      return (case fromWithError actWithError of
         Left _ -> Nothing
         Right act -> Just act
         )

-- | tryAcquireBSemsWithError is a generalisation of tryAcquireBSems, which
-- produces an error message
--
-- The first argument extracts an object\'s BSem; the second gets a String to
-- be used as a message if we can\'t get the object\'s lock.
tryAcquireBSemsWithError :: (object -> BSem) -> (object -> IO String)
   -> [object] -> IO (WithError (IO ()))
tryAcquireBSemsWithError toBSem toMess objects =
   let
      getBSem object = return (toBSem object)
      getMessIfError object =
         do
            mess <- toMess object
            return (Just mess)
   in
      tryAcquireBSemsWithError1 getBSem getMessIfError objects

-- | tryAcquireBSemsWithError1 toBSem getMessIfError objects
-- attempts to acquire the BSems in (map toBSem objects).  In
-- the event of a (toBSem object) already being acquired, it looks at
-- the result of getMessIfError object.  If this is (Just mess)
-- it returns an error condition with message (mess), first
-- releasing all BSems it has already acquired; if it is (Nothing)
-- it goes on to attempt to acquire the BSems for the remaining objects.
-- If it gets to the end of the list it returns an action which can be
-- used to release all the BSems it has acquired.
tryAcquireBSemsWithError1 ::
   (object -> IO BSem) -> (object -> IO (Maybe String)) -> [object]
   -> IO (WithError (IO ()))
tryAcquireBSemsWithError1 _ _ [] = return . return $ done
tryAcquireBSemsWithError1 getBSem getMessIfError (object:objects) =
   do
      bSem <- getBSem object
      acquire1 <- tryAcquire bSem
      if acquire1
         then
            do
               acquires
                  <- tryAcquireBSemsWithError1 getBSem getMessIfError objects
               case fromWithError acquires of
                  Right releaseAct ->
                     return (return (
                        do
                           releaseAct
                           release bSem
                        ))
                  Left _ ->
                     do
                        release bSem
                        return acquires
         else
            do
               errorMessIfError <- getMessIfError object
               case errorMessIfError of
                  Just errorMess -> return (fail errorMess)
                  Nothing ->
                     tryAcquireBSemsWithError1 getBSem getMessIfError objects