content: 
  -- Example: Work Stations in Series
  --
  -- This is a model of two work stations connected in a series and separated by finite queues.
  --
  -- It is described in different sources [1, 2]. So, this is chapter 7 of [2] and section 5.14 of [1].
  --
  -- The maintenance facility of a large manufacturer performs two operations. 
  -- These operations must be performed in series; operation 2 always follows operation 1. 
  -- The units that are maintained are bulky, and space is available for only eight units 
  -- including the units being worked on. A proposed design leaves space for two units 
  -- between the work stations, and space for four units before work station 1. [..] 
  -- Current company policy is to subcontract the maintenance of a unit if it cannot 
  -- gain access to the in-house facility.
  -- 
  -- Historical data indicates that the time interval between requests for maintenance 
  -- is exponentially distributed with a mean of 0.4 time units. Service times are also 
  -- exponentially distributed with the first station requiring on the average 0.25 time 
  -- units and the second station, 0.5 time units. Units are transported automatically 
  -- from work station 1 to work station 2 in a negligible amount of time. If the queue of 
  -- work station 2 is full, that is, if there are two units awaiting for work station 2, 
  -- the first station is blocked and a unit cannot leave the station. A blocked work 
  -- station cannot server other units.
  --
  -- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.
  -- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006
  
  import Prelude hiding (id, (.)) 
  
  import Control.Monad
  import Control.Monad.Trans
  import Control.Arrow
  import Control.Category (id, (.))
  
  import Simulation.Aivika
  import Simulation.Aivika.Queue
  
  -- | The simulation specs.
  specs = Specs { spcStartTime = 0.0,
                  spcStopTime = 300.0,
                  spcDT = 0.1,
                  spcMethod = RungeKutta4,
                  spcGeneratorType = SimpleGenerator }
  
  -- the mean delay of the input arrivals distributed exponentially
  meanOrderDelay = 0.4 
  
  -- the capacity of the queue before the first work places
  queueMaxCount1 = 4
  
  -- the capacity of the queue before the second work places
  queueMaxCount2 = 2
  
  -- the mean processing time distributed exponentially in
  -- the first work stations
  meanProcessingTime1 = 0.25
  
  -- the mean processing time distributed exponentially in
  -- the second work stations
  meanProcessingTime2 = 0.5
  
  -- the number of the first work stations
  -- (in parallel but the commented code allocates them sequentially)
  workStationCount1 = 1
  
  -- the number of the second work stations
  -- (in parallel but the commented code allocates them sequentially)
  workStationCount2 = 1
  
  -- create a work station (server) with the exponential processing time
  newWorkStationExponential meanTime =
    newServer $ \a ->
    do holdProcess =<<
         (liftParameter $
          randomExponential meanTime)
       return a
  
  model :: Simulation Results
  model = do
    -- it will gather the statistics of the processing time
    arrivalTimer <- newArrivalTimer
    -- define a stream of input events
    let inputStream = randomExponentialStream meanOrderDelay 
    -- create a queue before the first work stations
    queue1 <-
      runEventInStartTime $
      newFCFSQueue queueMaxCount1
    -- create a queue before the second work stations
    queue2 <-
      runEventInStartTime $
      newFCFSQueue queueMaxCount2
    -- create the first work stations (servers)
    workStation1s <- forM [1 .. workStationCount1] $ \_ ->
      newWorkStationExponential meanProcessingTime1
    -- create the second work stations (servers)
    workStation2s <- forM [1 .. workStationCount2] $ \_ ->
      newWorkStationExponential meanProcessingTime2
    -- processor for the queue before the first work station
    let queueProcessor1 =
          queueProcessor
          (\a -> liftEvent $ enqueueOrLost_ queue1 a)
          (dequeue queue1)
    -- processor for the queue before the second work station
    let queueProcessor2 =
          queueProcessor
          (enqueue queue2)
          (dequeue queue2)
    -- the entire processor from input to output
    let entireProcessor =
          queueProcessor1 >>>
          processorParallel (map serverProcessor workStation1s) >>>
          -- processorSeq (map serverProcessor workStation1s) >>>
          queueProcessor2 >>>
          processorParallel (map serverProcessor workStation2s) >>>
          -- processorSeq (map serverProcessor workStation2s) >>>
          arrivalTimerProcessor arrivalTimer
    -- start simulating the model
    runProcessInStartTime $
      sinkStream $ runProcessor entireProcessor inputStream
    -- return the simulation results
    return $
      results
      [resultSource
       "queue1" "Queue no. 1"
       queue1,
       --
       resultSource
       "workStation1s" "Work Stations of line no. 1"
       workStation1s,
       --
       resultSource
       "queue2" "Queue no. 2"
       queue2,
       --
       resultSource
       "workStation2s" "Work Stations of line no. 2"
       workStation2s,
       --
       resultSource
       "arrivalTimer" "The arrival timer"
       arrivalTimer]
  
  modelSummary :: Simulation Results
  modelSummary =
    fmap resultSummary model
  
  main =
    printSimulationResultsInStopTime
    printResultSourceInEnglish
    -- model specs
    modelSummary specs
  