import Debug.QuickCheck
import System.IO.Unsafe
import Control.Concurrent.MVar
import Control.Concurrent
import Control.Monad


data Action = NewEmptyMVar | NewMVar Int | TakeMVar | ReadMVar | PutMVar Int
	    | SwapMVar Int | IsEmptyMVar | ReturnInt Int | ReturnBool Bool
  deriving (Eq,Show)

main = do 
  t <- myThreadId
  forkIO (threadDelay 1000000 >> killThread t)
	-- just in case we deadlock
  testMVar

testMVar :: IO ()
testMVar = do
  quickCheck prop_NewEIs_NewERet
  quickCheck prop_NewIs_NewRet
  quickCheck prop_NewTake_NewRet
  quickCheck prop_NewEPutTake_NewERet
  quickCheck prop_NewRead_NewRet
  quickCheck prop_NewSwap_New


prop_NewEIs_NewERet = 
  [NewEmptyMVar,IsEmptyMVar] =^ [NewEmptyMVar,ReturnBool True]

prop_NewIs_NewRet n = 
  [NewMVar n,IsEmptyMVar] =^ [NewMVar n,ReturnBool False]

prop_NewTake_NewRet n =
  [NewMVar n,TakeMVar] =^ [NewEmptyMVar,ReturnInt n]

prop_NewEPutTake_NewERet n = 
  [NewEmptyMVar,PutMVar n,TakeMVar] =^ 
    [NewEmptyMVar,ReturnInt n]

prop_NewRead_NewRet n = 
  [NewMVar n,ReadMVar] =^ [NewMVar n,ReturnInt n]

prop_NewSwap_New m n =
  [NewMVar m,SwapMVar n] =^ [NewMVar n]


perform :: [Action] -> IO ([Bool],[Int])
perform [] = return ([],[])

perform (a:as) =
  case a of
    ReturnInt v	 -> liftM (\(b,l) -> (b,v:l)) (perform as)
    ReturnBool v -> liftM (\(b,l) -> (v:b,l)) (perform as)
    NewEmptyMVar -> newEmptyMVar >>= \mv -> perform' mv as
    NewMVar n    -> newMVar n >>= \mv -> perform' mv as    
    _		 -> error $ "Please use NewMVar or NewEmptyMVar as first "
			    ++ "action"


perform' :: MVar Int -> [Action] -> IO ([Bool],[Int])
perform' _ [] = return ([],[])

perform' mv (a:as) =
  case a of
    ReturnInt v	 -> liftM (\(b,l) -> (b,v:l)) (perform' mv as)
    ReturnBool v -> liftM (\(b,l) -> (v:b,l)) (perform' mv as)
    TakeMVar     -> liftM2 (\v (b,l) -> (b,v:l)) (takeMVar mv) 
			        (perform' mv as)
    ReadMVar     -> liftM2 (\v (b,l) -> (b,v:l)) (readMVar mv) 
			        (perform' mv as)
    PutMVar n    -> putMVar mv n >> perform' mv as
    SwapMVar n	 -> swapMVar mv n >> perform' mv as
    IsEmptyMVar  -> liftM2 (\v (b,l) -> (v:b,l)) (isEmptyMVar mv)
				(perform' mv as)
    _		 -> error $ "If you want to use " ++ show a 
			    ++ " please use the =^ operator"


actions :: Gen [Action]
actions = do
  oneof [liftM (NewEmptyMVar:) (actions' True),
	 liftM2 (:) (liftM NewMVar arbitrary) (actions' False)] 


actions' :: Bool -> Gen [Action]
actions' empty =
  oneof ([return [],
	  liftM (IsEmptyMVar:) (actions' empty)] ++
	  if empty
	     then [liftM2 (:) (liftM PutMVar arbitrary) (actions' False)]
	     else []
	  ++
	  if empty
	     then []
	     else [liftM (TakeMVar:) (actions' True)]
	  ++
	  if empty
	     then []
	     else [liftM (ReadMVar:) (actions' False)]
	  ++
	  if empty
	     then []
	     else [liftM2 (:) (liftM SwapMVar arbitrary) (actions' False)]   )


(=^) :: [Action] -> [Action] -> Property
c =^ c' =
  forAll (actions' (delta True c))
	 (\suff -> observe c suff == observe c' suff)
  where observe x suff = unsafePerformIO (perform (x++suff))


(^=^) :: [Action] -> [Action] -> Property
c ^=^ c' =
  forAll actions
	 (\pref -> forAll (actions' (delta True (pref++c)))
			  (\suff -> observe c pref suff == 
				      observe c' pref suff))
  where observe x pref suff = unsafePerformIO (perform (pref++x++suff))


delta :: Bool -> [Action] -> Bool
delta b [] = b

delta b (ReturnInt _:as) = delta b as

delta b (ReturnBool _:as) = delta b as

delta _ (NewEmptyMVar:as) = delta True as

delta _ (NewMVar _:as) = delta False as

delta b (TakeMVar:as) = delta (if b
				  then error "take on empty MVar"
				  else True) as

delta b (ReadMVar:as) = delta (if b
				  then error "read on empty MVar"
				  else False) as

delta _ (PutMVar _:as) = delta False as

delta b (SwapMVar _:as) = delta (if b
				  then error "swap on empty MVar"
				  else False) as

delta b (IsEmptyMVar:as) = delta b as
