{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Network.QUIC.Types.Time (
    Milliseconds (..),
    Microseconds (..),
    milliToMicro,
    microToMilli,
    TimeMicrosecond,
    timeMicrosecond0,
    getTimeMicrosecond,
    getElapsedTimeMicrosecond,
    elapsedTimeMicrosecond,
    getTimeoutInMicrosecond,
    getPastTimeMicrosecond,
    getFutureTimeMicrosecond,
    addMicroseconds,
) where

import Data.UnixTime
import Foreign.C.Types (CTime (..))

import Network.QUIC.Imports

----------------------------------------------------------------

newtype Milliseconds = Milliseconds Int64 deriving (Eq, Ord, Num, Bits)
newtype Microseconds = Microseconds Int deriving (Eq, Ord, Num, Bits)

instance Show Milliseconds where
    show (Milliseconds n) = show n

instance Show Microseconds where
    show (Microseconds n) = show n

{-# INLINE milliToMicro #-}
milliToMicro :: Milliseconds -> Microseconds
milliToMicro (Milliseconds n) = Microseconds (fromIntegral n * 1000)

microToMilli :: Microseconds -> Milliseconds
microToMilli (Microseconds n) = Milliseconds (fromIntegral n `div` 1000)

----------------------------------------------------------------

type TimeMicrosecond = UnixTime

timeMicrosecond0 :: UnixTime
timeMicrosecond0 = UnixTime 0 0

----------------------------------------------------------------

getTimeMicrosecond :: IO TimeMicrosecond
getTimeMicrosecond = getUnixTime

----------------------------------------------------------------

getElapsedTimeMicrosecond :: TimeMicrosecond -> IO Microseconds
getElapsedTimeMicrosecond base = do
    c <- getTimeMicrosecond
    return $ elapsedTimeMicrosecond c base

elapsedTimeMicrosecond :: UnixTime -> UnixTime -> Microseconds
elapsedTimeMicrosecond c base = Microseconds elapsed
  where
    UnixDiffTime (CTime s) u = c `diffUnixTime` base
    elapsed = fromIntegral (s * 1000000 + fromIntegral u)

getTimeoutInMicrosecond :: TimeMicrosecond -> IO Microseconds
getTimeoutInMicrosecond tmout = do
    c <- getTimeMicrosecond
    let UnixDiffTime (CTime s) u = tmout `diffUnixTime` c
        timeout = fromIntegral s * 1000000 + fromIntegral u
    return $ Microseconds timeout

----------------------------------------------------------------

getPastTimeMicrosecond :: Microseconds -> IO TimeMicrosecond
getPastTimeMicrosecond (Microseconds us) = do
    let diff = microSecondsToUnixDiffTime $ negate us
    c <- getTimeMicrosecond
    let past = c `addUnixDiffTime` diff
    return past

getFutureTimeMicrosecond :: Microseconds -> IO TimeMicrosecond
getFutureTimeMicrosecond (Microseconds us) = do
    let diff = microSecondsToUnixDiffTime us
    c <- getTimeMicrosecond
    let future = c `addUnixDiffTime` diff
    return future

addMicroseconds :: TimeMicrosecond -> Microseconds -> TimeMicrosecond
addMicroseconds t (Microseconds n) = t `addUnixDiffTime` microSecondsToUnixDiffTime n
