-- |
-- - Reentrant, protected references: an IORef in an MVar, protected by
-- - a reentrant monitor.
-- -
-- - The operations which change the value (setRef, changeRef, withRef)
-- - are protected by the monitor, which additionally provides a reentrant
-- - synchronize method.


module Reactor.ReferenceVariables(
  Ref,       -- type Ref a
  newRef,    -- :: a -> IO (Ref a)
  setRef,    -- :: Ref a -> a -> IO ()
  changeRef, -- :: Ref a -> (a -> a) -> IO ()
  changeRefM, -- :: Ref a-> (a-> IO a) -> IO ()
  withRef,   -- :: Ref a -> (a -> b) -> IO b
  getRef     -- :: Ref a -> IO a

) where

import Data.IORef
import Control.Concurrent.MVar

import Events.Synchronized
import Reactor.MSem

data Ref a = Ref MSem (MVar (IORef a))

newRef :: a -> IO (Ref a)
newRef val =
  do
    ioref <- newIORef val
    mvar <- newMVar ioref
    mtx <- newMSem
    return (Ref mtx mvar)

setRef :: Ref a -> a -> IO ()
setRef (Ref mtx mvar) val =
  synchronize mtx $
  do ioref <- takeMVar mvar
     writeIORef ioref val
     putMVar mvar ioref

changeRef :: Ref a -> (a -> a) -> IO ()
changeRef (Ref mtx mvar) fn =
  synchronize mtx $
  do ioref <- takeMVar mvar
     val <- readIORef ioref
     writeIORef ioref (fn val)
     putMVar mvar ioref

changeRefM :: Ref a -> (a -> IO a) -> IO ()
changeRefM (Ref mtx mvar) act =
  synchronize mtx $
  do ioref <- takeMVar mvar
     val <- readIORef ioref
     val' <- act val
     writeIORef ioref val'
     putMVar mvar ioref

withRef :: Ref a -> (a -> b) -> IO b
withRef (Ref mtx mvar) fn =
  synchronize mtx $
  do
    ioref <- takeMVar mvar
    val <- readIORef ioref
    putMVar mvar ioref
    return (fn val)

getRef :: Ref a -> IO a
getRef (Ref _ mvar) =
  do
    ioref <- takeMVar mvar
    val <- readIORef ioref
    putMVar mvar ioref
    return val

instance Synchronized (Ref a) where
  synchronize (Ref mtx mvar) = synchronize mtx