-- |
-- 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