{-# LANGUAGE Arrows, FlexibleContexts #-}

-- Example: In this example, the operations of a quarry are modeled.
--
-- It is described in different sources [1, 2]. So, this is chapter 10 of [2] and section 5.16 of [1].
--
-- [1] A. Alan B. Pritsker, Simulation with Visual SLAM and AweSim, 2nd ed.
-- [2] Труб И.И., Объектно-ориентированное моделирование на C++: Учебный курс. - СПб.: Питер, 2006
-- 
-- In this example, the operations of a quarry are modeled. In the quarry,
-- trucks deliver ore from three shovels to a crusher. A truck always
-- returns to its assigned shovel after dumping a load at the crusher.
-- There are two different truck sizes in use, twenty-ton and fifty-ton.
-- The size of the truck affects its loading time at the shovel, travel
-- time to the crusher, dumping time at the crusher and return trip time
-- from the crusher back to the appropriate shovel. For the twenty-ton
-- trucks, there loading, travel, dumping and return trip times are:
-- exponentially distributed with a mean 5; a constant 2.5; exponentially
-- distributed with mean 2; and a constant 1.5. The corresponding times
-- for the fifty-ton trucks are: exponentially distributed with mean 10;
-- a constant 3; exponentially distributed with mean 4; and a constant 2.
-- To each shovel is assigned two twenty-ton trucks are one fifty-ton truck.
-- The shovel queues are all ranked on a first-in, first-out basis.
-- The crusher queue is ranked on truck size, largest trucks first.
-- It is desired to analyze this system over 480 time units to determine
-- the utilization and queue lengths associated with the shovels and crusher.

import Control.Monad
import Control.Monad.Trans
import Control.Category

import Simulation.Aivika.Trans
import qualified Simulation.Aivika.Trans.Queue.Infinite as IQ
import Simulation.Aivika.IO

type DES = IO

-- | The simulation specs.
specs = Specs { spcStartTime = 0.0,
                spcStopTime = 480.0,
                spcDT = 0.1,
                spcMethod = RungeKutta4,
                spcGeneratorType = SimpleGenerator }

-- | The average loading time for twenty-ton truck
avgLoadingTime20 = 5

-- | A constant travel time for twenty-ton truck
travelTime20 = 2.5

-- | The average dumping time for twenty-ton truck
avgDumpingTime20 = 2

-- | A constant return trip time for twenty-ton truck
returnTripTime20 = 1.5

-- | A priority of the twenty-ton truck (less is higher)
crushingPriority20 = 2

-- | The average loading time for fifty-ton truck
avgLoadingTime50 = 10

-- | A constant travel time for fifty-ton truck
travelTime50 = 3

-- | The average dumping time for fifty-ton truck
avgDumpingTime50 = 4

-- | A constant return trip time for fifty-ton truck
returnTripTime50 = 2

-- | A priority of the fifty-ton truck (less is higher)
crushingPriority50 = 1

-- | It models a truck assigned to some queue.
data Truck =
  Truck { truckQueue :: TruckQueue,
          -- ^ a queue to which the truck is assigned
          truckTonSize :: TruckTonSize,
          -- ^ the truck ton size
          truckAvgLoadingTime :: Double,
          -- ^ the average loading time
          truckTravelTime :: Double,
          -- ^ a constant travel time
          truckCrushingPriority :: Double,
          -- ^ a priority for crushing (less is higher)
          truckAvgDumpingTime :: Double,
          -- ^ the average dumping time
          truckReturnTripTime :: Double
          -- ^ a constant return trip time
        }

-- | It defines the truck ton size
data TruckTonSize = TwentyTonSize | FiftyTonSize

-- | Specifies a queue to which the truck is assigned
data TruckQueue = TruckQueue1 | TruckQueue2 | TruckQueue3

-- | Return a truck assigned to the specified queue with the given ton size.
truck :: TruckQueue -> TruckTonSize -> Truck
truck tq TwentyTonSize =
  Truck { truckQueue = tq,
          truckTonSize = TwentyTonSize,
          truckAvgLoadingTime = avgLoadingTime20,
          truckTravelTime = travelTime20,
          truckCrushingPriority = crushingPriority20,
          truckAvgDumpingTime = avgDumpingTime20,
          truckReturnTripTime = returnTripTime20 }
truck tq FiftyTonSize =
  Truck { truckQueue = tq,
          truckTonSize = FiftyTonSize,
          truckAvgLoadingTime = avgLoadingTime50,
          truckTravelTime = travelTime50,
          truckCrushingPriority = crushingPriority50,
          truckAvgDumpingTime = avgDumpingTime50,
          truckReturnTripTime = returnTripTime50 }
  
model :: Simulation DES (Results DES)
model = do
  -- create a queue for the first shovel
  shovelQueue1 <-
    runEventInStartTime IQ.newFCFSQueue
  -- create another queue for the second shovel
  shovelQueue2 <-
    runEventInStartTime IQ.newFCFSQueue
  -- create a queue for the thrid shovel
  shovelQueue3 <-
    runEventInStartTime IQ.newFCFSQueue
  -- add initial trucks to the queue
  let initShovelQueue q tq =
        do IQ.enqueue q $ truck tq TwentyTonSize
           IQ.enqueue q $ truck tq TwentyTonSize
           IQ.enqueue q $ truck tq FiftyTonSize
  -- initiate the three shovel queues
  runEventInStartTime $
    do initShovelQueue shovelQueue1 TruckQueue1
       initShovelQueue shovelQueue2 TruckQueue2
       initShovelQueue shovelQueue3 TruckQueue3
  -- create a priority queue for the crusher
  crusherQueue <-
    runEventInStartTime IQ.newPriorityQueue
  -- define how the specified truck travels from the shovel to the crusher
  let truckTravel t =
        spawnProcess $
        do holdProcess (truckTravelTime t)
           liftEvent $
             IQ.enqueueWithStoringPriority crusherQueue (truckCrushingPriority t) t
  -- define how the specified truck returns to the queue
  let truckReturnTrip t =
        spawnProcess $
        do holdProcess (truckReturnTripTime t)
           let q = case truckQueue t of
                 TruckQueue1 -> shovelQueue1
                 TruckQueue2 -> shovelQueue2
                 TruckQueue3 -> shovelQueue3
           liftEvent $
             IQ.enqueue q t
  -- utilise the crusher's activity
  let utiliseCrusher q t =
        do randomExponentialProcess_ $
             truckAvgDumpingTime t
           return t
  -- utilise the shovel's activity
  let utiliseShovel q t =
        do randomExponentialProcess_ $
             truckAvgLoadingTime t
           return t
  -- create shovel activities
  shovelAct1 <-
    newActivity $ utiliseShovel shovelQueue1
  shovelAct2 <-
    newActivity $ utiliseShovel shovelQueue2
  shovelAct3 <-
    newActivity $ utiliseShovel shovelQueue3
  -- create the crusher's activity
  crusherAct <-
    newActivity $ utiliseCrusher crusherQueue
  -- define how we should iterate the crusher
  let crusherNet act q =
        proc () ->
        do t  <- arrNet (const $ IQ.dequeue q) -< ()
           t' <- activityNet act  -< t
           arrNet truckReturnTrip -< t'
  let shovelNet act q =
        proc () ->
        do t  <- arrNet (const $ IQ.dequeue q) -< ()
           t' <- activityNet act -< t
           arrNet truckTravel    -< t'
  -- start processing the cursher's queue
  runProcessInStartTime $
    iterateNet (crusherNet crusherAct crusherQueue) ()
  -- start processing the shovel queues
  runProcessInStartTime $
    iterateNet (shovelNet shovelAct1 shovelQueue1) ()
  runProcessInStartTime $
    iterateNet (shovelNet shovelAct2 shovelQueue2) ()
  runProcessInStartTime $
    iterateNet (shovelNet shovelAct3 shovelQueue3) ()
  -- return the simulation results in start time
  return $
    results
    [resultSource
     "shovelQueue" "the shovel's queue"
     [shovelQueue1, shovelQueue2, shovelQueue3],
     --
     resultSource
     "crusherQueue" "the crusher's queue"
     crusherQueue,
     --
     resultSource
     "shovelActvty" "the shovel's activity"
     [shovelAct1, shovelAct2, shovelAct3],
     --
     resultSource
     "crusherActvty" "the crusher's activity"
     crusherAct]

main =
  printSimulationResultsInStopTime
  printResultSourceInEnglish
  (fmap resultSummary model) specs