module Data.DDate(DDateTime,
                  DDate,
                  Yold,
                  Season,
                  Day,
                  weekdays,
                  seasons,
                  fluxes,
                  holydays,
                  yold,
                  convertSeason,
                  convertDay,
                  holyday,
                  convertDateTime,
                  convertDate,
                  ddateToDDateTime,
                  ddateTimeToDDate,
                 )
  where
import Data.Dates (DateTime(DateTime), getCurrentDateTime)
import Data.Time.Calendar (isLeapYear)

-- hack to make holyday generic
data DDatable = DT DDateTime
              | D DDate

-- |The DDateTime data type. It consists of a Yold, a Season, a Day
-- |and three integers that signify the time of day.
data DDateTime = DDateTime {
  year :: Yold,
  season :: Season,
  day :: Day,
  hour :: Int,
  minute :: Int,
  second :: Int
}
  deriving (Eq, Ord)

instance Show DDateTime where
  show (DDateTime y s d h m sec) =
      show (DDate y s d) ++ ", " ++ pad(show h) ++ ":" ++
      pad (show m) ++ ":" ++ pad (show sec)
    where pad s = replicate (2 - (length s)) '0' ++ s

-- |The DDateTime data type. It consists of a Yold, a Season and a Day.
data DDate = DDate {
  y :: Yold,
  s :: Season,
  d :: Day
}

instance Show DDate where
  show (DDate y s d@(Day dayval)) =
    show d ++ ", " ++ show s ++ " the " ++ dayofmonth dayval ++
    ", " ++ show y ++ " YOLD"

-- |The Yold type. It's just an Int.
type Yold = Int

-- |The Season data type. It's just a wrapped Int.
data Season = Season Int
  deriving (Eq, Ord)

instance Show Season where
  show (Season s) = seasons !! s

-- |The Day type. It's just a wrapped Int.
data Day = Day Int
  deriving (Eq, Ord)

instance Show Day where
  show (Day d) = weekdays !! (weekday d)

-- |A function returning the names of the days of the week.
weekdays :: [String]
weekdays = [ "Sweetmorn"
           , "Boomtime"
           , "Pungenday"
           , "Prickle-Prickle"
           , "Setting Orange"
           ]

-- |A function returning the names of the seasons.
seasons :: [String]
seasons = [ "Chaos"
          , "Discord"
          , "Confusion"
          , "Bureacracy"
          , "The Aftermath"
          ]

-- |A function returning the names of the fluxes.
fluxes :: [String]
fluxes = [ "Chaoflux"
         , "Discoflux"
         , "Confuflux"
         , "Bureflux"
         , "Afflux"
         ]

-- |A function returning the names of the holydays.
holydays :: [String]
holydays = [ "Mungday"
           , "Mojoday"
           , "Syaday"
           , "Zaraday"
           , "Maladay"
           ]

-- | Converts an Int to a Season (takes the day of the year).
convertSeason :: Int -> Season
convertSeason x = Season $ quot x 73

-- | Converts an Int to it's weekday (also Int).
weekday :: Int -> Int
weekday x = rem x 5

-- | Converts an Int to a Day (takes the day of the year).
convertDay :: Int -> Day
convertDay x = Day $ rem x 73

dayofmonth :: Int -> String
dayofmonth x =
        if day > 3 && day < 21
            then sday ++ "th"
            else case mod day 10 of
                1 -> sday ++ "st"
                2 -> sday ++ "nd"
                3 -> sday ++ "rd"
                _ -> sday ++ "th"
    where day = rem x 73
          sday = show $ day

-- | Converts an Int to a YOLD (takes a regular year).
yold :: Int -> Yold
yold year = (year + 1166)

holyday' :: Int -> String
holyday' day
    | rem day 73 == 50 =
            printHoly holydays day
    | rem day 73 == 5 =
            printHoly fluxes day
    | otherwise = ""
    where printHoly l x = "\nCelebrate " ++ l !! quot x 73 ++ "!"

-- | Takes a DDate or DDateTime instance and returns
-- | the appropriate Holyday (or an empty string).
holyday :: DDatable -> String
holyday (DT (DDateTime _ _ (Day d) _ _ _)) = holyday' d
holyday (D (DDate _ _ (Day d))) = holyday' d

monthdays :: [Int]
monthdays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

-- | Upgrades a DateTime to a DDateTime.
convertDateTime :: DateTime -> DDateTime
convertDateTime dt@(DateTime y m d h min s) =
  let totalDays = d + sum (take (m - 1) monthdays)
  in DDateTime (yold y) (convertSeason totalDays) (convertDay totalDays) h min s

-- | Upgrades a DateTime to a DDate.
convertDate :: DateTime -> DDate
convertDate dt@(DateTime y m d _ _ _) =
  let totalDays = d + sum (take (m - 1) monthdays)
  in DDate (yold y) (convertSeason totalDays) (convertDay totalDays)

-- | Converts a DDate instance to a DDateTime instance
-- | with the time set to midnight.
ddateToDDateTime :: DDate -> DDateTime
ddateToDDateTime (DDate y s d) = DDateTime y s d 0 0 0

-- | Converts a DDateTime instance to a DDate instance.
ddateTimeToDDate :: DDateTime -> DDate
ddateTimeToDDate (DDateTime y s d _ _ _) = DDate y s d

-- | Gets the current DDateTime.
currentDDateTime :: IO DDateTime
currentDDateTime = do
  dt <- getCurrentDateTime
  return $ convertDateTime dt