{-# LANGUAGE LambdaCase #-}

-- | Lazy 'BL.ByteString' as @Mu (Cons StrictByteString)@.
--
-- Lazy ByteString is internally a linked list of strict chunks:
--
-- @
-- data ByteString = Empty | Chunk !StrictByteString ByteString
-- @
--
-- This is exactly @Fix (Cons StrictByteString)@. This module
-- provides conversions and operations that let you apply the
-- full recursion scheme and streaming metamorphism infrastructure
-- to lazy ByteStrings.
--
-- @
-- import Data.Fmt.ByteString.Lazy
--
-- \-\- Fold a lazy ByteString chunk by chunk
-- foldChunks (\\case Nil -> 0; Cons chunk n -> BS.length chunk + n) myLBS
--
-- \-\- Stream-transform chunks
-- transformChunks (fstream unwrap wrap produce consume flush state) myLBS
-- @
module Data.Fmt.ByteString.Lazy (
    -- * Conversion
    toChunks,
    fromChunks,
    toNuChunks,

    -- * Folding
    foldChunks,

    -- * Unfolding
    unfoldChunks,

    -- * Refold
    refoldChunks,

    -- * Mapping
    mapChunks,

    -- * Streaming
    streamChunks,
    transformChunks,
) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Internal as BLI
import Data.Functor.Fixed (Mu, Nu (..), unwrap, Algebra, Coalgebra)
import Data.Functor.Foldable (fold, unfold, refold, comap)
import Data.Functor.Pattern (Cons (..))

---------------------------------------------------------------------
-- Conversion
---------------------------------------------------------------------

-- | View a lazy 'BL.ByteString' as @Mu (Cons ByteString)@.
--
-- Each 'Cons' layer holds one strict chunk.
--
-- O(n) — rebuilds the spine as a Church-encoded list.
toChunks :: BL.ByteString -> Mu (Cons ByteString)
toChunks :: ByteString -> Mu (Cons ByteString)
toChunks = Coalgebra (Cons ByteString) ByteString
-> ByteString -> Mu (Cons ByteString)
forall (f :: * -> *) a. Functor f => Coalgebra f a -> a -> Mu f
unfold Coalgebra (Cons ByteString) ByteString
coalg
  where
    coalg :: Coalgebra (Cons ByteString) ByteString
coalg ByteString
lbs = case ByteString
lbs of
        ByteString
BLI.Empty -> Cons ByteString ByteString
forall a b. Cons a b
Nil
        BLI.Chunk ByteString
c ByteString
rest -> ByteString -> Coalgebra (Cons ByteString) ByteString
forall a b. a -> b -> Cons a b
Cons ByteString
c ByteString
rest

-- | Reassemble a @Mu (Cons ByteString)@ into a lazy 'BL.ByteString'.
--
-- O(n) — folds the Church-encoded list back into chunks.
fromChunks :: Mu (Cons ByteString) -> BL.ByteString
fromChunks :: Mu (Cons ByteString) -> ByteString
fromChunks = Algebra (Cons ByteString) ByteString
-> Mu (Cons ByteString) -> ByteString
forall (f :: * -> *) a. Algebra f a -> Mu f -> a
fold Algebra (Cons ByteString) ByteString
alg
  where
    alg :: Algebra (Cons ByteString) ByteString
alg Cons ByteString ByteString
Nil = ByteString
BL.empty
    alg (Cons ByteString
c ByteString
rest) = ByteString -> ByteString -> ByteString
BLI.Chunk ByteString
c ByteString
rest

-- | View a lazy 'BL.ByteString' as @Nu (Cons ByteString)@ for
-- lazy streaming. O(1) construction — the ByteString itself is
-- the seed.
toNuChunks :: BL.ByteString -> Nu (Cons ByteString)
toNuChunks :: ByteString -> Nu (Cons ByteString)
toNuChunks = Coalgebra (Cons ByteString) ByteString
-> ByteString -> Nu (Cons ByteString)
forall (f :: * -> *) a. (a -> f a) -> a -> Nu f
Nu Coalgebra (Cons ByteString) ByteString
coalg
  where
    coalg :: Coalgebra (Cons ByteString) ByteString
coalg ByteString
lbs = case ByteString
lbs of
        ByteString
BLI.Empty -> Cons ByteString ByteString
forall a b. Cons a b
Nil
        BLI.Chunk ByteString
c ByteString
rest -> ByteString -> Coalgebra (Cons ByteString) ByteString
forall a b. a -> b -> Cons a b
Cons ByteString
c ByteString
rest

---------------------------------------------------------------------
-- Folding
---------------------------------------------------------------------

-- | Fold a lazy 'BL.ByteString' chunk by chunk.
--
-- @foldChunks alg = fold alg . toChunks@
--
-- >>> foldChunks (\case Nil -> 0; Cons c n -> BS.length c + n) myLBS
foldChunks :: Algebra (Cons ByteString) a -> BL.ByteString -> a
foldChunks :: forall a. Algebra (Cons ByteString) a -> ByteString -> a
foldChunks Algebra (Cons ByteString) a
alg = Algebra (Cons ByteString) a -> Mu (Cons ByteString) -> a
forall (f :: * -> *) a. Algebra f a -> Mu f -> a
fold Algebra (Cons ByteString) a
alg (Mu (Cons ByteString) -> a)
-> (ByteString -> Mu (Cons ByteString)) -> ByteString -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Mu (Cons ByteString)
toChunks

---------------------------------------------------------------------
-- Unfolding
---------------------------------------------------------------------

-- | Build a lazy 'BL.ByteString' from a seed, one chunk at a time.
--
-- @unfoldChunks coalg = fromChunks . unfold coalg@
unfoldChunks :: Coalgebra (Cons ByteString) a -> a -> BL.ByteString
unfoldChunks :: forall a. Coalgebra (Cons ByteString) a -> a -> ByteString
unfoldChunks Coalgebra (Cons ByteString) a
coalg = Mu (Cons ByteString) -> ByteString
fromChunks (Mu (Cons ByteString) -> ByteString)
-> (a -> Mu (Cons ByteString)) -> a -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Coalgebra (Cons ByteString) a -> a -> Mu (Cons ByteString)
forall (f :: * -> *) a. Functor f => Coalgebra f a -> a -> Mu f
unfold Coalgebra (Cons ByteString) a
coalg

---------------------------------------------------------------------
-- Refold
---------------------------------------------------------------------

-- | Unfold then fold, fused — no intermediate @Mu@ structure.
--
-- @refoldChunks alg coalg = foldChunks alg . unfoldChunks coalg@
-- but without materializing the Church-encoded list.
refoldChunks :: Algebra (Cons ByteString) b -> Coalgebra (Cons ByteString) a -> a -> b
refoldChunks :: forall b a.
Algebra (Cons ByteString) b
-> Coalgebra (Cons ByteString) a -> a -> b
refoldChunks = Algebra (Cons ByteString) b
-> Coalgebra (Cons ByteString) a -> a -> b
forall (f :: * -> *) b a.
Functor f =>
Algebra f b -> Coalgebra f a -> a -> b
refold

---------------------------------------------------------------------
-- Mapping
---------------------------------------------------------------------

-- | Map a function over each strict chunk.
--
-- >>> mapChunks (BS.map toUpper) myLBS
mapChunks :: (ByteString -> ByteString) -> BL.ByteString -> BL.ByteString
mapChunks :: (ByteString -> ByteString) -> ByteString -> ByteString
mapChunks ByteString -> ByteString
f = Mu (Cons ByteString) -> ByteString
fromChunks (Mu (Cons ByteString) -> ByteString)
-> (ByteString -> Mu (Cons ByteString)) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ByteString -> ByteString)
-> Mu (Cons ByteString) -> Mu (Cons ByteString)
forall (f :: * -> * -> *) a b.
(Bifunctor f, Functor (f a), Functor (f b)) =>
(a -> b) -> Mu (f a) -> Mu (f b)
comap ByteString -> ByteString
f (Mu (Cons ByteString) -> Mu (Cons ByteString))
-> (ByteString -> Mu (Cons ByteString))
-> ByteString
-> Mu (Cons ByteString)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Mu (Cons ByteString)
toChunks

---------------------------------------------------------------------
-- Streaming
---------------------------------------------------------------------

-- | Apply a streaming transformation to the chunks.
--
-- Takes a function that operates on @Mu (Cons ByteString)@ and
-- lifts it to operate on lazy 'BL.ByteString'.
streamChunks
    :: (Mu (Cons ByteString) -> Mu (Cons ByteString))
    -> BL.ByteString -> BL.ByteString
streamChunks :: (Mu (Cons ByteString) -> Mu (Cons ByteString))
-> ByteString -> ByteString
streamChunks Mu (Cons ByteString) -> Mu (Cons ByteString)
f = Mu (Cons ByteString) -> ByteString
fromChunks (Mu (Cons ByteString) -> ByteString)
-> (ByteString -> Mu (Cons ByteString)) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Mu (Cons ByteString) -> Mu (Cons ByteString)
f (Mu (Cons ByteString) -> Mu (Cons ByteString))
-> (ByteString -> Mu (Cons ByteString))
-> ByteString
-> Mu (Cons ByteString)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Mu (Cons ByteString)
toChunks

-- | Apply a streaming transformation that changes the element type.
--
-- Useful for encoding/decoding pipelines where the chunk type changes.
transformChunks
    :: (Mu (Cons ByteString) -> Mu (Cons a))
    -> BL.ByteString -> [a]
transformChunks :: forall a.
(Mu (Cons ByteString) -> Mu (Cons a)) -> ByteString -> [a]
transformChunks Mu (Cons ByteString) -> Mu (Cons a)
f = Cons a (Mu (Cons a)) -> [a]
forall {a}. Cons a (Mu (Cons a)) -> [a]
go (Cons a (Mu (Cons a)) -> [a])
-> (ByteString -> Cons a (Mu (Cons a))) -> ByteString -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Mu (Cons a) -> Cons a (Mu (Cons a))
forall (f :: * -> *). Functor f => Mu f -> f (Mu f)
unwrap (Mu (Cons a) -> Cons a (Mu (Cons a)))
-> (ByteString -> Mu (Cons a))
-> ByteString
-> Cons a (Mu (Cons a))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Mu (Cons ByteString) -> Mu (Cons a)
f (Mu (Cons ByteString) -> Mu (Cons a))
-> (ByteString -> Mu (Cons ByteString))
-> ByteString
-> Mu (Cons a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Mu (Cons ByteString)
toChunks
  where
    go :: Cons a (Mu (Cons a)) -> [a]
go Cons a (Mu (Cons a))
Nil = []
    go (Cons a
a Mu (Cons a)
rest) = a
a a -> [a] -> [a]
forall a. a -> [a] -> [a]
: Cons a (Mu (Cons a)) -> [a]
go (Mu (Cons a) -> Cons a (Mu (Cons a))
forall (f :: * -> *). Functor f => Mu f -> f (Mu f)
unwrap Mu (Cons a)
rest)