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

#if __GLASGOW_HASKELL__ >= 701
{-# LANGUAGE Trustworthy #-}
#endif
----------------------------------------------------------------
--                                                    2013.05.29
-- |
-- Module      :  Control.Concurrent.STM.TChan.Compat
-- Copyright   :  Copyright (c) 2011--2013 wren ng thornton
-- License     :  BSD
-- Maintainer  :  wren@community.haskell.org
-- Stability   :  provisional
-- Portability :  non-portable (GHC STM, CPP)
--
-- Compatibility layer for older versions of the @stm@ library.
-- Namely, we define 'tryReadTChan', 'peekTChan', and 'tryPeekTChan'
-- which @stm < 2.3.0@ lacks. These implementations are less efficient
-- than the package versions due to the 'TChan' type being abstract.
-- However, this module uses Cabal-style CPP macros in order to use
-- the package versions when available.
--
-- /Deprecated: 2.1.0 (will be removed in 3.0)/
----------------------------------------------------------------
module Control.Concurrent.STM.TChan.Compat
    {-# DEPRECATED "stm-chans >= 2.1 requires stm >= 2.4; so this module no longer does anything useful." #-}
    (
    -- * The TChan type
      TChan()
    -- ** Creating TChans
    , newTChan              -- :: STM (TChan a)
    , newTChanIO            -- :: IO  (TChan a)
    , dupTChan              -- :: TChan a -> STM (TChan a)
    , newBroadcastTChan     -- :: STM (TChan a)
    , newBroadcastTChanIO   -- :: IO  (TChan a)
    -- ** Reading from TChans
    , readTChan             -- :: TChan a -> STM a
    , tryReadTChan          -- :: TChan a -> STM (Maybe a)
    , peekTChan             -- :: TChan a -> STM a
    , tryPeekTChan          -- :: TChan a -> STM (Maybe a)
    -- ** Writing to TChans
    , unGetTChan            -- :: TChan a -> a -> STM ()
    , writeTChan            -- :: TChan a -> a -> STM ()
    -- ** Predicates
    , isEmptyTChan          -- :: TChan a -> STM Bool
    ) where

import Control.Concurrent.STM.TChan -- N.B., GHC only

#if ! (MIN_VERSION_stm(2,3,0))
import Control.Applicative ((<$>))
import Control.Monad.STM   (STM)
#endif
#if ! (MIN_VERSION_stm(2,4,0))
import Control.Monad.STM   (STM)
import Control.Concurrent.STM.TVar
#endif

----------------------------------------------------------------
#if ! (MIN_VERSION_stm(2,3,0))
-- | A version of 'readTChan' which does not retry. Instead it
-- returns @Nothing@ if no value is available.
tryReadTChan :: TChan a -> STM (Maybe a)
tryReadTChan chan = do
    b <- isEmptyTChan chan
    if b then return Nothing else Just <$> readTChan chan
{- -- The optimized implementation in stm-2.3.0
tryReadTChan (TChan read _write) = do
    hd <- readTVar =<< readTVar read
    case hd of
        TNil       -> return Nothing
        TCons a tl -> do
            writeTVar read tl
            return (Just a)
-}


-- | Get the next value from the @TChan@ without removing it,
-- retrying if the channel is empty.
peekTChan :: TChan a -> STM a
peekTChan chan = do
    x <- readTChan chan
    unGetTChan chan x
    return x
{- -- The optimized implementation in stm-2.3.0
peekTChan (TChan read _write) = do
    hd <- readTVar =<< readTVar read
    case hd of
        TNil      -> retry
        TCons a _ -> return a
-}


-- | A version of 'peekTChan' which does not retry. Instead it
-- returns @Nothing@ if no value is available.
tryPeekTChan :: TChan a -> STM (Maybe a)
tryPeekTChan chan = do
    b <- isEmptyTChan chan
    if b then return Nothing else Just <$> peekTChan chan
{- -- The optimized implementation in stm-2.3.0
tryPeekTChan (TChan read _write) = do
    hd <- readTVar =<< readTVar read
    case hd of
        TNil      -> return Nothing
        TCons a _ -> return (Just a)
-}

#endif

----------------------------------------------------------------
#if ! (MIN_VERSION_stm(2,4,0))
-- BUG: how can we replicate this??

-- | Create a write-only 'TChan'. More precisely, 'readTChan' will
-- 'retry' even after items have been written to the channel. The
-- only way to read a broadcast channel is to duplicate it with
-- 'dupTChan'.
--
-- Consider a server that broadcasts messages to clients:
--
-- >serve :: TChan Message -> Client -> IO loop
-- >serve broadcastChan client = do
-- >    myChan <- dupTChan broadcastChan
-- >    forever $ do
-- >        message <- readTChan myChan
-- >        send client message
--
-- The problem with using 'newTChan' to create the broadcast channel
-- is that if it is only written to and never read, items will pile
-- up in memory. By using 'newBroadcastTChan' to create the broadcast
-- channel, items can be garbage collected after clients have seen
-- them.
newBroadcastTChan :: STM (TChan a)
newBroadcastTChan = do
    -- a la stm-2.4.2 (not stm-2.4 !)
    write_hole <- newTVar TNil
    read <- newTVar (error "reading from a TChan created by newBroadcastTChan; use dupTChan first")
    write <- newTVar write_hole
    return (TChan read write)


-- | @IO@ version of 'newBroadcastTChan'.
newBroadcastTChanIO :: IO (TChan a)
newBroadcastTChanIO = do
    -- a la stm-2.4.2 (which is the same as stm-2.4 !!)
    dummy_hole <- newTVarIO TNil
    write_hole <- newTVarIO TNil
    read  <- newTVarIO dummy_hole
    write <- newTVarIO write_hole
    return (TChan read write)
#endif

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