{-# LANGUAGE RankNTypes #-}

-- | Like "Streaming.Eversion", but for Producer folds and transformations.
-- 

module Streaming.Eversion.Pipes (
        -- * Producer folds 
        evert
    ,   evertM
    ,   evertM_
    ,   evertMIO
    ,   evertMIO_
        -- * Producer transformations 
    ,   transvert
    ,   transvertM
    ,   transvertMIO
        -- * Examples
        -- $examples
    ) where

import           Control.Monad.IO.Class
import           Control.Monad.Trans.Class

import           Streaming(strictly)
import qualified Streaming.Prelude
import qualified Streaming.Eversion
import           Pipes
import           Pipes.Prelude
import           Control.Foldl (Fold(..),FoldM(..))

{- $setup
>>> :set -XOverloadedStrings
>>> import           Control.Monad
>>> import           Control.Monad.Trans.Except
>>> import           Control.Foldl (Fold(..),FoldM(..))
>>> import qualified Control.Foldl as L
>>> import           Streaming.Prelude (yield,next)
>>> import qualified Streaming.Prelude as S
>>> import           Pipes
>>> import qualified Pipes.Prelude as P
>>> import qualified Pipes.Text as PT
>>> import qualified Pipes.Text.Encoding as PT
>>> import qualified Pipes.ByteString as PB
>>> import           Lens.Micro.Extras
-}

-----------------------------------------------------------------------------------------

evert :: (forall m r. Monad m => Producer a m r -> m (x,r)) 
      -> Fold a x -- ^
evert phi = Streaming.Eversion.evert (\stream -> fmap strictly (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

evertM :: Monad m => (forall t r. (MonadTrans t, Monad (t m)) => Producer a (t m) r -> t m (x,r)) 
       -> FoldM m a x -- ^
evertM phi = Streaming.Eversion.evertM (\stream -> fmap strictly (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

evertM_ :: Monad m => (forall t r. (MonadTrans t, Monad (t m)) => Producer a (t m) r -> t m r) 
        -> FoldM m a () -- ^
evertM_ phi = Streaming.Eversion.evertM_ (\stream -> phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream))

evertMIO :: MonadIO m => (forall t r. (MonadTrans t, MonadIO (t m)) => Producer a (t m) r -> t m (x,r)) 
         -> FoldM m a x -- ^
evertMIO phi = Streaming.Eversion.evertMIO (\stream -> fmap strictly (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

evertMIO_ :: MonadIO m => (forall t r. (MonadTrans t, MonadIO (t m)) => Producer a (t m) r -> t m r) 
          -> FoldM m a () -- ^
evertMIO_ phi = Streaming.Eversion.evertMIO_ (\stream -> phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream))

transvert :: (forall m r. Monad m => Producer a m r -> Producer b m r)
          -> Fold b x -- ^
          -> Fold a x 
transvert phi = Streaming.Eversion.transvert (\stream -> Streaming.Prelude.unfoldr Pipes.next (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

transvertM :: Monad m 
           => (forall t r. (MonadTrans t, Monad (t m)) => Producer a (t m) r -> Producer b (t m) r)
           -> FoldM m b x -- ^
           -> FoldM m a x
transvertM phi = Streaming.Eversion.transvertM (\stream -> Streaming.Prelude.unfoldr Pipes.next (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

transvertMIO :: MonadIO m 
             => (forall t r. (MonadTrans t, MonadIO (t m)) => Producer a (t m) r -> Producer b (t m) r)
             -> FoldM m b x -- ^
             -> FoldM m a x
transvertMIO phi = Streaming.Eversion.transvertMIO (\stream -> Streaming.Prelude.unfoldr Pipes.next (phi (Pipes.Prelude.unfoldr Streaming.Prelude.next stream)))

{- $examples
 
    Applying a decoder from "Pipes.Text.Encoding" to the inputs of a Fold. In
    case the decoding fails, part of the leftovers are read in order to build the
    error value.  

>>> :{ 
    let trans = transvertM (\producer -> do result <- PT.decode (PT.utf8 . PT.eof) producer 
                                            lift (case result of
                                                    Left ls -> sample ls >>= lift . throwE
                                                    Right r -> return r))
        sample leftovers = L.purely P.fold L.mconcat (void (view (PB.splitAt 5) leftovers))
    in  runExceptT $ L.foldM (trans (L.generalize L.mconcat)) ["decode","this"]
    :}
Right "decodethis"

>>> :{ 
    let trans = transvertM (\producer -> do result <- PT.decode (PT.utf8 . PT.eof) producer 
                                            lift (case result of
                                                    Left ls -> sample ls >>= lift . throwE
                                                    Right r -> return r))
        sample leftovers = L.purely P.fold L.mconcat (void (view (PB.splitAt 8) leftovers))
    in  runExceptT $ L.foldM (trans (L.generalize L.mconcat)) ["invalid \xc3\x28","sequence"]
    :}
Left "\195(sequen"

Note that the errors are thrown in an 'ExceptT' layer below the 'Pipes.Producer'
and the polymorphic transformer.

-}