-- It corresponds to model TimeOutInt described in document 
-- Advanced Features of the SimPy Language
-- [http://heather.cs.ucdavis.edu/~matloff/156/PLN/AdvancedSimPy.pdf]. 
-- SimPy is available on [http://simpy.sourceforge.net/].
--   
-- The model description is as follows.
--
-- Same as TimeOut.hs but using interrupts. A network node sends a message
-- but also sets a timeout period; if the node times out, it assumes the
-- message it had sent was lost, and it will send again. The time to get
-- an acknowledgement for a message is exponentially distributed with
-- mean 1.0, and the timeout period is 0.5. Immediately after receiving
-- an acknowledgement, the node sends out a new message.
--
-- We find the proportion of messages which timeout. The output should
-- be about 0.61.

import Control.Monad
import Control.Monad.Trans

import Simulation.Aivika.Trans
import Simulation.Aivika.IO

ackRate = 1.0 / 1.0  -- reciprocal of the acknowledge mean time
toPeriod = 0.5       -- timeout period

specs = Specs { spcStartTime = 0.0,
                spcStopTime = 10000.0,
                spcDT = 1.0,
                spcMethod = RungeKutta4,
                spcGeneratorType = SimpleGenerator }
     
model :: Simulation IO Double
model =
  do -- number of messages sent
     nMsgs <- newRef 0
     
     -- number of timeouts which have occured
     nTimeOuts <- newRef 0

     nodePid <- newProcessId
     
     let node =
           do liftEvent $ modifyRef nMsgs $ (+) 1
              -- create the process ID
              timeoutPid <- liftSimulation newProcessId
              -- set up the timeout
              liftEvent $ runProcessUsingId timeoutPid timeout
              -- wait for ACK, but could be timeout
              ackTime <-
                liftParameter $
                randomExponential (1 / ackRate)
              holdProcess ackTime
              liftEvent $
                do interrupted <- processInterrupted nodePid
                   if interrupted
                     then modifyRef nTimeOuts $ (+) 1
                     else cancelProcessWithId timeoutPid
              node
              
         timeout =
           do holdProcess toPeriod
              liftEvent $ interruptProcess nodePid

     runProcessInStartTimeUsingId
       nodePid node 
     
     runEventInStopTime $
       do x <- readRef nTimeOuts
          y <- readRef nMsgs
          return $ x / y
  
main = 
  do putStr "The percentage of timeout was "
     runSimulation model specs >>= print