-- |
-- Module     : Simulation.Aivika.Gate
-- Copyright  : Copyright (c) 2009-2017, David Sorokin <david.sorokin@gmail.com>
-- License    : BSD3
-- Maintainer : David Sorokin <david.sorokin@gmail.com>
-- Stability  : experimental
-- Tested with: GHC 8.0.1
--
-- The module defines a gate which can be either opened or closed.
--
module Simulation.Aivika.Gate
       (Gate,
        newGate,
        newGateOpened,
        newGateClosed,
        openGate,
        closeGate,
        invertGate,
        gateOpened,
        gateClosed,
        awaitGateOpened,
        awaitGateClosed,
        gateChanged_) where

import Control.Monad

import Simulation.Aivika.Simulation
import Simulation.Aivika.Event
import Simulation.Aivika.Process
import Simulation.Aivika.Signal
import Simulation.Aivika.Ref

-- | Represents a gate, which can be either opened or closed.
data Gate = Gate { gateRef :: Ref Bool }

-- | Create a new gate, specifying whether the gate is initially open.
newGate :: Bool -> Simulation Gate
newGate opened =
  do r <- newRef opened
     return Gate { gateRef = r }

-- | Create a new initially open gate.
newGateOpened :: Simulation Gate
newGateOpened = newGate True

-- | Create a new initially close gate.
newGateClosed :: Simulation Gate
newGateClosed = newGate False

-- | Open the gate if it was closed.
openGate :: Gate -> Event ()
openGate gate =
  writeRef (gateRef gate) True

-- | Close the gate if it was open.
closeGate :: Gate -> Event ()
closeGate gate =
  writeRef (gateRef gate) False

-- | Invert the gate.
invertGate :: Gate -> Event ()
invertGate gate =
  modifyRef (gateRef gate) not

-- | Test whether the gate is open.
gateOpened :: Gate -> Event Bool
gateOpened gate =
  readRef (gateRef gate)

-- | Test whether the gate is closed.
gateClosed :: Gate -> Event Bool
gateClosed gate =
  fmap not $ readRef (gateRef gate)

-- | Await the gate to be opened if required. If the gate is already open
-- then the computation returns immediately.
awaitGateOpened :: Gate -> Process ()
awaitGateOpened gate =
  do f <- liftEvent $ readRef (gateRef gate)
     unless f $
       do processAwait $ refChanged_ (gateRef gate)
          awaitGateOpened gate

-- | Await the gate to be closed if required. If the gate is already closed
-- then the computation returns immediately.
awaitGateClosed :: Gate -> Process ()
awaitGateClosed gate =
  do f <- liftEvent $ readRef (gateRef gate)
     when f $
       do processAwait $ refChanged_ (gateRef gate)
          awaitGateClosed gate

-- | Signal triggered when the state of the gate changes.
gateChanged_ :: Gate -> Signal ()
gateChanged_ gate =
  refChanged_ (gateRef gate)