{-# LANGUAGE TypeFamilies #-}

-- |
-- Module     : Simulation.Aivika.Trans.Resource.Preemption
-- Copyright  : Copyright (c) 2009-2017, David Sorokin <david.sorokin@gmail.com>
-- License    : BSD3
-- Maintainer : David Sorokin <david.sorokin@gmail.com>
-- Stability  : experimental
-- Tested with: GHC 8.0.1
--
-- This module defines the preemptible resource.
--
module Simulation.Aivika.Trans.Resource.Preemption (MonadResource(..)) where

import Control.Monad
import Control.Monad.Trans

import Simulation.Aivika.Trans.DES
import Simulation.Aivika.Trans.Internal.Simulation
import Simulation.Aivika.Trans.Internal.Event
import Simulation.Aivika.Trans.Internal.Process
import Simulation.Aivika.Trans.Statistics
import Simulation.Aivika.Trans.Signal

-- | A type class of monads whithin which we can create preemptible resources.
class MonadDES m => MonadResource m where

  -- | Represents a preemptible resource.
  data Resource m 

  -- | Create a new resource with the specified initial count that becomes the upper bound as well.
  newResource :: Int
                 -- ^ the initial count (and maximal count too) of the resource
                 -> Event m (Resource m)

  -- | Create a new resource with the specified initial and maximum counts,
  -- where 'Nothing' means that the resource has no upper bound.
  newResourceWithMaxCount :: Int
                             -- ^ the initial count of the resource
                             -> Maybe Int
                             -- ^ the maximum count of the resource, which can be indefinite
                             -> Event m (Resource m)

  -- | Return the current count of the resource.
  resourceCount :: Resource m -> Event m Int
  
  -- | Return the maximum count of the resource, where 'Nothing'
  -- means that the resource has no upper bound.
  resourceMaxCount :: Resource m -> Maybe Int

  -- | Return the statistics for the available count of the resource.
  resourceCountStats :: Resource m -> Event m (TimingStats Int)

  -- | Signal triggered when the 'resourceCount' property changes.
  resourceCountChanged :: Resource m -> Signal m Int

  -- | Signal triggered when the 'resourceCount' property changes.
  resourceCountChanged_ :: Resource m -> Signal m ()

  -- | Return the current utilisation count of the resource.
  resourceUtilisationCount :: Resource m -> Event m Int

  -- | Return the statistics for the utilisation count of the resource.
  resourceUtilisationCountStats :: Resource m -> Event m (TimingStats Int)

  -- | Signal triggered when the 'resourceUtilisationCount' property changes.
  resourceUtilisationCountChanged :: Resource m -> Signal m Int

  -- | Signal triggered when the 'resourceUtilisationCount' property changes.
  resourceUtilisationCountChanged_ :: Resource m -> Signal m ()

  -- | Return the current queue length of the resource.
  resourceQueueCount :: Resource m -> Event m Int

  -- | Return the statistics for the queue length of the resource.
  resourceQueueCountStats :: Resource m -> Event m (TimingStats Int)

  -- | Signal triggered when the 'resourceQueueCount' property changes.
  resourceQueueCountChanged :: Resource m -> Signal m Int

  -- | Signal triggered when the 'resourceQueueCount' property changes.
  resourceQueueCountChanged_ :: Resource m -> Signal m ()

  -- | Return the total wait time of the resource.
  resourceTotalWaitTime :: Resource m -> Event m Double

  -- | Return the statistics for the wait time of the resource.
  resourceWaitTime :: Resource m -> Event m (SamplingStats Double)

  -- | Signal triggered when the 'resourceTotalWaitTime' and 'resourceWaitTime' properties change.
  resourceWaitTimeChanged :: Resource m -> Signal m (SamplingStats Double)

  -- | Signal triggered when the 'resourceTotalWaitTime' and 'resourceWaitTime' properties change.
  resourceWaitTimeChanged_ :: Resource m -> Signal m ()

  -- | Signal triggered when one of the resource counters changes.
  resourceChanged_ :: Resource m -> Signal m ()

  -- | Request with the priority for the resource decreasing its count
  -- in case of success, otherwise suspending the discontinuous process
  -- until some other process releases the resource.
  --
  -- It may preempt another process if the latter aquired the resource before
  -- but had a lower priority. Then the current process takes an ownership of
  -- the resource.
  requestResourceWithPriority :: Resource m
                                 -- ^ the requested resource
                                 -> Double
                                 -- ^ the priority (the less value has a higher priority)
                                 -> Process m ()

  -- | Release the resource increasing its count and resuming one of the
  -- previously suspended or preempted processes as possible.
  releaseResource :: Resource m
                     -- ^ the resource to release
                     -> Process m ()

  -- | Acquire the resource with the specified priority, perform some action and
  -- safely release the resource in the end, even if the 'IOException' was raised
  -- within the action.
  usingResourceWithPriority :: Resource m
                               -- ^ the resource we are going to request for and then
                               -- release in the end
                               -> Double
                               -- ^ the priority (the less value has a higher priority)
                               -> Process m a
                               -- ^ the action we are going to apply having the resource
                               -> Process m a
                               -- ^ the result of the action

  -- | Increase the count of available resource by the specified number,
  -- invoking the awaiting and preempted processes according to their priorities
  -- as needed.
  incResourceCount :: Resource m
                      -- ^ the resource
                      -> Int
                      -- ^ the increment for the resource count
                      -> Event m ()

  -- | Decrease the count of available resource by the specified number,
  -- preempting the processes according to their priorities as needed.
  decResourceCount :: Resource m
                      -- ^ the resource
                      -> Int
                      -- ^ the decrement for the resource count
                      -> Event m ()

  -- | Alter the resource count either increasing or decreasing it by calling
  -- 'incResourceCount' or 'decResourceCount' respectively. 
  alterResourceCount :: Resource m
                        -- ^ the resource
                        -> Int
                        -- ^ a change of the resource count
                        -> Event m ()

  -- | Reset the statistics.
  resetResource :: Resource m -> Event m ()