-- Local and global IVars
--
-- Author: Patrick Maier
-----------------------------------------------------------------------------

module Control.Parallel.HdpH.Internal.IVar
  ( -- * local IVar type
    IVar,       -- synonym: IVar m a = IORef <IVarContent m a>

    -- * operations on local IVars
    newIVar,    -- :: IO (IVar m a)
    putIVar,    -- :: IVar m a -> a -> IO [Thread m]
    getIVar,    -- :: IVar m a -> (a -> Thread m) -> IO (Maybe a)
    pollIVar,   -- :: IVar m a -> IO (Maybe a)
    probeIVar,  -- :: IVar m a -> IO Bool

    -- * global IVar type
    GIVar,      -- synonym: GIVar m a = GRef (IVar m a)

    -- * operations on global IVars
    globIVar,   -- :: Int -> IVar m a -> IO (GIVar m a)
    hostGIVar,  -- :: GIVar m a -> NodeId
    putGIVar    -- :: Int -> GIVar m a -> a -> IO [Thread m]
  ) where

import Prelude hiding (error)
import Data.Functor ((<$>))
import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef)
import Data.Maybe (isJust)

import Control.Parallel.HdpH.Internal.Location 
       (NodeId, debug, dbgGIVar, dbgIVar)
import Control.Parallel.HdpH.Internal.GRef (GRef, at, globalise, free, withGRef)
import Control.Parallel.HdpH.Internal.Type.Par (Thread)


-----------------------------------------------------------------------------
-- type of local IVars

-- An IVar is a mutable reference to either a value or a list of blocked
-- continuations (waiting for a value);
-- the parameter 'm' abstracts a monad (cf. module HdpH.Internal.Type.Par).
type IVar m a = IORef (IVarContent m a)

data IVarContent m a = Full a
                     | Blocked [a -> Thread m]


-----------------------------------------------------------------------------
-- operations on local IVars, borrowing from
--    [1] Marlow et al. "A monad for deterministic parallelism". Haskell 2011.

-- Create a new, empty IVar.
newIVar :: IO (IVar m a)
newIVar = newIORef (Blocked [])


-- Write 'x' to the IVar 'v' and return the list of blocked threads.
-- Unlike [1], multiple writes fail silently (ie. they do not change
-- the value stored, and return an empty list of threads).
putIVar :: IVar m a -> a -> IO [Thread m]
putIVar v x = do
  e <- readIORef v
  case e of
    Full _    -> do debug dbgIVar $ "Put to full IVar"
                    return []
    Blocked _ -> do maybe_ts <- atomicModifyIORef v fill_and_unblock
                    case maybe_ts of
                      Nothing -> do debug dbgIVar $ "Put to full IVar"
                                    return []
                      Just ts -> do debug dbgIVar $
                                      "Put to empty IVar; unblocking " ++
                                      show (length ts) ++ " threads"
                                    return ts
      where
     -- fill_and_unblock :: IVarContent m a ->
     --                       (IVarContent m a, Maybe [Thread m])
        fill_and_unblock e =
          case e of
            Full _     -> (e,       Nothing)
            Blocked cs -> (Full x,  Just $ map ($ x) cs)


-- Read from the given IVar 'v' and return the value if it is full.
-- Otherwise add the given continuation 'c' to the list of blocked
-- continuations and return nothing.
getIVar :: IVar m a -> (a -> Thread m) -> IO (Maybe a)
getIVar v c = do
  e <- readIORef v
  case e of
    Full x    -> do return (Just x)
    Blocked _ -> do maybe_x <- atomicModifyIORef v get_or_block
                    case maybe_x of
                      Just _  -> do return maybe_x
                      Nothing -> do debug dbgIVar $ "Blocking on IVar"
                                    return maybe_x
      where
     -- get_or_block :: IVarContent m a -> (IVarContent m a, Maybe a)
        get_or_block e =
          case e of
            Full x     -> (e,              Just x)
            Blocked cs -> (Blocked (c:cs), Nothing)


-- Poll the given IVar 'v' and return its value if full, Nothing otherwise.
-- Does not block.
pollIVar :: IVar m a -> IO (Maybe a)
pollIVar v = do
  e <- readIORef v
  case e of
    Full x    -> return (Just x)
    Blocked _ -> return Nothing


-- Probe whether the given IVar is full, returning True if it is.
-- Does not block.
probeIVar :: IVar m a -> IO Bool
probeIVar v = isJust <$> pollIVar v


-----------------------------------------------------------------------------
-- type of global IVars; instances mostly inherited from global references

-- A global IVar is a global reference to an IVar; 'm' abstracts a monad.
-- NOTE: The HdpH interface will restrict the type parameter 'a' to 
--       'Closure b' for some type 'b', but but the type constructor 'GIVar' 
--       does not enforce this restriction.
type GIVar m a = GRef (IVar m a)


-----------------------------------------------------------------------------
-- operations on global IVars

-- Returns node hosting given global IVar.
hostGIVar :: GIVar m a -> NodeId
hostGIVar = at


-- Globalise the given IVar;
-- the scheduler ID argument may be used for logging.
globIVar :: Int -> IVar m a -> IO (GIVar m a)
globIVar schedID v = do
  gv <- globalise v
  debug dbgGIVar $ "New global IVar " ++ show gv
  return gv


-- Write 'x' to the locally hosted global IVar 'gv', free 'gv' and return 
-- the list of blocked threads. Like putIVar, multiple writes fail silently
-- (as do writes to a dead global IVar);
-- the scheduler ID argument may be used for logging.
putGIVar :: Int -> GIVar m a -> a -> IO [Thread m]
putGIVar schedID gv x = do
  debug dbgGIVar $ "Put to global IVar " ++ show gv
  ts <- withGRef gv (\ v -> putIVar v x) (return [])
  free gv    -- free 'gv' (eventually)
  return ts