module Database.PostgreSQL.Protocol.Codecs.Time 
    ( dayToPgj
    , utcToMicros
    , localTimeToMicros
    , timeOfDayToMcs
    , pgjToDay
    , microsToUTC
    , microsToLocalTime
    , mcsToTimeOfDay
    , mcsToDiffTime
    , intervalToDiffTime
    , diffTimeToInterval
    , diffTimeToMcs
    ) where

import Data.Int  (Int64, Int32, Int64)
import Data.Time (Day(..), UTCTime(..), LocalTime(..), DiffTime, TimeOfDay,
                  picosecondsToDiffTime, timeToTimeOfDay,
                  diffTimeToPicoseconds, timeOfDayToTime)

{-# INLINE dayToPgj #-}
dayToPgj :: Integral a => Day -> a
dayToPgj = fromIntegral 
    .(+ (modifiedJulianEpoch - postgresEpoch)) . toModifiedJulianDay

{-# INLINE utcToMicros #-}
utcToMicros :: UTCTime -> Int64
utcToMicros (UTCTime day diffTime) = dayToMcs day + diffTimeToMcs diffTime

{-# INLINE localTimeToMicros #-}
localTimeToMicros :: LocalTime -> Int64
localTimeToMicros (LocalTime day time) = dayToMcs day + timeOfDayToMcs time

{-# INLINE pgjToDay #-}
pgjToDay :: Integral a => a -> Day
pgjToDay = ModifiedJulianDay . fromIntegral 
                        . subtract (modifiedJulianEpoch - postgresEpoch)

{-# INLINE microsToUTC #-}
microsToUTC :: Int64 -> UTCTime
microsToUTC mcs =
    let (d, r) = mcs `divMod` microsInDay
    in UTCTime (pgjToDay d) (mcsToDiffTime r)

{-# INLINE microsToLocalTime #-}
microsToLocalTime :: Int64 -> LocalTime
microsToLocalTime mcs =
    let (d, r) = mcs `divMod` microsInDay
    in LocalTime (pgjToDay d) (mcsToTimeOfDay r)

{-# INLINE intervalToDiffTime #-}
intervalToDiffTime :: Int64 -> Int32 -> Int32 -> DiffTime
intervalToDiffTime mcs days months = picosecondsToDiffTime . mcsToPcs $ 
    microsInDay * (fromIntegral months * daysInMonth + fromIntegral days) 
    + fromIntegral mcs

{-# INLINE diffTimeToInterval #-}
diffTimeToInterval :: DiffTime -> (Int64, Int32, Int32)
diffTimeToInterval dt = (fromIntegral $ diffTimeToMcs dt, 0, 0)

--
-- Utils
--
{-# INLINE dayToMcs #-}
dayToMcs :: Integral a => Day -> a
dayToMcs = (microsInDay *) . dayToPgj 

{-# INLINE diffTimeToMcs #-}
diffTimeToMcs :: Integral a => DiffTime -> a
diffTimeToMcs = fromIntegral . pcsToMcs . diffTimeToPicoseconds 

{-# INLINE timeOfDayToMcs #-}
timeOfDayToMcs :: Integral a => TimeOfDay -> a
timeOfDayToMcs = diffTimeToMcs . timeOfDayToTime 

{-# INLINE mcsToDiffTime #-}
mcsToDiffTime :: Integral a => a -> DiffTime
mcsToDiffTime = picosecondsToDiffTime . fromIntegral . mcsToPcs 

{-# INLINE mcsToTimeOfDay #-}
mcsToTimeOfDay :: Integral a => a -> TimeOfDay
mcsToTimeOfDay = timeToTimeOfDay . mcsToDiffTime

{-# INLINE pcsToMcs #-}
pcsToMcs :: Integral a => a -> a
pcsToMcs = (`div` 10 ^ 6)

{-# INLINE mcsToPcs #-}
mcsToPcs :: Integral a => a -> a
mcsToPcs = (* 10 ^ 6)

{-# INLINE modifiedJulianEpoch #-}
modifiedJulianEpoch :: Num a => a 
modifiedJulianEpoch = 2400001

{-# INLINE postgresEpoch #-}
postgresEpoch :: Num a => a
postgresEpoch = 2451545

{-# INLINE microsInDay #-}
microsInDay :: Num a => a
microsInDay = 24 * 60 * 60 * 10 ^ 6

{-# INLINE daysInMonth #-}
daysInMonth :: Num a => a
daysInMonth = 30
