-- | A toggle is a switch initially True, which can only be made false
-- (when some action is performed, say).  This module implements
-- toggles, allowing atomic switching to false of 1 toggle, or
-- 2 toggles together.  To avoid deadlock we use a supply of unique
-- integers.
module Events.Toggle(
   Toggle, -- toggle type
   newToggle, -- create a new toggle
   toggle1, -- set the toggle to false, and return the original value.
   toggle2, -- if toggles are both true, change them to false, otherwise
   -- leave the toggle settings unchanged and return them.
   ifToggle, -- :: Toggle -> IO () -> IO ()
   -- If the toggle is true, change it to false and execute action.
   peekToggle, -- :: Toggle -> IO Bool
   -- peek at the contents of a toggle, without changing it.

   SimpleToggle, -- A simple toggle.  We can only flip these one at a time.
   newSimpleToggle, -- create a new simple toggle
   simpleToggle, -- set this toggle to false, and return the original value.
   ifSimpleToggle, -- like ifToggle
   ) where

import Control.Concurrent

import Util.Computation
import Util.Object

-- ----------------------------------------------------------------------
-- Simple Toggles
-- ----------------------------------------------------------------------

newtype SimpleToggle = SimpleToggle (MVar Bool)

newSimpleToggle :: IO SimpleToggle
newSimpleToggle =
   do
      mVar <- newMVar True
      return (SimpleToggle mVar)

simpleToggle :: SimpleToggle -> IO Bool
simpleToggle (SimpleToggle mVar) =
   do
      oldVal <- takeMVar mVar
      putMVar mVar False
      return oldVal

ifSimpleToggle :: SimpleToggle -> IO () -> IO ()
ifSimpleToggle sToggle action =
   do
      goAhead <- simpleToggle sToggle
      if goAhead then action else done

-- simpleToggle2 is not safe from deadlocks
simpleToggle2 :: SimpleToggle -> SimpleToggle -> IO (Maybe (Bool,Bool))
simpleToggle2 (SimpleToggle mVar1) (SimpleToggle mVar2) =
   do
      oldVal1 <- takeMVar mVar1
      oldVal2 <- takeMVar mVar2
      if (oldVal1 && oldVal2)
         then
            do
               putMVar mVar2 False
               putMVar mVar1 False
               return Nothing
         else
            do
               putMVar mVar2 oldVal2
               putMVar mVar1 oldVal1
               return (Just (oldVal1,oldVal2))


-- peekSimpleToggle is used by toggle2
peekSimpleToggle :: SimpleToggle -> IO Bool
peekSimpleToggle (SimpleToggle mVar) = readMVar mVar

-- ----------------------------------------------------------------------
-- Toggles
-- ----------------------------------------------------------------------

data Toggle = Toggle !ObjectID !SimpleToggle

newToggle :: IO Toggle
newToggle =
   do
      uniqVal <- newObject
      stoggle <- newSimpleToggle
      return (Toggle uniqVal stoggle)

toggle1 :: Toggle -> IO Bool
-- switch bool to false, returning original value.
toggle1 (Toggle _ stoggle) = simpleToggle stoggle

ifToggle :: Toggle -> IO () -> IO ()
ifToggle toggle action =
   do
      goAhead <- toggle1 toggle
      if goAhead then action else done

toggle2 :: Toggle -> Toggle -> IO(Maybe(Bool,Bool))
-- Switch both toggles to from True to False, atomically, if possible.
-- If we can't do this, return Just (the current status of the toggles).
toggle2 (Toggle unique1 stoggle1) (Toggle unique2 stoggle2) =
   case compare unique1 unique2 of
      LT -> simpleToggle2 stoggle1 stoggle2
      GT ->
         do
            result <- simpleToggle2 stoggle2 stoggle1
            case result of
               Nothing -> return Nothing
               Just (r1,r2) -> return (Just (r2,r1))
      EQ ->
         do
            r <- peekSimpleToggle stoggle1
            return (Just (r,r))

-- peekToggle is used in Channels.hs to avoid a memory leak.
peekToggle :: Toggle -> IO Bool
peekToggle (Toggle _ sToggle) = peekSimpleToggle sToggle


-- ----------------------------------------------------------------------
-- Optimisations
-- ----------------------------------------------------------------------


{-# INLINE newToggle #-}
{-# INLINE toggle1 #-}
{-# INLINE toggle2 #-}
{-# INLINE peekToggle #-}
{-# INLINE newSimpleToggle #-}
{-# INLINE simpleToggle #-}
{-# INLINE simpleToggle2 #-}