{-|
Module      : $Header$
Description : Mutable references for marvin.
Copyright   : (c) Justus Adam, 2016
License     : BSD3
Maintainer  : dev@justus.science
Stability   : experimental
Portability : POSIX
-}
module Marvin.Util.Mutable where


import           Control.Concurrent.MVar
import           Control.Monad.IO.Class
import           Data.IORef

-- | A mutable reference to a value of type @v@
--
-- This is like a pointer in c, the value behind which can be mutated by several functions.
-- So long as they retain this reference they are able to retrieve the updated value.
type Mutable v = IORef v


-- | Create a new mutable reference of type @v@ from an initial value.
newMutable :: MonadIO m => a -> m (Mutable a)
newMutable = liftIO . newIORef


-- | Retrieve the value behind by a mutable reference.
readMutable :: MonadIO m => Mutable a -> m a
readMutable = liftIO . readIORef


-- | Set the value inside a mutable reference.
writeMutable :: MonadIO m => Mutable a -> a -> m ()
writeMutable m = liftIO . writeIORef m


-- | Change the value behind a mutable reference.
modifyMutable :: MonadIO m => Mutable a -> (a -> a) -> m ()
modifyMutable m = liftIO . modifyIORef m


-- | A value that can be shared on multiple concurrent Threads.
--
-- This value works like a channel. It can either be empty or full.
-- If it is empty 'writeSynchronized' fills it, otherwise the write blocks.
-- If it is full 'takeSynchronized' empties, otherwise it blocks until it is filled.
-- 'readSynchronized' does not empty it and also blocks if the 'Synchronized' is empty.
--
-- Should you just use it as a thread safe mutable variable, mutations typically follow the pattern:
-- @
--   val <- takeSynchronized -- obtain the value and leave it empty to block concurrent reads
--   let mod = modify val -- modify the value
--   writeSynchronized mod -- write back the result
-- @
--
-- Another use for this type is as a message channel, where we have a producer and a consumer,
-- the producer tries to write values into the 'Synchronized' ('writeSynchronized') and the consumer
-- waits for the 'Synchronized' to be filled and takes the value 'takeSynchronized' for procesing.
--
-- It works generally best if any 'Synchronized' is only used for one of these two applications at the same time.
--
-- This type is the same as 'MVar', only renamed for readability. If you want a more in depth documentation, see the documentation for 'MVar'.
type Synchronized = MVar


-- | Read the vaue, but don't empty the 'Synchronized'. Blocks if it is empty.
readSynchronized :: MonadIO m => Synchronized a -> m a
readSynchronized = liftIO . readMVar


-- | Non blocking version of 'readSynchronized'. Returns 'Nothing' if it was empty.
tryReadSynchronized :: MonadIO m => Synchronized a -> m (Maybe a)
tryReadSynchronized = liftIO . tryReadMVar


-- | Read the value and empty the 'Synchronized', blocks if already empty.
takeSynchronized :: MonadIO m => Synchronized a -> m a
takeSynchronized = liftIO . takeMVar


-- | Non blocking version of 'takeSynchronized', returns 'Nothing' if it was empty.
tryTakeSynchronized :: MonadIO m => Synchronized a -> m (Maybe a)
tryTakeSynchronized = liftIO . tryTakeMVar


-- | Fills the empty 'Synchronized' with a value, blocks if it is full.
writeSynchronized :: MonadIO m => Synchronized a -> a -> m ()
writeSynchronized mv = liftIO . putMVar mv


-- | Non blocking version of 'writeSynchronized'. Returns 'False' if it was full.
tryWriteSynchronized :: MonadIO m => Synchronized a -> a -> m Bool
tryWriteSynchronized mv = liftIO . tryPutMVar mv


-- | Query if the 'Synchronized' is empty or full.
isEmptySynchronized :: MonadIO m => Synchronized a -> m Bool
isEmptySynchronized = liftIO . isEmptyMVar