-- | Pipe composition can be supply or demand driven, which refers to how the pipeline is
--   initiated. Supply driven initiates the left side first.
--
--   When a pipe terminates, one side must return a value. Unless the pipeline is run
--   in resumable mode, the other sides may never "complete" in that they do not
--   return a value. If they allocate resources, MonadResource or similar should be
--   used to handle cleanup (see 'Piped.Extras.bracketPipe').
--
--   Right hand termination works by indicating that no more values are available.
--
--   In a mode where either side can return a value, no special termination logic is invoked,
--   execution ends the first time a side returns.
--
--   Left termination is not provided; since there is no way to indicate to the left side
--   that the right has terminated, and potential solutions involve discarding values.
--   However, the "either" modes are also suitable for left termination.
--

module Piped.Compose
  (
  -- ** Operators
  --
    (.|)
  , (|.)

  -- ** Demand driven
  --
  -- | The right hand side is run first. The left hand side is only
  --   invoked by calling `await`.
  --
  , composeDemand
  , composeDemandEither

  -- ** Supply driven
  --
  -- | The left hand side is run first. If it returns immediately, the right
  --   hand side is invoked only in the case of `composeSupply`.
  --   
  , composeSupply
  , composeSupplyEither
  ) where

import Piped.Internal


-- | Demand driven; same as 'composeDemand
--
(.|) :: Monad m => Pipe i e m () -> Pipe e o m b -> Pipe i o m b
(.|) = composeDemand
{-# INLINE (.|) #-}


-- | Supply driven; same as 'composeSupplyEither
--
(|.) :: Monad m => Pipe i e m a -> Pipe e o m a -> Pipe i o m a
(|.) = composeSupplyEither
{-# INLINE (|.) #-}


-- | The right side is run first, only the right side may return a value.
--
composeDemand :: Monad m => Pipe i e m () -> Pipe e o m b -> Pipe i o m b
composeDemand (Pipe f1) (Pipe f2) =
  Pipe $ \rest l r ->
    f2 (\_ -> rest termLeft) (Await $ f1 (\_ r _ -> terminate r) l) r
{-# INLINE composeDemand #-}


-- | The right side is run first, either side may return a value.
--
composeDemandEither :: Monad m => Pipe i e m a -> Pipe e o m a -> Pipe i o m a
composeDemandEither (Pipe f1) (Pipe f2) =
  Pipe $ \rest l r ->
    f2 (\_ -> rest termLeft) (Await $ f1 (\l -> rest l . termRight) l) r
{-# INLINE composeDemandEither #-}


-- | The left side is run first, only the right side may return a value.
--
composeSupply :: Monad m => Pipe i e m () -> Pipe e o m b -> Pipe i o m b
composeSupply (Pipe f1) (Pipe f2) =
  Pipe $ \rest l r ->
    f1 (\_ r _ -> terminate r) l $
      Yield
        (           f2 (\_ -> rest termLeft) termLeft             r)
        (\i left -> f2 (\_ -> rest termLeft) (addLeftover i left) r)
{-# INLINE composeSupply #-}


-- | The left side is run first, either side may return a value.
--
composeSupplyEither :: Monad m => Pipe i e m a -> Pipe e o m a -> Pipe i o m a
composeSupplyEither (Pipe f1) (Pipe f2) =
  Pipe $ \rest l r ->
    f1 (\l r -> rest l $ termRight r) l $
      Yield
        (           f2 (\_ -> rest termLeft) termLeft             r)
        (\i left -> f2 (\_ -> rest termLeft) (addLeftover i left) r)
{-# INLINE composeSupplyEither #-}