{-# LANGUAGE RecordWildCards #-}

module Hans.Time (
    module Hans.Time,
    H.Entry(..),
    H.toUnsortedList,
  ) where

import qualified Data.Heap as H
import           Data.Time.Clock (UTCTime,NominalDiffTime,diffUTCTime)
import           Data.Tuple (swap)


type Expires = H.Entry UTCTime

expiresBefore :: UTCTime -> Expires a -> Bool
expiresBefore time entry = time >= H.priority entry


type ExpireHeap a = H.Heap (Expires a)

emptyHeap :: ExpireHeap a
emptyHeap  = H.empty
{-# INLINE emptyHeap #-}

fromListHeap :: [Expires a] -> ExpireHeap a
fromListHeap  = H.fromList
{-# INLINE fromListHeap #-}

filterHeap :: (a -> Bool) -> ExpireHeap a -> ExpireHeap a
filterHeap p = H.filter p'
  where
  p' H.Entry { .. } = p payload
{-# INLINE filterHeap #-}

partitionHeap :: (a -> Bool) -> ExpireHeap a -> (ExpireHeap a,ExpireHeap a)
partitionHeap p = H.partition p'
  where
  p' H.Entry { .. } = p payload
{-# INLINE partitionHeap #-}

-- | The next time that something in the heap will expire, if the heap is
-- non-empty.
nextEvent :: ExpireHeap a -> Maybe UTCTime
nextEvent heap =
  do (entry,_) <- H.viewMin heap
     return (H.priority entry)

-- | Remove all expired entries from the heap.
dropExpired :: UTCTime -> ExpireHeap a -> ExpireHeap a
dropExpired now heap = H.dropWhile (expiresBefore now) heap
{-# INLINE dropExpired #-}

-- | Given the current time, partition the heap into valid entries, and entries
-- that have expired.
partitionExpired :: UTCTime -> ExpireHeap a -> (ExpireHeap a, ExpireHeap a)
partitionExpired now heap = swap (H.break (expiresBefore now) heap)
{-# INLINE partitionExpired #-}

-- | Add an entry to the 'ExpireHeap', and return the time of the next
-- expiration event.
expireAt :: UTCTime -> a -> ExpireHeap a -> (ExpireHeap a,UTCTime)
expireAt time a heap =
  let heap' = H.insert H.Entry { H.priority = time, H.payload = a } heap
   in (heap',H.priority (H.minimum heap'))
   -- NOTE: it's safe to use the partial function minimum, as we just inserted
   -- into the heap we're asking for the minimum element of.
{-# INLINE expireAt #-}

nullHeap :: ExpireHeap a -> Bool
nullHeap  = H.null
{-# INLINE nullHeap #-}

-- | The amount of time until the top of the heap expires, relative to the time
-- given.
expirationDelay :: UTCTime -> ExpireHeap a -> Maybe NominalDiffTime
expirationDelay now heap =
  do (H.Entry { .. }, _) <- H.viewMin heap
     return $! diffUTCTime priority now


-- | Convert a 'NominalDiffTime' into microseconds for use with 'threadDelay'.
toUSeconds :: NominalDiffTime -> Int
toUSeconds diff = max 0 (truncate (diff * 1000000))