module Time.SCalendar.Operations ( augmentCalendar , isQuantityAvailable , isReservAvailable , reservePeriod' , reservePeriod , reserveManyPeriods , reserveManyPeriods' , cancelPeriod , cancelManyPeriods , periodReport ) where import Data.Maybe (isNothing) import Time.SCalendar.Zippers import Time.SCalendar.Types import Data.Time (UTCTime(..), toGregorian) import Control.Monad (guard) import Time.SCalendar.Internal import qualified Data.Set as S ( null , size , difference , isSubsetOf , union , unions ) -- | Given an SCalendar of size 2^n, this function increases its size k times, that is, -- 2^(n+k). The new SCalendar is properly updated up to its root so that it will render -- the same results as the previous one. For example, given an SCalendar `c` of size 2^5=32, -- 'augmentCalendar c 3' would produce a new SCalendar of size 2^(5+3)=256. augmentCalendar :: SCalendar -- ^ SCalendar to be augmented. -> Int -- ^ Number of times by which the SCalendar will be augmented. -> Maybe SCalendar augmentCalendar _ k | k <= 0 = Nothing augmentCalendar scal k = do let interval = getInterval $ calendar scal (from, to) = (getFrom interval, getTo interval) (UTCTime gregDay _) = from (year, month, day) = toGregorian gregDay newSize = daysBetween from to * (2^k) largerCal <- createCalendar year month day newSize (_, bs) <- goToNode interval largerCal updatedCal <- updateQ (calendar scal, bs) (root, _) <- upToRoot updatedCal return $ SCalendar (calUnits scal) root -- | Given a quantity, this function determines if it is available in a TimePeriod for a -- specific SCalendar. Thus, it does not take into account the particular resources whose -- availability wants to be determined: it is only concerned with the availabilty of a quantity -- in a specific SCalendar. isQuantityAvailable :: Int -- ^ Quantity of resources. -> TimePeriod -- ^ TimePeriod over which we want to determine the availability of -- the quantity. -> SCalendar -- ^ SCalendar over which we want to determine the availability of -- the quantity in a Given TimePeriod. -> Bool isQuantityAvailable quant interval scal | S.null (calUnits scal) = False | quant <= 0 = False | quant > S.size (calUnits scal) = False | not $ intervalFitsCalendar interval (calendar scal) = False | otherwise = checkQuantAvailability (toTimeUnit interval) quant (calUnits scal) (calendar scal, []) -- | Given a Reservation, this function determines if it is available in a SCalendar. A -- Reservation is the product of a set of identifiers which point to reservable resources -- and a TimePeriod over which those resources are to be reserved. Thus, this function -- checks if that particular set of resources is available for a TimePeriod in the given SCalendar. isReservAvailable :: Reservation -> SCalendar -> Bool isReservAvailable reservation scal | S.null (calUnits scal) = False | not $ S.isSubsetOf (reservUnits reservation) (calUnits scal) = False | not $ intervalFitsCalendar (reservPeriod reservation) (calendar scal) = False | otherwise = checkReservAvailability reservation (calUnits scal) (calendar scal, []) -- | This function introduces a new Reservation in a Calendar. Note that since no availability check -- is performed before introducing the Reservation, here we use a plain Calendar. Thus this function -- is useful to introduce Reservations without any constraint, but that's why it must be used carefully -- since information can be lost due to the usage of the union set-operation to update the Q and QN sets -- in the Calendar. reservePeriod' :: Reservation -> Calendar -> Maybe Calendar reservePeriod' reservation calendar = do let interval = (toTimeUnit . reservPeriod) reservation tmNodes <- topMostNodes interval calendar let tmIntervals = fmap getZipInterval tmNodes updateCalendar tmIntervals (reservUnits reservation) calendar (\x y -> Just $ S.union x y) -- | This function is like reservePeriod' but adds a list of Reservations without any availabilty check. reserveManyPeriods' :: [Reservation] -> Calendar -> Maybe Calendar reserveManyPeriods' [] calendar = Just calendar reserveManyPeriods' (reservation:rs) calendar = do updatedCalendar <- addReservation reservation calendar reserveManyPeriods' rs updatedCalendar where addReservation res cal | isNothing maybeCalendar = Just cal | otherwise = maybeCalendar where maybeCalendar = reservePeriod' res cal -- | This function introduces a new Reservation in a SCalendar applying an availability check. This means -- that if the reservation conflicts with others already made in the SCalendar, it will no be introduced. -- Thus this function takes into account the set of reservable identifiers for the SCalendar to calculate -- the subset of available ones and introduce the Reservation if possible. reservePeriod :: Reservation -> SCalendar -> Maybe SCalendar reservePeriod reservation scalendar | not $ isReservAvailable reservation scalendar = Nothing reservePeriod reservation scal = do updatedCalendar <- reservePeriod' reservation (calendar scal) return $ SCalendar (calUnits scal) updatedCalendar -- | This function is like reservePeriod but introduces several Reservations at once. It is important to note -- that if a Reservation in the list conflicts with others already made in the SCalendar, it will be excluded. -- Thus the order of the Reservations in the list matters, since if one Reservation passes the availability check -- but the next one does not, then latter will be excluded. reserveManyPeriods :: [Reservation] -> SCalendar -> Maybe SCalendar reserveManyPeriods [] calendar = Just calendar reserveManyPeriods (reservation:rs) calendar = do updatedCalendar <- addReservation reservation calendar reserveManyPeriods rs updatedCalendar where addReservation res uCal | isNothing maybeCalendar = Just uCal | otherwise = maybeCalendar where maybeCalendar = reservePeriod res uCal -- | This function removes reserved identifiers in a Calendar according to the Set of identifiers and TimePeriod -- specified in the Cancellation. Thus a Cancellation only affects the nodes whose upper or lower bounds are -- included in the TimePeriod of the Cancellation. cancelPeriod :: Cancellation -> Calendar -> Maybe Calendar cancelPeriod cancellation calendar = do tmNodes <- topMostNodes (cancPeriod cancellation) calendar let tmIntervals = fmap getZipInterval tmNodes updateCalendar tmIntervals (cancUnits cancellation) calendar diff where diff x y | not $ S.isSubsetOf y x = Nothing | otherwise = Just (S.difference x y) -- | This is like cancelPeriod but performs several Cancellations at once. cancelManyPeriods :: [Cancellation] -> Calendar -> Maybe Calendar cancelManyPeriods [] calendar = Just calendar cancelManyPeriods (cancellation:cs) calendar = do updatedCalendar <- addCancellation cancellation calendar cancelManyPeriods cs updatedCalendar where addCancellation canc cal | isNothing maybeCalendar = Just cal | otherwise = maybeCalendar where maybeCalendar = cancelPeriod canc cal -- | Given a TimePeriod and a SCalendar, this function returns a Report which summarizes important -- data about the reserved and available identifiers in that SCalendar. periodReport :: TimePeriod -> SCalendar -> Maybe Report periodReport interval scal = do guard $ intervalFitsCalendar interval (calendar scal) tmNodes <- topMostNodes (toTimeUnit interval) (calendar scal) qMaxs <- mapM getQMax tmNodes let sQMax = S.unions qMaxs return $ Report interval (calUnits scal) sQMax (S.difference (calUnits scal) sQMax)