{-# OPTIONS_GHC -Wall -fwarn-tabs #-}
{-# LANGUAGE CPP, DeriveDataTypeable #-}

#if __GLASGOW_HASKELL__ >= 701
{-# LANGUAGE Safe #-}
#endif
----------------------------------------------------------------
--                                                    2021.10.17
-- |
-- Module      :  Control.Concurrent.STM.TMQueue
-- Copyright   :  Copyright (c) 2011--2021 wren gayle romano
-- License     :  BSD
-- Maintainer  :  wren@cpan.org
-- Stability   :  provisional
-- Portability :  non-portable (GHC STM, DeriveDataTypeable)
--
-- A version of "Control.Concurrent.STM.TQueue" where the queue is
-- closeable. This is similar to a @TQueue (Maybe a)@ with a
-- monotonicity guarantee that once there's a @Nothing@ there will
-- always be @Nothing@.
--
-- /Since: 2.0.0/
----------------------------------------------------------------
module Control.Concurrent.STM.TMQueue
    (
    -- * The TMQueue type
      TMQueue()
    -- ** Creating TMQueues
    , newTMQueue
    , newTMQueueIO
    -- ** Reading from TMQueues
    , readTMQueue
    , tryReadTMQueue
    , peekTMQueue
    , tryPeekTMQueue
    -- ** Writing to TMQueues
    , writeTMQueue
    , unGetTMQueue
    -- ** Closing TMQueues
    , closeTMQueue
    -- ** Predicates
    , isClosedTMQueue
    , isEmptyTMQueue
    ) where

import Data.Typeable       (Typeable)
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif
import Control.Monad.STM   (STM)
import Control.Concurrent.STM.TVar
import Control.Concurrent.STM.TQueue -- N.B., GHC only
----------------------------------------------------------------

-- | @TMQueue@ is an abstract type representing a closeable FIFO
-- queue.
data TMQueue a = TMQueue
    {-# UNPACK #-} !(TVar Bool)
    {-# UNPACK #-} !(TQueue a)
    deriving Typeable


-- | Build and returns a new instance of @TMQueue@.
newTMQueue :: STM (TMQueue a)
newTMQueue = do
    closed <- newTVar False
    queue  <- newTQueue
    return (TMQueue closed queue)


-- | @IO@ version of 'newTMQueue'. This is useful for creating
-- top-level @TMQueue@s using 'System.IO.Unsafe.unsafePerformIO',
-- because using 'Control.Monad.STM.atomically' inside
-- 'System.IO.Unsafe.unsafePerformIO' isn't possible.
newTMQueueIO :: IO (TMQueue a)
newTMQueueIO = do
    closed <- newTVarIO False
    queue  <- newTQueueIO
    return (TMQueue closed queue)


-- | Read the next value from the @TMQueue@, retrying if the queue
-- is empty (and not closed). We return @Nothing@ immediately if
-- the queue is closed and empty.
readTMQueue :: TMQueue a -> STM (Maybe a)
readTMQueue (TMQueue closed queue) = do
    b <- readTVar closed
    if b
        then tryReadTQueue queue
        else Just <$> readTQueue queue
{-
-- The above is lazier reading from @queue@, and slightly optimized, compared to the clearer:
readTMQueue (TMQueue closed queue) = do
    b  <- isEmptyTQueue queue
    b' <- readTVar closed
    if b && b'
        then return Nothing
        else Just <$> readTQueue queue
-- TODO: compare Core and benchmarks; is the loss of clarity worth it?
-}


-- | A version of 'readTMQueue' which does not retry. Instead it
-- returns @Just Nothing@ if the queue is open but no value is
-- available; it still returns @Nothing@ if the queue is closed
-- and empty.
tryReadTMQueue :: TMQueue a -> STM (Maybe (Maybe a))
tryReadTMQueue (TMQueue closed queue) = do
    b <- readTVar closed
    if b
        then fmap Just <$> tryReadTQueue queue
        else Just <$> tryReadTQueue queue
{-
-- The above is lazier reading from @queue@ (and removes an extraneous isEmptyTQueue when using the compatibility layer) than the clearer:
tryReadTMQueue (TMQueue closed queue) = do
    b  <- isEmptyTQueue queue
    b' <- readTVar closed
    if b && b'
        then return Nothing
        else Just <$> tryReadTQueue queue
-- TODO: compare Core and benchmarks; is the loss of clarity worth it?
-}


-- | Get the next value from the @TMQueue@ without removing it,
-- retrying if the queue is empty.
peekTMQueue :: TMQueue a -> STM (Maybe a)
peekTMQueue (TMQueue closed queue) = do
    b <- readTVar closed
    if b
        then do
            b' <- isEmptyTQueue queue
            if b'
                then return Nothing
                else Just <$> peekTQueue queue
        else Just <$> peekTQueue queue
{-
-- The above is lazier reading from @queue@ than the clearer:
peekTMQueue (TMQueue closed queue) = do
    b  <- isEmptyTQueue queue
    b' <- readTVar closed
    if b && b'
        then return Nothing
        else Just <$> peekTQueue queue
-- TODO: compare Core and benchmarks; is the loss of clarity worth it?
-}


-- | A version of 'peekTMQueue' which does not retry. Instead it
-- returns @Just Nothing@ if the queue is open but no value is
-- available; it still returns @Nothing@ if the queue is closed
-- and empty.
tryPeekTMQueue :: TMQueue a -> STM (Maybe (Maybe a))
tryPeekTMQueue (TMQueue closed queue) = do
    b <- readTVar closed
    if b
        then fmap Just <$> tryPeekTQueue queue
        else Just <$> tryPeekTQueue queue
{-
-- The above is lazier reading from @queue@ (and removes an extraneous isEmptyTQueue when using the compatibility layer) than the clearer:
tryPeekTMQueue (TMQueue closed queue) = do
    b  <- isEmptyTQueue queue
    b' <- readTVar closed
    if b && b'
        then return Nothing
        else Just <$> tryPeekTQueue queue
-- TODO: compare Core and benchmarks; is the loss of clarity worth it?
-}


-- | Write a value to a @TMQueue@. If the queue is closed then the
-- value is silently discarded. Use 'isClosedTMQueue' to determine
-- if the queue is closed before writing, as needed.
writeTMQueue :: TMQueue a -> a -> STM ()
writeTMQueue (TMQueue closed queue) x = do
    b <- readTVar closed
    if b
        then return () -- discard silently
        else writeTQueue queue x


-- | Put a data item back onto a queue, where it will be the next
-- item read. If the queue is closed then the value is silently
-- discarded; you can use 'peekTMQueue' to circumvent this in certain
-- circumstances.
unGetTMQueue :: TMQueue a -> a -> STM ()
unGetTMQueue (TMQueue closed queue) x = do
    b <- readTVar closed
    if b
        then return () -- discard silently
        else unGetTQueue queue x


-- | Closes the @TMQueue@, preventing any further writes.
closeTMQueue :: TMQueue a -> STM ()
closeTMQueue (TMQueue closed _queue) =
    writeTVar closed True


-- | Returns @True@ if the supplied @TMQueue@ has been closed.
isClosedTMQueue :: TMQueue a -> STM Bool
isClosedTMQueue (TMQueue closed _queue) =
    readTVar closed

{-
-- | Returns @True@ if the supplied @TMQueue@ has been closed.
isClosedTMQueueIO :: TMQueue a -> IO Bool
isClosedTMQueueIO (TMQueue closed _queue) =
    readTVarIO closed
-}


-- | Returns @True@ if the supplied @TMQueue@ is empty.
isEmptyTMQueue :: TMQueue a -> STM Bool
isEmptyTMQueue (TMQueue _closed queue) =
    isEmptyTQueue queue

----------------------------------------------------------------
----------------------------------------------------------- fin.
