module Acme.Missiles.STM (
    -- * Launching missiles in the 'STM' monad
    withMissilesDo,
    launchMissilesSTM,
) where

import Acme.Missiles (launchMissiles)

import Control.Concurrent       (forkIO, killThread)
import Control.Concurrent.STM   (STM, atomically, retry,
                                 TVar, newTVarIO, readTVar, writeTVar)
import Control.Exception        (bracket)
import Control.Monad            (forever)
import System.IO.Unsafe         (unsafePerformIO)

-- | Perform initialization needed to launch missiles in the 'STM' monad.
withMissilesDo :: IO a -> IO a
withMissilesDo action =
        bracket (forkIO doLaunching)
                killThread
                (\_ -> action)
    where
        doLaunching = forever $ do
            atomically $ do
                n <- readTVar missileCommand
                if n > 0
                    then writeTVar missileCommand (n - 1)
                    else retry
            launchMissiles

-- | Launch missiles within an 'STM' computation.  Even if the memory
-- transaction is retried, only one salvo of missiles will be launched.
--
-- Example:
--
-- >import Acme.Missiles
-- >import Control.Concurrent
-- >import Control.Concurrent.STM
-- >
-- >main :: IO ()
-- >main = withMissilesDo $ do
-- >    xv <- atomically $ newTVar (2 :: Int)
-- >    yv <- atomically $ newTVar (1 :: Int)
-- >    atomically $ do
-- >        x <- readTVar xv
-- >        y <- readTVar yv
-- >        if x > y
-- >            then launchMissilesSTM
-- >            else return ()
-- >    threadDelay 100000
launchMissilesSTM :: STM ()
launchMissilesSTM = do
    n <- readTVar missileCommand
    writeTVar missileCommand $! n + 1

missileCommand :: TVar Integer
{-# NOINLINE missileCommand #-}
missileCommand = unsafePerformIO (newTVarIO 0)