module Codec.Compression.LZF (compress,decompress,compressString,decompressString) where

import Data.Char(ord,chr)
import Control.Monad (when)
import Foreign
import Foreign.C

{-# CFILES liblzf-1.5/lzf_c.c liblzf-1.5/lzf_d.c #-}
foreign import ccall unsafe "lzf_compress" _compress :: Ptr a -> CUInt -> Ptr b -> CUInt -> IO CUInt
foreign import ccall unsafe "lzf_decompress" _decompress :: Ptr a -> CUInt -> Ptr b -> CUInt -> IO CUInt

-- | Compress a block of data. The length of data is 
-- not recorded. Returns the length of output or 0
-- if it is longer than the size of the output buffer.
compress :: Ptr a -> Int -> Ptr b -> Int -> IO Int
compress input ilen output olen = do
  res <- _compress input (fromIntegral ilen) output (fromIntegral olen) 
  return (fromIntegral res)

-- | Decompress a block of data. Returns the length
-- of data uncompressed or 0 on error.
decompress :: Ptr a -> Int -> Ptr b -> Int -> IO Int
decompress input ilen output olen = do
  res <- _decompress input (fromIntegral ilen) output (fromIntegral olen) 
  return (fromIntegral res)

-- | Compress a string with worst case length = original + 1.
-- The first char of the string contains the length of the original.
compressString :: String -> IO String
compressString str =
  withCStringLen str $ \(inp,len) ->
    allocaBytes len $ \out -> do
      res <- compress inp len out (len-2)
      if res == 0 then return ("\0"++str)
                  else do c <- peekCStringLen (out,res)
                          return (chr len:c)

-- | Decompress a string compressed with compressString.
decompressString :: String -> IO String
decompressString ('\0':rest) = return rest
decompressString (ol:str) = 
  withCStringLen str $ \(inp,len) -> do
    let olen = ord ol
    allocaBytes olen $ \out -> do
      res <- decompress inp len out olen
      when (res == 0) $ fail "Codec.Compression.LZF.decompressString"
      peekCStringLen (out,res)


{-
t s = do c <- compressString s
         d <- decompressString c
         putStrLn $ unwords ["lzf test",show $ length s,"=>",show $ length c,show (s==d)]
-}