-- | Defines the types for a conduit, which is a transformer of data. A conduit
-- is almost always connected either left (to a source) or right (to a sink).
module Data.Conduit.Types.Conduit
    ( Conduit (..)
    , ConduitPush
    , ConduitClose
    ) where

import Control.Monad (liftM)
import Data.Conduit.Types.Source

-- | Pushing new data to a @Conduit@ produces a new @Conduit@.
--
-- Since 0.3.0
type ConduitPush input m output = input -> Conduit input m output

-- | When closing a @Conduit@, it can produce a final stream of values.
--
-- Since 0.3.0
type ConduitClose m output = Source m output

-- | A @Conduit@ allows data to be pushed to it, and for each new input, can
-- produce a stream of output values (possibly an empty stream). It can be
-- considered a hybrid of a @Sink@ and a @Source@.
--
-- A @Conduit@ has four constructors, corresponding to four distinct states of
-- operation.
--
-- Since 0.3.0
data Conduit input m output =
    NeedInput (ConduitPush input m output) (ConduitClose m output)
    -- ^ Indicates that the @Conduit@ needs more input in order to produce
    -- output. It also provides an action to close the @Conduit@ early, for
    -- cases when there is no more input available, or when no more output is
    -- requested. Closing at this point returns a @Source@ to allow for either
    -- consuming or ignoring the new stream.

  | HaveOutput (Conduit input m output) (m ()) output
    -- ^ Indicates that the @Conduit@ has more output available. It has three
    -- records: the next @Conduit@ to continue the stream, a close action for
    -- early termination, and the output currently available. Note that, unlike
    -- @NeedInput@, the close action here returns @()@ instead of @Source@. The
    -- reasoning is that @HaveOutput@ will only be closed early if no more
    -- output is requested, since no input is required.

  | Finished (Maybe input)
    -- ^ Indicates that no more output is available, and no more input may be
    -- sent. It provides an optional leftover input record. Note: It is a
    -- violation of @Conduit@'s invariants to return leftover output that was
    -- never consumed, similar to the invariants of a @Sink@.

  | ConduitM (m (Conduit input m output)) (m ())
    -- ^ Indicates that a monadic action must be taken to determine the next
    -- @Conduit@. It also provides an early close action. Like @HaveOutput@,
    -- this action returns @()@, since it should only be used when no more
    -- output is requested.

instance Monad m => Functor (Conduit input m) where
    fmap f (NeedInput p c) = NeedInput (fmap f . p) (fmap f c)
    fmap _ (Finished i) = Finished i
    fmap f (HaveOutput pull close output) = HaveOutput
        (fmap f pull) close (f output)
    fmap f (ConduitM mcon c) = ConduitM (liftM (fmap f) mcon) c