module Codec.Compression.Zlib.Internal (
  
  compress,
  CompressParams(..),
  defaultCompressParams,
  
  decompress,
  DecompressParams(..),
  defaultDecompressParams,
  
  Stream.Format(..),
    Stream.gzipFormat,
    Stream.zlibFormat,
    Stream.rawFormat,
    Stream.gzipOrZlibFormat,
  Stream.CompressionLevel(..),
    Stream.defaultCompression,
    Stream.noCompression,
    Stream.bestSpeed,
    Stream.bestCompression,
    Stream.compressionLevel,
  Stream.Method(..),
    Stream.deflateMethod,
  Stream.WindowBits(..),
    Stream.defaultWindowBits,
    Stream.windowBits,
  Stream.MemoryLevel(..),
    Stream.defaultMemoryLevel,
    Stream.minMemoryLevel,
    Stream.maxMemoryLevel,
    Stream.memoryLevel,
  Stream.CompressionStrategy(..),
    Stream.defaultStrategy,
    Stream.filteredStrategy,
    Stream.huffmanOnlyStrategy,
  
  decompressWithErrors,
  DecompressStream(..),
  DecompressError(..),
  ) where
import Prelude hiding (length)
import Control.Monad (when)
import Control.Exception (assert)
import qualified Data.ByteString.Lazy as L
#ifdef BYTESTRING_IN_BASE
import qualified Data.ByteString.Base as S
#else
import qualified Data.ByteString.Lazy.Internal as L
import qualified Data.ByteString.Internal as S
#endif
import qualified Codec.Compression.Zlib.Stream as Stream
import Codec.Compression.Zlib.Stream (Stream)
data CompressParams = CompressParams {
  compressLevel       :: !Stream.CompressionLevel,
  compressMethod      :: !Stream.Method,
  compressWindowBits  :: !Stream.WindowBits,
  compressMemoryLevel :: !Stream.MemoryLevel,
  compressStrategy    :: !Stream.CompressionStrategy,
  compressBufferSize  :: !Int
}
data DecompressParams = DecompressParams {
  decompressWindowBits :: !Stream.WindowBits,
  decompressBufferSize :: !Int
}
defaultCompressParams :: CompressParams
defaultCompressParams = CompressParams {
  compressLevel       = Stream.defaultCompression,
  compressMethod      = Stream.deflateMethod,
  compressWindowBits  = Stream.defaultWindowBits,
  compressMemoryLevel = Stream.defaultMemoryLevel,
  compressStrategy    = Stream.defaultStrategy,
  compressBufferSize  = defaultCompressBufferSize
}
defaultDecompressParams :: DecompressParams
defaultDecompressParams = DecompressParams {
  decompressWindowBits = Stream.defaultWindowBits,
  decompressBufferSize = defaultDecompressBufferSize
}
defaultCompressBufferSize, defaultDecompressBufferSize :: Int
#ifdef BYTESTRING_IN_BASE
defaultCompressBufferSize   = 16 * 1024  16
defaultDecompressBufferSize = 32 * 1024  16
#else
defaultCompressBufferSize   = 16 * 1024  L.chunkOverhead
defaultDecompressBufferSize = 32 * 1024  L.chunkOverhead
#endif
data DecompressStream = StreamEnd
                      | StreamChunk S.ByteString DecompressStream
                        
                      | StreamError DecompressError String
data DecompressError =
     
     
     TruncatedInput
     
     
     
     
     
   | DictionaryRequired
     
     
     
     
     
   | DataError
foldDecompressStream :: (S.ByteString -> a -> a) -> a
                 -> (DecompressError -> String -> a)
                 -> DecompressStream -> a
foldDecompressStream chunk end err = fold
  where
    fold StreamEnd               = end
    fold (StreamChunk bs stream) = chunk bs (fold stream)
    fold (StreamError code msg)  = err code msg
fromDecompressStream :: DecompressStream -> L.ByteString
fromDecompressStream =
  foldDecompressStream L.Chunk L.Empty
    (\_code msg -> error ("Codec.Compression.Zlib: " ++ msg))
compress
  :: Stream.Format
  -> CompressParams
  -> L.ByteString
  -> L.ByteString
compress format
  (CompressParams compLevel method bits memLevel strategy initChunkSize)
  input =
  L.fromChunks $ Stream.run $ do
    Stream.deflateInit format compLevel method bits memLevel strategy
    case L.toChunks input of
      [] -> fillBuffers 20 [] 
      S.PS inFPtr offset length : chunks -> do
        Stream.pushInputBuffer inFPtr offset length
        fillBuffers initChunkSize chunks
  where
    
    
    
    
    
  fillBuffers :: Int
              -> [S.ByteString]
              -> Stream [S.ByteString]
  fillBuffers outChunkSize inChunks = do
#ifdef DEBUG
    Stream.consistencyCheck
#endif
    
    
    
    
    
    inputBufferEmpty <- Stream.inputBufferEmpty
    outputBufferFull <- Stream.outputBufferFull
    assert (inputBufferEmpty || outputBufferFull) $ return ()
    when outputBufferFull $ do
      outFPtr <- Stream.unsafeLiftIO (S.mallocByteString outChunkSize)
      Stream.pushOutputBuffer outFPtr 0 outChunkSize
    if inputBufferEmpty
      then case inChunks of
             [] -> drainBuffers []
             S.PS inFPtr offset length : inChunks' -> do
                Stream.pushInputBuffer inFPtr offset length
                drainBuffers inChunks'
      else drainBuffers inChunks
  drainBuffers ::
      [S.ByteString]
   -> Stream [S.ByteString]
  drainBuffers inChunks = do
    inputBufferEmpty' <- Stream.inputBufferEmpty
    outputBufferFull' <- Stream.outputBufferFull
    assert(not outputBufferFull'
       && (null inChunks || not inputBufferEmpty')) $ return ()
    
    
    let flush = if null inChunks then Stream.Finish else Stream.NoFlush
    status <- Stream.deflate flush
    case status of
      Stream.Ok -> do
        outputBufferFull <- Stream.outputBufferFull
        if outputBufferFull
          then do (outFPtr, offset, length) <- Stream.popOutputBuffer
                  outChunks <- Stream.unsafeInterleave
                    (fillBuffers defaultCompressBufferSize inChunks)
                  return (S.PS outFPtr offset length : outChunks)
          else do fillBuffers defaultCompressBufferSize inChunks
      Stream.StreamEnd -> do
        inputBufferEmpty <- Stream.inputBufferEmpty
        assert inputBufferEmpty $ return ()
        outputBufferBytesAvailable <- Stream.outputBufferBytesAvailable
        if outputBufferBytesAvailable > 0
          then do (outFPtr, offset, length) <- Stream.popOutputBuffer
                  Stream.finalise
                  return [S.PS outFPtr offset length]
          else do Stream.finalise
                  return []
      Stream.Error code msg -> case code of
        Stream.BufferError  -> fail "BufferError should be impossible!"
        Stream.NeedDict     -> fail "NeedDict is impossible!"
        _                   -> fail msg
decompress
  :: Stream.Format
  -> DecompressParams
  -> L.ByteString
  -> L.ByteString
decompress format params = fromDecompressStream
                         . decompressWithErrors format params
decompressWithErrors
  :: Stream.Format
  -> DecompressParams
  -> L.ByteString
  -> DecompressStream
decompressWithErrors format (DecompressParams bits initChunkSize) input =
  Stream.run $ do
    Stream.inflateInit format bits
    case L.toChunks input of
      [] -> fillBuffers 4 [] 
      S.PS inFPtr offset length : chunks -> do
        Stream.pushInputBuffer inFPtr offset length
        fillBuffers initChunkSize chunks
  where
    
    
    
    
    
  fillBuffers :: Int
              -> [S.ByteString]
              -> Stream DecompressStream
  fillBuffers outChunkSize inChunks = do
#ifdef DEBUG
    Stream.consistencyCheck
#endif
    
    
    
    
    
    inputBufferEmpty <- Stream.inputBufferEmpty
    outputBufferFull <- Stream.outputBufferFull
    assert (inputBufferEmpty || outputBufferFull) $ return ()
    when outputBufferFull $ do
      outFPtr <- Stream.unsafeLiftIO (S.mallocByteString outChunkSize)
      Stream.pushOutputBuffer outFPtr 0 outChunkSize
    if inputBufferEmpty
      then case inChunks of
             [] -> drainBuffers []
             S.PS inFPtr offset length : inChunks' -> do
                Stream.pushInputBuffer inFPtr offset length
                drainBuffers inChunks'
      else drainBuffers inChunks
  drainBuffers ::
      [S.ByteString]
   -> Stream DecompressStream
  drainBuffers inChunks = do
    inputBufferEmpty' <- Stream.inputBufferEmpty
    outputBufferFull' <- Stream.outputBufferFull
    assert(not outputBufferFull'
       && (null inChunks || not inputBufferEmpty')) $ return ()
    
    
    status <- Stream.inflate Stream.NoFlush
    case status of
      Stream.Ok -> do
        outputBufferFull <- Stream.outputBufferFull
        if outputBufferFull
          then do (outFPtr, offset, length) <- Stream.popOutputBuffer
                  outChunks <- Stream.unsafeInterleave
                    (fillBuffers defaultDecompressBufferSize inChunks)
                  return $ StreamChunk (S.PS outFPtr offset length) outChunks
          else do fillBuffers defaultDecompressBufferSize inChunks
      Stream.StreamEnd      -> finish StreamEnd
      Stream.Error code msg -> case code of
        Stream.BufferError  -> finish (StreamError TruncatedInput msg')
          where msg' = "premature end of compressed stream"
        Stream.NeedDict     -> finish (StreamError DictionaryRequired msg)
        Stream.DataError    -> finish (StreamError DataError msg)
        _                   -> fail msg
  
  
  finish end = do
    
    
    
    outputBufferBytesAvailable <- Stream.outputBufferBytesAvailable
    if outputBufferBytesAvailable > 0
      then do (outFPtr, offset, length) <- Stream.popOutputBuffer
              Stream.finalise
              return (StreamChunk (S.PS outFPtr offset length) end)
      else do Stream.finalise
              return end