{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE RankNTypes #-}
-- | Convert a stream of blaze-builder @Builder@s into a stream of @ByteString@s.
--
-- Adapted from blaze-builder-enumerator, written by myself and Simon Meier.
--
-- Note: if you have blaze-builder >= 0.4, 'newBlazeRecv' just calls
-- 'Data.Streaming.ByteString.Builder.newByteStringBuilderRecv'

-- Note that the functions here can work in any monad built on top of @IO@ or
-- @ST@.
module Data.Streaming.Blaze
    ( BlazeRecv
    , BlazePopper
    , BlazeFinish
    , newBlazeRecv

  -- * Buffers
  , Buffer

  -- ** Status information
  , freeSize
  , sliceSize
  , bufferSize

  -- ** Creation and modification
  , allocBuffer
  , reuseBuffer
  , nextSlice

  -- ** Conversion to bytestings
  , unsafeFreezeBuffer
  , unsafeFreezeNonEmptyBuffer

  -- * Buffer allocation strategies
  , BufferAllocStrategy
  , allNewBuffersStrategy
  , reuseBufferStrategy
  , defaultStrategy
    ) where

import Blaze.ByteString.Builder
import qualified Data.ByteString as S

#if MIN_VERSION_blaze_builder(0,4,0)

import Data.Streaming.ByteString.Builder

newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish)
newBlazeRecv = newByteStringBuilderRecv
{-# INLINE newBlazeRecv #-}

#else /* !MIN_VERSION_blaze_builder(0,4,0) */

import Blaze.ByteString.Builder.Internal hiding (insertByteString)
import Blaze.ByteString.Builder.Internal.Types hiding (insertByteString)
import Blaze.ByteString.Builder.Internal.Buffer (execBuildStep)
import Data.IORef

import Data.Streaming.ByteString.Builder.Buffer

newBlazeRecv :: BufferAllocStrategy -> IO (BlazeRecv, BlazeFinish)
newBlazeRecv (ioBufInit, nextBuf) = do
    refBuf <- newIORef ioBufInit
    return (push refBuf, finish refBuf)
  where
    finish refBuf = do
        ioBuf <- readIORef refBuf
        buf <- ioBuf
        return $ unsafeFreezeNonEmptyBuffer buf

    push refBuf builder = do
        refStep <- newIORef $ Left $ unBuilder builder (buildStep finalStep)
        return $ popper refBuf refStep
      where
        finalStep !(BufRange pf _) = return $ Done pf ()

    popper refBuf refStep = do
        ioBuf <- readIORef refBuf
        ebStep <- readIORef refStep
        case ebStep of
            Left bStep -> do
                !buf   <- ioBuf
                signal <- execBuildStep bStep buf
                case signal of
                    Done op' _ -> do
                        writeIORef refBuf $ return $ updateEndOfSlice buf op'
                        return S.empty
                    BufferFull minSize op' bStep' -> do
                        let buf' = updateEndOfSlice buf op'
                            {-# INLINE cont #-}
                            cont mbs = do
                                -- sequencing the computation of the next buffer
                                -- construction here ensures that the reference to the
                                -- foreign pointer `fp` is lost as soon as possible.
                                ioBuf' <- nextBuf minSize buf'
                                writeIORef refBuf ioBuf'
                                writeIORef refStep $ Left bStep'
                                case mbs of
                                    Just bs | not $ S.null bs -> return bs
                                    _ -> popper refBuf refStep
                        cont $ unsafeFreezeNonEmptyBuffer buf'
                    InsertByteString op' bs bStep' -> do
                        let buf' = updateEndOfSlice buf op'
                        let yieldBS = do
                                nextBuf 1 buf' >>= writeIORef refBuf
                                writeIORef refStep $ Left bStep'
                                if S.null bs
                                    then popper refBuf refStep
                                    else return bs
                        case unsafeFreezeNonEmptyBuffer buf' of
                            Nothing -> yieldBS
                            Just bs' -> do
                                writeIORef refStep $ Right yieldBS
                                return bs'
            Right action -> action

{-
helper :: (MonadBase base m, PrimMonad base, Monad (t m), MonadTrans t)
       => t m (Maybe (Flush Builder))
       -> (Flush S.ByteString -> t m ())
       -> BufferAllocStrategy
       -> t m ()
helper await' yield' (ioBufInit, nextBuf) =
    loop ioBufInit
  where
    loop ioBuf = do
        await' >>= maybe (close ioBuf) (cont' ioBuf)

    cont' ioBuf Flush = push ioBuf flush $ \ioBuf' -> yield' Flush >> loop ioBuf'
    cont' ioBuf (Chunk builder) = push ioBuf builder loop

    close ioBuf = do
        buf <- lift $ unsafeLiftIO $ ioBuf
        maybe (return ()) (yield' . Chunk) (unsafeFreezeNonEmptyBuffer buf)
-}

#endif /* !MIN_VERSION_blaze_builder(0,4,0) */

-- | Provides a series of @ByteString@s until empty, at which point it provides
-- an empty @ByteString@.
--
-- Since 0.1.2
type BlazePopper = IO S.ByteString

type BlazeRecv = Builder -> IO BlazePopper

type BlazeFinish = IO (Maybe S.ByteString)