{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, TypeOperators #-}
-- | A pure specification of basic operations on MVars.

module Test.IOSpec.MVar
   (
   -- * The 'MVarS' spec
     MVarS
   -- * Supported functions
   , MVar
   , newEmptyMVar
   , takeMVar
   , putMVar
   )
   where

import Data.Dynamic
import Data.Maybe (fromJust)
import Test.IOSpec.Types
import Test.IOSpec.VirtualMachine

-- The 'MVarS' data type and its instances.
--
-- | An expression of type @IOSpec MVarS a@ corresponds to an @IO@
-- computation that uses shared, mutable variables and returns a
-- value of type @a@.
--
-- By itself, 'MVarS' is not terribly useful. You will probably want
-- to use @IOSpec (ForkS :+: MVarS)@.

data MVarS a =
     NewEmptyMVar (Loc -> a)
  |  TakeMVar Loc (Data -> a)
  |  PutMVar Loc Data a

instance Functor MVarS where
  fmap f (NewEmptyMVar io) = NewEmptyMVar (f . io)
  fmap f (TakeMVar l io) = TakeMVar l (f . io)
  fmap f (PutMVar l d io) = PutMVar l d (f io)

-- | An 'MVar' is a shared, mutable variable.
newtype MVar a = MVar Loc deriving Typeable

-- | The 'newEmptyMVar' function creates a new 'MVar' that is initially empty.
newEmptyMVar        :: (Typeable a, MVarS :<: f) => IOSpec f (MVar a)
newEmptyMVar        = inject $ NewEmptyMVar (return . MVar)

-- | The 'takeMVar' function removes the value stored in an
-- 'MVar'. If the 'MVar' is empty, the thread is blocked.
takeMVar            :: (Typeable a, MVarS :<: f) => MVar a -> IOSpec f a
takeMVar (MVar l)   = inject $ TakeMVar l (return . fromJust . fromDynamic)

-- | The 'putMVar' function fills an 'MVar' with a new value. If the
-- 'MVar' is not empty, the thread is blocked.
putMVar             :: (Typeable a, MVarS :<: f) => MVar a -> a -> IOSpec f ()
putMVar (MVar l) d  = inject $ PutMVar l (toDyn d) (return ())

instance Executable MVarS where
  step (NewEmptyMVar t) = do loc <- alloc
                             emptyLoc loc
                             return (Step (t loc))
  step (TakeMVar loc t) = do var <- lookupHeap loc
                             case var of
                               Nothing -> return Block
                               Just x -> do
                                 emptyLoc loc
                                 return (Step (t x))
  step (PutMVar loc d t) = do var <- lookupHeap loc
                              case var of
                                Nothing -> do
                                  updateHeap loc d
                                  return (Step t)
                                Just _ -> return Block
