{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Conduit.Simple.Compat
    ( ($=), (=$), (=$=), ($$)
    , sequenceSources
    -- , toFoldM, fromFoldM
    -- , adaptFrom, adaptTo
    ) where

import           Conduit.Simple.Core
-- import           Control.Category (Category)
-- import           Control.Exception.Lifted (finally)
-- import           Control.Foldl (FoldM(..))
-- import           Control.Monad (liftM)
-- import           Control.Monad.CC hiding (control)
-- import           Control.Monad.Cont
-- import           Control.Monad.Logic
-- import           Control.Monad.Trans.Class (lift)
-- import           Control.Monad.Trans.Control
-- import           Control.Monad.Trans.Either (EitherT(..))
-- import           Control.Monad.Trans.Maybe
-- import           Crypto.Hash
-- import qualified Data.ByteString as B
-- import           Data.Foldable
-- import           Data.Functor.Identity
-- import qualified Data.Machine as M
import           Data.Traversable

-- import qualified Data.Conduit.Internal as C (Source, Producer,
--                                              ConduitM(..), Pipe(..))

-- | Compose a 'Source' and a 'Conduit' into a new 'Source'.  Note that this
--   is just flipped function application, so ($) can be used to achieve the
--   same thing.
infixl 1 $=
($=) :: a -> (a -> b) -> b
($=) = flip ($)

-- | Compose a 'Conduit' and a 'Sink' into a new 'Sink'.  Note that this is
--   just function composition, so (.) can be used to achieve the same thing.
infixr 2 =$
(=$) :: (a -> b) -> (b -> c) -> a -> c
(=$) = flip (.)

-- | Compose two 'Conduit'.  This is also just function composition.
infixr 2 =$=
(=$=) :: (a -> b) -> (b -> c) -> a -> c
(=$=) = flip (.)

-- | Compose a 'Source' and a 'Sink' and compute the result.  Note that this
--   is just flipped function application, so ($) can be used to achieve the
--   same thing.
infixr 0 $$
($$) :: a -> (a -> b) -> b
($$) = flip ($)

-- | Sequence a collection of sources.
--
-- >>> sinkList $ sequenceSources [yieldOne 1, yieldOne 2, yieldOne 3]
-- [[1,2,3]]
sequenceSources :: (Traversable f, Monad m) => f (Source m a) -> Source m (f a)
sequenceSources = sequenceA

{-
-- | Convert a 'Control.Foldl.FoldM' fold abstraction into a Sink.
--
--   NOTE: This requires ImpredicativeTypes in the code that uses it.
--
-- >>> fromFoldM (FoldM ((return .) . (+)) (return 0) return) $ yieldMany [1..10]
-- 55
fromFoldM :: Monad m => FoldM m a b -> Sink a m b
fromFoldM (FoldM step start done) src =
    start >>= (\r -> sink r ((lift .) . step) src) >>= done

-- | Convert a Sink into a 'Control.Foldl.FoldM', passing it as a continuation
--   over the elements.
--
-- >>> toFoldM sumC (\f -> Control.Foldl.foldM f [1..10])
-- 55
toFoldM :: Monad m => Sink a m b -> (forall r. FoldM m a r -> m r) -> m b
toFoldM s f = s $ source $ \z yield ->
    lift $ f $ FoldM ((unwrap .) . yield) (return z) return

-- | Turns any conduit 'Producer' into a simple-conduit 'Source'.
--   Finalization is taken care of, as is processing of leftovers, provided
--   the base monad implements @MonadBaseControl IO@.
adaptFrom :: forall m a. MonadBaseControl IO m => C.Producer m a -> Source m a
adaptFrom (C.ConduitM m) = source go
  where
    go :: r -> (r -> a -> EitherT r m r) -> EitherT r m r
    go z yield = f z m
      where
        f r (C.HaveOutput p c o) = yield r o >>= \r' -> f r' p `finally` lift c
        f r (C.NeedInput _ u)    = f r (u ())
        f r (C.Done ())          = return r
        f r (C.PipeM mp)         = lift mp >>= f r
        f r (C.Leftover p l)     = yield r l >>= flip f p

-- | Turn a non-resource dependent simple-conduit into a conduit 'Source'.
--
--   Finalization data would be lost in this transfer, and so is denied by
--   lack of an instance for @MonadBaseControl IO@.  Further, the resulting
--   pipeline must be run under 'Control.Monad.CC.runCCT', so really this is
--   more a curiosity than anything else.
adaptTo :: MonadDelimitedCont p s m => Source m a -> C.Source m a
adaptTo src = C.ConduitM $ C.PipeM $ reset $ \p ->
    liftM C.Done $ unwrap $ runSource src () $ \() x ->
        lift $ shift p $ \k ->
            return $ C.HaveOutput (C.PipeM $ k (return ())) (return ()) x

fromLogicT :: Monad m => LogicT m a -> Source m a
fromLogicT (LogicT await) = source $ \z yield ->
    lift $ await (go yield) (return z)
  where
    go yield x mr = do
        r <- mr
        eres <- runEitherT $ yield r x
        case eres of
            Left e -> return e   -- no short-circuiting here!
            Right r -> return r

-- toLogicT :: forall m a. Monad m => Source m a -> LogicT m a
-- toLogicT (Source (ContT await)) = LogicT $ \yield mz -> do
--     z <- mz
--     liftM (either id id) . runEitherT $
--         runIdentity (await (\x -> Identity $ liftM lift $ yield x . return)) z

fromMachine :: forall m k a. Monad m => M.MachineT m k a -> Source m a
fromMachine mach = source go
  where
    go :: forall r. r -> (r -> a -> EitherT r m r) -> EitherT r m r
    go z yield = loop mach z
      where
        loop :: M.MachineT m k a -> r -> EitherT r m r
        loop (M.MachineT m) r = do
            step <- lift m
            case step of
                M.Stop        -> return r
                M.Yield x k   -> loop k r >>= flip yield x
                M.Await _ _ e -> loop e r

-- toMachine :: forall m k s a. (Category k, Monad m)
--           => Source m a -> s -> M.MachineT m (k a) s
-- toMachine (Source (ContT await)) seed =
--     M.construct $ M.PlanT
--         (\r -> )
--         (\a mr -> )
--         (\f kz mr -> )
--         (return seed)
--     liftM (either id id) . runEitherT $
--         runIdentity (await go) seed
--   where
--     go :: a -> Identity (s -> EitherT s (M.PlanT (k a) a m) ())
--     go x = Identity $ liftM lift $ \r -> M.yield x

-- | A 'Sink' that hashes a stream of 'B.ByteString'@s@ and creates a digest
--   @d@.
sinkHash :: (Monad m, HashAlgorithm hash) => Sink B.ByteString m (Digest hash)
sinkHash = liftM hashFinalize . sink hashInit ((return .) . hashUpdate)

-- | Hashes the whole contents of the given file in constant memory.  This
--   function is just a convenient wrapper around 'sinkHash'.
hashFile :: (MonadIO m, HashAlgorithm hash) => FilePath -> m (Digest hash)
hashFile = liftIO . sinkHash . sourceFile
-}