-- | A transactional priority queue, based on a Finger Tree.
module Control.Concurrent.STM.TPQueue
  ( TPQueue ()
  , newTPQueue
  , newTPQueueIO
  , writeTPQueue
  , readTPQueue
  , tryReadTPQueue
  , peekTPQueue
  , tryPeekTPQueue
  , isEmptyTPQueue
  ) where

import           Control.Concurrent.STM.TVar
import           Control.Monad.STM
import           Data.PriorityQueue.FingerTree (PQueue)
import qualified Data.PriorityQueue.FingerTree as PQueue

-- | 'TPQueue' is an unbounded priority queue based on a Finger Tree.
newtype TPQueue k v = TPQueue (TVar (PQueue k v))

mkTPQueue :: Functor f => f (TVar (PQueue k v)) -> f (TPQueue k v)
mkTPQueue = fmap TPQueue

-- | Build a new 'TPQueue'.
newTPQueue :: Ord k => STM (TPQueue k v)
newTPQueue = mkTPQueue (newTVar PQueue.empty)

-- | IO version of 'newTPQueue'. This is useful for creating top-level
-- 'TPQueues' using 'unsafePerformIO', because using 'atomically' inside
-- 'unsafePerformIO' isn't possible.
newTPQueueIO :: Ord k => IO (TPQueue k v)
newTPQueueIO = mkTPQueue (newTVarIO PQueue.empty)

-- | Write a value to a 'TPQueue'.
writeTPQueue :: Ord k => TPQueue k v -> k -> v -> STM ()
writeTPQueue (TPQueue h) k v = modifyTVar' h (PQueue.add k v)

-- | Read the next minimal value from a 'TPQueue'.
readTPQueue :: Ord k => TPQueue k v -> STM v
readTPQueue (TPQueue h) = do
    xs <- readTVar h
    case PQueue.minView xs of
        Just (x, xs') -> writeTVar h xs' >> pure x
        Nothing       -> retry

-- | A version of 'readTPQueue' that does not retry, but returns 'Nothing'
-- instead if no value is available.
tryReadTPQueue :: Ord k => TPQueue k v -> STM (Maybe v)
tryReadTPQueue (TPQueue h) = do
    xs <- readTVar h
    case PQueue.minView xs of
        Just (x, xs') -> writeTVar h xs' >> pure (Just x)
        Nothing       -> pure Nothing

-- | Get the next minimal value from a 'TPQueue' without removing it.
peekTPQueue :: Ord k => TPQueue k v -> STM v
peekTPQueue (TPQueue h) = do
    xs <- readTVar h
    case PQueue.minView xs of
        Just (x, _) -> pure x
        Nothing     -> retry

-- | A version of 'peekTPQueue' that does not retry, but returns 'Nothing'
-- instead if no value is available.
tryPeekTPQueue :: Ord k => TPQueue k v -> STM (Maybe v)
tryPeekTPQueue (TPQueue h) = do
    xs <- readTVar h
    case PQueue.minView xs of
        Just (x, _) -> pure (Just x)
        Nothing     -> pure Nothing

-- | Returns 'True' if the 'TPQueue' is empty.
isEmptyTPQueue :: Ord k => TPQueue k v -> STM Bool
isEmptyTPQueue (TPQueue h) = fmap PQueue.null (readTVar h)