module Streamly.External.ByteString.Lazy
  ( chunkReader
  , reader

  , toChunks
  , fromChunks
  , fromChunksIO

  -- Deprecated
  , read
  , readChunks
  )
where

import Data.Word (Word8)
import Streamly.Data.Array (Array)
import System.IO.Unsafe (unsafeInterleaveIO)
import Streamly.Data.Stream (Stream)

-- Internal imports
import Data.ByteString.Lazy.Internal (ByteString(..), chunk)
import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
import Streamly.Internal.Data.Unfold.Type (Unfold(..))

import qualified Streamly.External.ByteString as Strict
import qualified Streamly.Data.Array as Array
import qualified Streamly.Data.Unfold as Unfold
import qualified Streamly.Data.Stream as Stream

import Prelude hiding (read)

-- | Unfold a lazy ByteString to a stream of 'Array' 'Words'.
{-# INLINE  chunkReader #-}
chunkReader :: Monad m => Unfold m ByteString (Array Word8)
chunkReader :: forall (m :: * -> *). Monad m => Unfold m ByteString (Array Word8)
chunkReader = forall (m :: * -> *) a b s.
(s -> m (Step s b)) -> (a -> m s) -> Unfold m a b
Unfold forall {m :: * -> *}.
Monad m =>
ByteString -> m (Step ByteString (Array Word8))
step forall {a}. a -> m a
seed
  where
    seed :: a -> m a
seed = forall (m :: * -> *) a. Monad m => a -> m a
return
    step :: ByteString -> m (Step ByteString (Array Word8))
step (Chunk ByteString
bs ByteString
bl) = forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall s a. a -> s -> Step s a
Yield (ByteString -> Array Word8
Strict.toArray ByteString
bs) ByteString
bl
    step ByteString
Empty = forall (m :: * -> *) a. Monad m => a -> m a
return forall s a. Step s a
Stop

-- | Unfold a lazy ByteString to a stream of Word8
{-# INLINE reader #-}
reader :: Monad m => Unfold m ByteString Word8
reader :: forall (m :: * -> *). Monad m => Unfold m ByteString Word8
reader = forall (m :: * -> *) b c a.
Monad m =>
Unfold m b c -> Unfold m a b -> Unfold m a c
Unfold.many forall (m :: * -> *) a. (Monad m, Unbox a) => Unfold m (Array a) a
Array.reader forall (m :: * -> *). Monad m => Unfold m ByteString (Array Word8)
readChunks

-- TODO: "toChunks" should be called "read" instead
-- | Convert a lazy 'ByteString' to a serial stream of 'Array' 'Word8'.
{-# INLINE toChunks #-}
toChunks :: Monad m => ByteString -> Stream m (Array Word8)
toChunks :: forall (m :: * -> *).
Monad m =>
ByteString -> Stream m (Array Word8)
toChunks = forall (m :: * -> *) a b.
Applicative m =>
Unfold m a b -> a -> Stream m b
Stream.unfold forall (m :: * -> *). Monad m => Unfold m ByteString (Array Word8)
readChunks

{-
newtype LazyIO a = LazyIO { runLazy :: IO a } deriving (Functor, Applicative)

liftToLazy :: IO a -> LazyIO a
liftToLazy = LazyIO

instance Monad LazyIO where
    return = pure
    LazyIO a >>= f = LazyIO (unsafeInterleaveIO a >>= unsafeInterleaveIO . runLazy . f)
-}

-- | Convert a serial stream of 'Array' 'Word8' to a lazy 'ByteString'.
--
-- IMPORTANT NOTE: This function is lazy only for lazy monads
-- (e.g. Identity). For strict monads (e.g. /IO/) it consumes the entire input
-- before generating the output. For /IO/ monad please use fromChunksIO
-- instead.
--
-- For strict monads like /IO/ you could create a newtype wrapper to make the
-- monad bind operation lazy and lift the stream to that type using hoist, then
-- you can use this function to generate the bytestring lazily. For example you
-- can wrap the /IO/ type to make the bind lazy like this:
--
-- @
-- newtype LazyIO a = LazyIO { runLazy :: IO a } deriving (Functor, Applicative)
--
-- liftToLazy :: IO a -> LazyIO a
-- liftToLazy = LazyIO
--
-- instance Monad LazyIO where
--   return = pure
--   LazyIO a >>= f = LazyIO (unsafeInterleaveIO a >>= unsafeInterleaveIO . runLazy . f)
-- @
--
-- /fromChunks/ can then be used as,
-- @
-- {-# INLINE fromChunksIO #-}
-- fromChunksIO :: Stream IO (Array Word8) -> IO ByteString
-- fromChunksIO str = runLazy (fromChunks (Stream.hoist liftToLazy str))
-- @
{-# INLINE fromChunks #-}
fromChunks :: Monad m => Stream m (Array Word8) -> m ByteString
fromChunks :: forall (m :: * -> *).
Monad m =>
Stream m (Array Word8) -> m ByteString
fromChunks = forall (m :: * -> *) a b.
Monad m =>
(a -> b -> b) -> b -> Stream m a -> m b
Stream.foldr ByteString -> ByteString -> ByteString
chunk ByteString
Empty forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Array Word8 -> ByteString
Strict.fromArray

-- | Convert a serial stream of 'Array' 'Word8' to a lazy 'ByteString' in the
-- /IO/ monad.
{-# INLINE fromChunksIO #-}
fromChunksIO :: Stream IO (Array Word8) -> IO ByteString
fromChunksIO :: Stream IO (Array Word8) -> IO ByteString
fromChunksIO =
    -- Although the /IO/ monad is strict in nature we emulate laziness using
    -- 'unsafeInterleaveIO'.
    forall (m :: * -> *) a b.
Monad m =>
(a -> m b -> m b) -> m b -> Stream m a -> m b
Stream.foldrM (\ByteString
x IO ByteString
b -> ByteString -> ByteString -> ByteString
chunk ByteString
x forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. IO a -> IO a
unsafeInterleaveIO IO ByteString
b) (forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
Empty)
        forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Array Word8 -> ByteString
Strict.fromArray

--------------------------------------------------------------------------------
-- Deprecated
--------------------------------------------------------------------------------

{-# DEPRECATED readChunks "Please use chunkReader instead." #-}
{-# INLINE  readChunks #-}
readChunks :: Monad m => Unfold m ByteString (Array Word8)
readChunks :: forall (m :: * -> *). Monad m => Unfold m ByteString (Array Word8)
readChunks = forall (m :: * -> *). Monad m => Unfold m ByteString (Array Word8)
chunkReader

{-# DEPRECATED read "Please use reader instead." #-}
{-# INLINE read #-}
read :: Monad m => Unfold m ByteString Word8
read :: forall (m :: * -> *). Monad m => Unfold m ByteString Word8
read = forall (m :: * -> *). Monad m => Unfold m ByteString Word8
reader