-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later

module Handler.Utils.Occurrences
  ( occurrencesWidget
  , occurrencesBounds
  , occurrencesAddBusinessDays
  ) where

import Import

import qualified Data.Set as Set

import Utils.Holidays (isWeekend)
import Utils.Occurrences

import Handler.Utils.DateTime


occurrencesWidget :: Occurrences -> Widget
occurrencesWidget (normalizeOccurrences -> Occurrences{..}) = do
  let occurrencesScheduled' = flip map (Set.toList occurrencesScheduled) $ \case
        ScheduleWeekly{..} -> do
          scheduleStart' <- formatTime SelFormatTime scheduleStart
          scheduleEnd' <- formatTime SelFormatTime scheduleEnd
          $(widgetFile "widgets/occurrence/cell/weekly")
      occurrencesExceptions' = flip map (Set.toList occurrencesExceptions) $ \case
        ExceptOccur{..} -> do
          exceptStart' <- formatTime SelFormatDateTime (LocalTime exceptDay exceptStart)
          exceptEnd'   <- formatTime SelFormatTime     exceptEnd
          $(widgetFile "widgets/occurrence/cell/except-occur")
        ExceptNoOccur{..} -> do
          exceptTime' <- formatTime SelFormatDateTime exceptTime
          $(widgetFile "widgets/occurrence/cell/except-no-occur")
  $(widgetFile "widgets/occurrence/cell")

-- | Get bounds for an Occurrences 
occurrencesBounds :: Term -> Occurrences -> (Maybe Day, Maybe Day)
occurrencesBounds Term{..} Occurrences{..} = (Set.lookupMin occDays, Set.lookupMax occDays)    
  where 
    occDays = (scdDays <> plsDays) \\ excDays -- (excDays <> termHolidays term) -- TODO: should holidays be exluded here? Probably not, as they can be added as exceptions already

    scdDays = Set.foldr getOccDays mempty occurrencesScheduled
    (plsDays,excDays) = Set.foldr getExcDays mempty occurrencesExceptions

    getExcDays :: OccurrenceException -> (Set Day, Set Day) -> (Set Day, Set Day)
    getExcDays ExceptNoOccur{exceptTime} (occ,exc) = (occ, Set.insert (localDay exceptTime) exc)
    getExcDays ExceptOccur{exceptDay}    (occ,exc) = (Set.insert exceptDay occ, exc)

    getOccDays :: OccurrenceSchedule -> Set Day -> Set Day
    getOccDays ScheduleWeekly{scheduleDayOfWeek=wday} = Set.union $ daysOfWeekBetween (termLectureStart,termLectureEnd) wday

occurrencesAddBusinessDays :: Term -> (Day,Day) -> Occurrences -> Occurrences
occurrencesAddBusinessDays Term{..} (dayOld, dayNew) Occurrences{..} = Occurrences newSchedule newExceptions
  where
    newSchedule = Set.map switchDayOfWeek occurrencesScheduled
    dayDiff = diffDays dayNew dayOld

    offDays = Set.fromList $ termHolidays <> weekends
    weekends = [d | d <- [(min termLectureStart termStart)..(max termEnd termLectureEnd)], isWeekend d]    

    switchDayOfWeek :: OccurrenceSchedule -> OccurrenceSchedule
    switchDayOfWeek os | 0 == dayDiff `mod` 7 = os
    switchDayOfWeek os@ScheduleWeekly{scheduleDayOfWeek=wday} = os{scheduleDayOfWeek= toEnum (fromIntegral dayDiff + fromEnum wday)}

    newExceptions = snd $ Set.foldl' advanceExceptions (dayDiff,mempty) occurrencesExceptions

    -- we assume that instance Ord OccurrenceException is ordered chronologically
    advanceExceptions :: (Integer, Set OccurrenceException) -> OccurrenceException -> (Integer, Set OccurrenceException)
    advanceExceptions (offset, acc) ex
        | ed `Set.notMember` offDays   -- skip term-holidays and weekends, unless the original day was a holiday or weekend
        , nd `Set.member`    offDays
          = advanceExceptions (succ offset, acc) ex
        | otherwise
          = (offset, Set.insert (setDayOfOccurrenceException nd ex) acc)
      where 
        ed = dayOfOccurrenceException ex
        nd = addDays offset ed
