module Data.Holiday.USA
  ( Holiday(Holiday)
  , getHoliday
  ) where

import qualified Data.Time as Time
import qualified Data.Time.Calendar.WeekDate as Time

-- | Holiday means a "real" holiday day like you may not go to work on that day.
-- Like Mother's day isn't considered a holiday because it's on Sundays.
data Holiday =
  Holiday
    { holiday_day :: Time.Day -- ^ The actual day of the holiday
    , holiday_name :: String -- ^ The name of the holiday according to Wikipedia
    }
  deriving (Eq, Show)

-- | Returns a Holiday if the given date is
--
-- Examples:
--
-- >>> let christmas = Time.fromGregorian 2019 12 25
-- >>> getHoliday christmas
-- Just (Holiday {holiday_day = 2019-12-25, holiday_name = "Christmas"})
--
-- >>> let noHoliday = Time.fromGregorian 2019 12 20
-- >>> getHoliday noHoliday
-- Nothing
getHoliday :: Time.Day -> Maybe Holiday
getHoliday d
  | isChristmas d = Just (Holiday d "Christmas")
  | isThanksgiving d = Just (Holiday d "Thanksgiving")
  | isIndependence d = Just (Holiday d "Independence Day")
  | isNewYearsEve d = Just (Holiday d "New Year's Eve")
  | isMemorialDay d = Just (Holiday d "Memorial Day")
  | isLaborDay d = Just (Holiday d "Labor Day")
  | isMartinLutherKingJrDay d = Just (Holiday d "Martin Luther King Jr. Day")
  | otherwise = Nothing
  where
    isChristmas :: Time.Day -> Bool
    isChristmas _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 12 && day == 25
    isThanksgiving :: Time.Day -> Bool
    isThanksgiving _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 11 && 22 <= day && day <= 28 && getDayOfWeek _d == Thursday
    isIndependence :: Time.Day -> Bool
    isIndependence _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 7 && day == 4
    isNewYearsEve :: Time.Day -> Bool
    isNewYearsEve _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 12 && day == 31
    isMemorialDay _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 5 && 25 <= day && day <= 31 && getDayOfWeek _d == Monday
    isLaborDay _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 9 && 1 <= day && day <= 7 && getDayOfWeek _d == Monday
    isMartinLutherKingJrDay :: Time.Day -> Bool
    isMartinLutherKingJrDay _d =
      let (_, month, day) = Time.toGregorian _d
       in month == 1 && 15 <= day && day <= 21 && getDayOfWeek _d == Monday

data DayOfWeek
  = Monday
  | Tuesday
  | Wednesday
  | Thursday
  | Friday
  | Saturday
  | Sunday
  deriving (Enum, Eq, Show)

-- | This should be removed when 'time' package has been updated to 1.9.3+
getDayOfWeek :: Time.Day -> DayOfWeek
getDayOfWeek d =
  let (_, _, dow) = Time.toWeekDate d
   in toEnum (dow - 1)