{-# LANGUAGE GADTs, MultiParamTypeClasses #-}
module Control.Concurrent.Chan.Split (
    -- * Chan pairs
      InChan()
    , OutChan()
    -- * Utility functions:
    , getChanContents
    , dupChan     
    -- * Supporting module
    , module Control.Concurrent.Chan.Class

    ) where

import qualified Control.Concurrent.Chan as C
import Data.Functor.Contravariant
import Control.Applicative
import Control.Arrow
import Control.Concurrent.Chan.Class



-- TODO: test performance of this with and without fmaped / contramap values in
-- comparison with standard Chan. Test to see if we can improve performance
-- using special constructor for fmaped / contramap version


-- | The "write side" of a chan pair
data InChan i where
    InChan :: (i -> a) -> C.Chan a -> InChan i

-- | The "read side" of a chan pair
data OutChan o where
    OutChan :: (a -> o) -> C.Chan a -> OutChan o

instance NewSplitChan InChan OutChan where
    -- | Create corresponding read and write ends of a chan pair. Writes to the
    -- 'InChan' side can be read on the 'OutChan' side.
    newSplitChan = (InChan id &&& OutChan id) <$> C.newChan


instance SplitChan InChan OutChan where
    writeChan (InChan f c) = C.writeChan c . f
    writeList2Chan (InChan f c) = C.writeList2Chan c . map f
    readChan (OutChan f c) = f <$> C.readChan c 

instance Contravariant InChan where
    contramap f' (InChan f c) = InChan (f . f') c

instance Functor OutChan where
    fmap f' (OutChan f c) = OutChan (f' . f) c




-- | Return a lazy list representing the contents of the supplied OutChan, much
-- like System.IO.hGetContents.
getChanContents :: OutChan a -> IO [a]
getChanContents (OutChan f c) = map f <$> C.getChanContents c


-- | Duplicate an 'OutChan': the duplicate channel begins empty, but data
-- written to the corresponding 'InChan' will appear in both, i.e. consuming a
-- value from the copy will have no affect on the values in the original
-- OutChan.
dupChan :: OutChan a -> IO (OutChan a)
dupChan (OutChan f c) = OutChan f <$> C.dupChan c

{-
-- | EXPERIMENTAL: combine multiple output chans, interleaving their values
mergeOutChans :: [OutChan a] -> IO (OutChan a)
mergeOutChans cs = 
    as <- mapM C.getChanContents cs
    ...
-}