{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE FlexibleInstances #-} module Schedule ( schedule ) where import Data.Map ((!), Map, elems, lookupMin, mapWithKey, partition, size) import qualified Data.Map as Map (filter) import Data.Time (Day, addDays) import Medicine (Medicine(..), MedicineName, Pharmacy) import Timeline (State(..)) import YAML ((.:), Value(..), YAML(..)) data ScheduleType = NextBox | Provision | OutOfStock deriving (Eq, Show) instance YAML ScheduleType where toYAML NextBox = Simple "start a new box" toYAML Provision = Simple "go to the pharmacy get some more" toYAML OutOfStock = Simple "out of it" data Schedule = Schedule { days :: Integer , date :: Day , scheduleType :: ScheduleType } deriving (Show) instance YAML (Map MedicineName Schedule) where toYAML schedules = Object $ outOfStocks ++ nextDay where (urgent, normal) = partition ((== OutOfStock) . scheduleType) schedules outOfStocks = if size urgent > 0 then ["Warning ! Out of" .: (Object . timing <$> urgent)] else [] nextDay = maybe [] (scheduleNext normal) (snd <$> lookupMin normal) scheduleNext :: Map MedicineName Schedule -> Schedule -> [(String, Value)] scheduleNext schedules aSchedule = ["Schedule" .: (Object $ timing aSchedule ++ ["TODO" .: (scheduleType <$> schedules)] )] timing :: Schedule -> [(String, Value)] timing aSchedule = ["On" .: date aSchedule, "In" .: (show (days aSchedule) ++ " days")] zipWithKey :: Ord k => (k -> a -> b -> c) -> Map k a -> Map k b -> Map k c zipWithKey f as bs = mapWithKey (\k -> f k $ as ! k) bs scheduleByMedicine :: Pharmacy -> Day -> MedicineName -> Float -> Float -> Schedule scheduleByMedicine pharmacy day medicineName stockLeft rate = let (scheduleType, amount) = typeAndAmount stockLeft minStock leftInBox in let days = truncate (amount / rate) in Schedule {days, date = days `addDays` day, scheduleType} where (Medicine {minStock, content}) = pharmacy ! medicineName truncateF = fromInteger . truncate leftInBox = stockLeft - (truncateF (stockLeft / content) * content) typeAndAmount :: Float -> Float -> Float -> (ScheduleType, Float) typeAndAmount stockLeft minStock leftInBox = if secureLeft > 0 then if secureLeft < leftInBox then (Provision, secureLeft) else (NextBox, leftInBox) else (OutOfStock, stockLeft) where secureLeft = stockLeft - minStock schedule :: Pharmacy -> State -> Map MedicineName Schedule schedule pharmacy (State {day, stock, consumptionRate}) = Map.filter keep schedules where schedules = zipWithKey (scheduleByMedicine pharmacy day) stock consumptionRate minDays = minimum . elems $ days <$> schedules keep aSchedule = days aSchedule == minDays || scheduleType aSchedule == OutOfStock