module Network.Wai.Handler.Warp.IO where

import Control.Exception (mask_)
import Data.ByteString.Builder (Builder)
import Data.ByteString.Builder.Extra (Next (Chunk, Done, More), runBuilder)
import Data.IORef (IORef, readIORef, writeIORef)
import Network.Wai.Handler.Warp.Buffer
import Network.Wai.Handler.Warp.Imports
import Network.Wai.Handler.Warp.Types

toBufIOWith :: Int -> IORef WriteBuffer -> (ByteString -> IO ()) -> Builder -> IO Integer
toBufIOWith maxRspBufSize writeBufferRef io builder = do
  writeBuffer <- readIORef writeBufferRef
  loop writeBuffer firstWriter 0
  where
    firstWriter = runBuilder builder
    loop writeBuffer writer bytesSent = do
      let buf = bufBuffer writeBuffer
          size = bufSize writeBuffer
      (len, signal) <- writer buf size
      bufferIO buf len io
      let totalBytesSent = toInteger len + bytesSent
      case signal of
        Done -> return totalBytesSent
        More minSize next
          | size < minSize -> do
              when (minSize > maxRspBufSize) $
                error $ "Sending a Builder response required a buffer of size "
                          ++ show minSize ++ " which is bigger than the specified maximum of "
                          ++ show maxRspBufSize ++ "!"
              -- The current WriteBuffer is too small to fit the next
              -- batch of bytes from the Builder so we free it and
              -- create a new bigger one. Freeing the current buffer,
              -- creating a new one and writing it to the IORef need
              -- to be performed atomically to prevent both double
              -- frees and missed frees. So we mask async exceptions:
              biggerWriteBuffer <- mask_ $ do
                bufFree writeBuffer
                biggerWriteBuffer <- createWriteBuffer minSize
                writeIORef writeBufferRef biggerWriteBuffer
                return biggerWriteBuffer
              loop biggerWriteBuffer next totalBytesSent
          | otherwise -> loop writeBuffer next totalBytesSent
        Chunk bs next -> do
          io bs
          loop writeBuffer next totalBytesSent
