module MultiMVar where

import Control.Concurrent

-- An abstraction around MVar that supports multiple nondeterministic take.
--   ToDo: we need Ord on MVar to do this properly.
--   ToDo: exception safety.

newtype MultiMVar a = MultiMVar (MVar (Either (a,[MVar a]) [Reader a]))
	-- Left indicates MultiMVar is full
	-- Right indicates it is empty

data Reader a = 
  Reader 
   (MVar ())	-- if this is empty, this reader is already satisfied
   (MVar a)	-- the box to put the result in for this reader

newEmptyMultiMVar :: IO (MultiMVar a)
newEmptyMultiMVar = newMVar (Right []) >>= return . MultiMVar

putMultiMVar :: MultiMVar a -> a -> IO ()
putMultiMVar (MultiMVar mm) a = do
  r <- takeMVar mm
  case r of
     Left (a,writers) -> do -- already full, so we need to wait
	wait <- newMVar a
	putMVar mm (Left (a,wait:writers))
	putMVar wait undefined -- this blocks until the put is performed
     Right rdrs -> loop rdrs
      where loop [] = putMVar mm (Left (a, []))
	    loop (Reader ctrl put : rdrs) = do
		k <- tryTakeMVar ctrl
		case k of
		   Nothing -> loop rdrs -- somebody got here first
		   Just _  -> do putMVar put a
				 putMVar mm (Right rdrs)

multiTake :: [MultiMVar a] -> IO a
multiTake mms = loop mms []
  -- we need to take all the MultiMVars, in order (to avoid deadlock)
  -- if we find one that is full, then return that, and unlock all the others
 where
  loop [] taken = do
    ctrl <- newMVar ()
    box  <- newEmptyMVar 
    sequence_ [ putMVar m (Right (Reader ctrl box : rdrs)) | (m,rdrs) <- taken ]
    takeMVar box
  loop (MultiMVar mm : mms) taken = do
    r <- takeMVar mm
    case r of
	Left (a,writers) -> do
	    -- got a full one, so unlock all the others
	    sequence_ [ putMVar m (Right rdrs) | (m,rdrs) <- taken ]
	    case writers of
		[] -> do
		   putMVar mm (Right [])
		   return a
		(wr:wrs) -> do
		   new_a <- takeMVar wr
		   putMVar mm (Left (new_a,wrs))
		   return a
	Right rdrs ->
	    loop mms ((mm,rdrs):taken)
