module Simulation.Aivika.Trans.Resource.Base
       (
        FCFSResource,
        LCFSResource,
        SIROResource,
        PriorityResource,
        Resource,
        
        newFCFSResource,
        newFCFSResourceWithMaxCount,
        newLCFSResource,
        newLCFSResourceWithMaxCount,
        newSIROResource,
        newSIROResourceWithMaxCount,
        newPriorityResource,
        newPriorityResourceWithMaxCount,
        newResource,
        newResourceWithMaxCount,
        
        resourceStrategy,
        resourceMaxCount,
        resourceCount,
        
        requestResource,
        requestResourceWithPriority,
        tryRequestResourceWithinEvent,
        releaseResource,
        releaseResourceWithinEvent,
        usingResource,
        usingResourceWithPriority,
        
        incResourceCount,
        decResourceCount) where
import Control.Monad
import Control.Monad.Trans
import Control.Exception
import Simulation.Aivika.Trans.Exception
import Simulation.Aivika.Trans.Ref.Base
import Simulation.Aivika.Trans.DES
import Simulation.Aivika.Trans.Internal.Specs
import Simulation.Aivika.Trans.Internal.Simulation
import Simulation.Aivika.Trans.Internal.Event
import Simulation.Aivika.Trans.Internal.Cont
import Simulation.Aivika.Trans.Internal.Process
import Simulation.Aivika.Trans.QueueStrategy
type FCFSResource m = Resource m FCFS
type LCFSResource m = Resource m LCFS
type SIROResource m = Resource m SIRO
type PriorityResource m = Resource m StaticPriorities
data Resource m s = 
  Resource { resourceStrategy :: s,
             
             resourceMaxCount :: Maybe Int,
             
             
             resourceCountRef :: Ref m Int, 
             resourceWaitList :: StrategyQueue m s (FrozenCont m ()) }
newFCFSResource :: MonadDES m
                   => Int
                   
                   -> Simulation m (FCFSResource m)
newFCFSResource = newResource FCFS
newFCFSResourceWithMaxCount :: MonadDES m
                               => Int
                               
                               -> Maybe Int
                               
                               -> Simulation m (FCFSResource m)
newFCFSResourceWithMaxCount = newResourceWithMaxCount FCFS
newLCFSResource :: MonadDES m
                   => Int
                   
                   -> Simulation m (LCFSResource m)
newLCFSResource = newResource LCFS
newLCFSResourceWithMaxCount :: MonadDES m
                               => Int
                               
                               -> Maybe Int
                               
                               -> Simulation m (LCFSResource m)
newLCFSResourceWithMaxCount = newResourceWithMaxCount LCFS
newSIROResource :: (MonadDES m, QueueStrategy m SIRO)
                   => Int
                   
                   -> Simulation m (SIROResource m)
newSIROResource = newResource SIRO
newSIROResourceWithMaxCount :: (MonadDES m, QueueStrategy m SIRO)
                               => Int
                               
                               -> Maybe Int
                               
                               -> Simulation m (SIROResource m)
newSIROResourceWithMaxCount = newResourceWithMaxCount SIRO
newPriorityResource :: (MonadDES m, QueueStrategy m StaticPriorities)
                       => Int
                       
                       -> Simulation m (PriorityResource m)
newPriorityResource = newResource StaticPriorities
newPriorityResourceWithMaxCount :: (MonadDES m, QueueStrategy m StaticPriorities)
                                   => Int
                                   
                                   -> Maybe Int
                                   
                                   -> Simulation m (PriorityResource m)
newPriorityResourceWithMaxCount = newResourceWithMaxCount StaticPriorities
newResource :: (MonadDES m, QueueStrategy m s)
               => s
               
               -> Int
               
               -> Simulation m (Resource m s)
newResource s count =
  Simulation $ \r ->
  do when (count < 0) $
       throwComp $
       SimulationRetry $
       "The resource count cannot be negative: " ++
       "newResource."
     countRef <- invokeSimulation r $ newRef count
     waitList <- invokeSimulation r $ newStrategyQueue s
     return Resource { resourceStrategy = s,
                       resourceMaxCount = Just count,
                       resourceCountRef = countRef,
                       resourceWaitList = waitList }
newResourceWithMaxCount :: (MonadDES m, QueueStrategy m s)
                           => s
                           
                           -> Int
                           
                           -> Maybe Int
                           
                           -> Simulation m (Resource m s)
newResourceWithMaxCount s count maxCount =
  Simulation $ \r ->
  do when (count < 0) $
       throwComp $
       SimulationRetry $
       "The resource count cannot be negative: " ++
       "newResourceWithMaxCount."
     case maxCount of
       Just maxCount | count > maxCount ->
         throwComp $
         SimulationRetry $
         "The resource count cannot be greater than " ++
         "its maximum value: newResourceWithMaxCount."
       _ ->
         return ()
     countRef <- invokeSimulation r $ newRef count
     waitList <- invokeSimulation r $ newStrategyQueue s
     return Resource { resourceStrategy = s,
                       resourceMaxCount = maxCount,
                       resourceCountRef = countRef,
                       resourceWaitList = waitList }
resourceCount :: MonadDES m => Resource m s -> Event m Int
resourceCount r =
  Event $ \p -> invokeEvent p $ readRef (resourceCountRef r)
requestResource :: (MonadDES m, EnqueueStrategy m s)
                   => Resource m s 
                   
                   -> Process m ()
requestResource r =
  Process $ \pid ->
  Cont $ \c ->
  Event $ \p ->
  do a <- invokeEvent p $ readRef (resourceCountRef r)
     if a == 0 
       then do c <- invokeEvent p $
                    freezeContReentering c () $
                    invokeCont c $
                    invokeProcess pid $
                    requestResource r
               invokeEvent p $
                 strategyEnqueue (resourceWaitList r) c
       else do let a' = a  1
               a' `seq` invokeEvent p $ writeRef (resourceCountRef r) a'
               invokeEvent p $ resumeCont c ()
requestResourceWithPriority :: (MonadDES m, PriorityQueueStrategy m s p)
                               => Resource m s
                               
                               -> p
                               
                               -> Process m ()
requestResourceWithPriority r priority =
  Process $ \pid ->
  Cont $ \c ->
  Event $ \p ->
  do a <- invokeEvent p $ readRef (resourceCountRef r)
     if a == 0 
       then do c <- invokeEvent p $
                    freezeContReentering c () $
                    invokeCont c $
                    invokeProcess pid $
                    requestResourceWithPriority r priority
               invokeEvent p $
                 strategyEnqueueWithPriority (resourceWaitList r) priority c
       else do let a' = a  1
               a' `seq` invokeEvent p $ writeRef (resourceCountRef r) a'
               invokeEvent p $ resumeCont c ()
releaseResource :: (MonadDES m, DequeueStrategy m s)
                   => Resource m s
                   
                   -> Process m ()
releaseResource r = 
  Process $ \_ ->
  Cont $ \c ->
  Event $ \p ->
  do invokeEvent p $ releaseResourceWithinEvent r
     invokeEvent p $ resumeCont c ()
releaseResourceWithinEvent :: (MonadDES m, DequeueStrategy m s)
                              => Resource m s
                              
                              -> Event m ()
releaseResourceWithinEvent r =
  Event $ \p ->
  do a <- invokeEvent p $ readRef (resourceCountRef r)
     let a' = a + 1
     case resourceMaxCount r of
       Just maxCount | a' > maxCount ->
         throwComp $
         SimulationRetry $
         "The resource count cannot be greater than " ++
         "its maximum value: releaseResourceWithinEvent."
       _ ->
         return ()
     f <- invokeEvent p $
          strategyQueueNull (resourceWaitList r)
     if f 
       then a' `seq` invokeEvent p $ writeRef (resourceCountRef r) a'
       else do c <- invokeEvent p $
                    strategyDequeue (resourceWaitList r)
               c <- invokeEvent p $ unfreezeCont c
               case c of
                 Nothing ->
                   invokeEvent p $ releaseResourceWithinEvent r
                 Just c  ->
                   invokeEvent p $ enqueueEvent (pointTime p) $ resumeCont c ()
tryRequestResourceWithinEvent :: MonadDES m
                                 => Resource m s
                                 
                                 -> Event m Bool
tryRequestResourceWithinEvent r =
  Event $ \p ->
  do a <- invokeEvent p $ readRef (resourceCountRef r)
     if a == 0 
       then return False
       else do let a' = a  1
               a' `seq` invokeEvent p $ writeRef (resourceCountRef r) a'
               return True
               
usingResource :: (MonadDES m, EnqueueStrategy m s)
                 => Resource m s
                 
                 -> Process m a
                 
                 -> Process m a
                 
usingResource r m =
  do requestResource r
     finallyProcess m $ releaseResource r
usingResourceWithPriority :: (MonadDES m, PriorityQueueStrategy m s p)
                             => Resource m s
                             
                             
                             -> p
                             
                             -> Process m a
                             
                             -> Process m a
                             
usingResourceWithPriority r priority m =
  do requestResourceWithPriority r priority
     finallyProcess m $ releaseResource r
incResourceCount :: (MonadDES m, DequeueStrategy m s)
                    => Resource m s
                    
                    -> Int
                    
                    -> Event m ()
incResourceCount r n
  | n < 0     = throwEvent $ SimulationRetry "The increment cannot be negative: incResourceCount"
  | n == 0    = return ()
  | otherwise =
    do releaseResourceWithinEvent r
       incResourceCount r (n  1)
decResourceCount :: (MonadDES m, EnqueueStrategy m s)
                    => Resource m s
                    
                    -> Int
                    
                    -> Process m ()
decResourceCount r n
  | n < 0     = throwProcess $ SimulationRetry "The decrement cannot be negative: decResourceCount"
  | n == 0    = return ()
  | otherwise =
    do requestResource r
       decResourceCount r (n  1)