{-# OPTIONS_GHC -fno-warn-overlapping-patterns #-} module Utils.Occurences ( normalizeOccurences ) where import ClassyPrelude import Model.Types import Utils import Utils.Lens import Control.Monad.Trans.Reader (runReader, Reader) import Control.Monad.Trans.Except (ExceptT, throwE, runExceptT) import qualified Data.Set as Set import Data.Time import Data.Time.Calendar.WeekDate normalizeOccurences :: Occurences -> Occurences -- ^ -- -- - Removes unnecessary exceptions -- - Merges overlapping schedules normalizeOccurences initial | Left new <- runReader (runExceptT go) initial = normalizeOccurences new | otherwise = initial where go :: ExceptT Occurences (Reader Occurences) () -- Find some inconsistency and `throwE` a version without it go = do scheduled <- view _occurencesScheduled forM_ scheduled $ \case a@ScheduleWeekly{} -> do let merge b@ScheduleWeekly{} | scheduleDayOfWeek a == scheduleDayOfWeek b -- b starts during a , scheduleStart a <= scheduleStart b , scheduleEnd a >= scheduleStart b = Just $ ScheduleWeekly (scheduleDayOfWeek a) (scheduleStart a) ((max `on` scheduleEnd) a b) | scheduleDayOfWeek a == scheduleDayOfWeek b -- b ends during a , scheduleStart a <= scheduleEnd b , scheduleEnd a >= scheduleEnd b = Just $ ScheduleWeekly (scheduleDayOfWeek a) ((min `on` scheduleStart) a b) (scheduleEnd a) | otherwise = Nothing merge _ = Nothing merges <- views _occurencesScheduled $ mapMaybe (\b -> (,) <$> pure b <*> merge b) . Set.toList . Set.delete a case merges of [] -> return () ((b, merged) : _) -> throwE =<< asks (over _occurencesScheduled $ Set.insert merged . Set.delete b . Set.delete a) exceptions <- view _occurencesExceptions forM_ exceptions $ \case needle@ExceptNoOccur{..} -> do let LocalTime{..} = exceptTime (_, _, toEnum . (`mod` 7) -> localWeekDay) = toWeekDate localDay needed <- views _occurencesScheduled . any $ \case ScheduleWeekly{..} -> and [ scheduleDayOfWeek == localWeekDay , scheduleStart <= localTimeOfDay , localTimeOfDay <= scheduleEnd ] unless needed $ throwE =<< asks (over _occurencesExceptions $ Set.delete needle) needle@ExceptOccur{..} -> do let (_, _, toEnum . (`mod` 7) -> localWeekDay) = toWeekDate exceptDay -- | Does this ExceptNoOccur target within needle? withinNeedle ExceptNoOccur{..} = LocalTime exceptDay exceptStart <= exceptTime && exceptTime <= LocalTime exceptDay exceptEnd withinNeedle _ = False needed <- views _occurencesScheduled . none $ \case ScheduleWeekly{..} -> and [ scheduleDayOfWeek == localWeekDay , scheduleStart == exceptStart , scheduleEnd == exceptEnd ] unless needed $ throwE =<< asks (over _occurencesExceptions $ Set.filter (not . withinNeedle) . Set.delete needle)