content: -- |
  -- Module      : Control.Concurrent.Classy.Chan
  -- Copyright   : (c) 2016 Michael Walker
  -- License     : MIT
  -- Maintainer  : Michael Walker <mike@barrucadu.co.uk>
  -- Stability   : stable
  -- Portability : portable
  --
  -- Unbounded channels.
  --
  -- __Deviations:__ @Chan@ as defined here does not have an @Eq@
  -- instance, this is because the @MonadConc@ @MVar@ type does not have
  -- an @Eq@ constraint. The deprecated @unGetChan@ and @isEmptyCHan@
  -- functions are not provided. Furthermore, the @getChanContents@
  -- function is not provided as it needs unsafe I/O.
  module Control.Concurrent.Classy.Chan
    ( -- * The 'Chan' type
      Chan
  
    -- * Operations
    , newChan
    , writeChan
    , readChan
    , dupChan
  
    -- * Stream interface
    , writeList2Chan
    ) where
  
  import           Control.Concurrent.Classy.MVar
  import           Control.Monad.Catch            (mask_)
  import           Control.Monad.Conc.Class       (MonadConc)
  
  -- | 'Chan' is an abstract type representing an unbounded FIFO
  -- channel.
  --
  -- @since 1.0.0.0
  data Chan m a
    = Chan (MVar m (Stream m a))
           (MVar m (Stream m a)) -- Invariant: the Stream a is always an empty MVar
  
  type Stream m a = MVar m (ChItem m a)
  
  data ChItem m a = ChItem a (Stream m a)
  
  -- | Build and returns a new instance of 'Chan'.
  --
  -- @since 1.0.0.0
  newChan :: MonadConc m => m (Chan m a)
  newChan = do
    hole  <- newEmptyMVar
    readVar  <- newMVar hole
    writeVar <- newMVar hole
    pure (Chan readVar writeVar)
  
  -- | Write a value to a 'Chan'.
  --
  -- @since 1.0.0.0
  writeChan :: MonadConc m => Chan m a -> a -> m ()
  writeChan (Chan _ writeVar) val = do
    new_hole <- newEmptyMVar
    mask_ $ do
      old_hole <- takeMVar writeVar
      putMVar old_hole (ChItem val new_hole)
      putMVar writeVar new_hole
  
  -- | Read the next value from the 'Chan'.
  --
  -- @since 1.0.0.0
  readChan :: MonadConc m => Chan m a -> m a
  readChan (Chan readVar _) =  modifyMVarMasked readVar $ \read_end -> do
    (ChItem val new_read_end) <- readMVar read_end
    pure (new_read_end, val)
  
  -- | Duplicate a 'Chan': the duplicate channel begins empty, but data
  -- written to either channel from then on will be available from both.
  -- Hence this creates a kind of broadcast channel, where data written
  -- by anyone is seen by everyone else.
  --
  -- @since 1.0.0.0
  dupChan :: MonadConc m => Chan m a -> m (Chan m a)
  dupChan (Chan _ writeVar) = do
    hole       <- readMVar writeVar
    newReadVar <- newMVar hole
    pure (Chan newReadVar writeVar)
  
  -- | Write an entire list of items to a 'Chan'.
  --
  -- @since 1.0.0.0
  writeList2Chan :: MonadConc m => Chan m a -> [a] -> m ()
  writeList2Chan = mapM_ . writeChan
  