{-# LANGUAGE OverloadedStrings #-}

module System.IO.Streams.Tests.Zlib (tests) where

------------------------------------------------------------------------------
import qualified Codec.Compression.GZip               as GZ
import qualified Codec.Compression.Zlib               as Z
import           Control.Monad                        hiding (mapM)
import           Data.ByteString.Builder              (Builder, byteString)
import           Data.ByteString.Builder.Extra        (flush)
import           Data.ByteString.Char8                (ByteString)
import qualified Data.ByteString.Char8                as S
import qualified Data.ByteString.Lazy.Char8           as L
import           Prelude                              hiding (mapM, read)
import           Test.Framework
import           Test.Framework.Providers.HUnit
import           Test.Framework.Providers.QuickCheck2
import           Test.HUnit                           hiding (Test)
import           Test.QuickCheck                      hiding (output)
import           Test.QuickCheck.Monadic
------------------------------------------------------------------------------
import           System.IO.Streams
import           System.IO.Streams.Tests.Common

tests :: [Test]
tests = [ testIdGzip
        , testIdCompress
        , testBigString
        , testBuilderFlushGZip
        , testBuilderFlushCompress
        , testTrivials
        ]


------------------------------------------------------------------------------
testIdGzip :: Test
testIdGzip = testProperty "zlib/idGZip" $ monadicIO $ forAllM arbitrary prop
  where
    prop :: [ByteString] -> PropertyM IO ()
    prop l = propId "idGZip" GZ.decompress GZ.compress gunzip gzip l


------------------------------------------------------------------------------
testIdCompress :: Test
testIdCompress = testProperty "zlib/idCompress" $ monadicIO $
                 forAllM arbitrary prop
  where
    prop :: [ByteString] -> PropertyM IO ()
    prop l = propId "idCompress" Z.decompress Z.compress
                    decompress compress l


------------------------------------------------------------------------------
propId :: String
       -> (L.ByteString -> L.ByteString)
       -> (L.ByteString -> L.ByteString)
       -> (InputStream ByteString -> IO (InputStream ByteString))
       -> (CompressionLevel -> OutputStream ByteString
                            -> IO (OutputStream ByteString))
       -> [ByteString]
       -> PropertyM IO ()
propId name inf def infStr defStr l0 = do
    pre (not (null l0) && L.length (L.fromChunks l0) > 0)
    liftQ $ do
        let l   = L.fromChunks $ l0 ++
                  [ S.concat $ L.toChunks $ L.take 32000 $ L.fromChunks $
                    cycle l0 ]
        let inp = def l

        is          <- fromList (L.toChunks inp) >>= infStr
        (os0, grab) <- listOutputStream
        os          <- defStr defaultCompressionLevel os0

        connect is os
        outp        <- liftM L.fromChunks grab

        assertEqual name l (inf outp)



------------------------------------------------------------------------------
testBigString :: Test
testBigString = testCase "zlib/bigString" $ do
    let l = S.concat $ L.toChunks $ L.take 640000 $ L.fromChunks $ cycle
            [ "lfkdsjflkdshflkjdhsfkljhdslkfhdslakjfhlkdsjhflkjdsahflkjhsa"
            , "39287647893264987368947632198746328974698327649873216498713"
            , "bznmbxz879hJKHYG^&%^&^%*&^%*&^%*&^%&*^%&*65tykjhdgbmdnvkjch"
            , "VBUYDUHKJC*(HJKDHLCJBUYEOUIHJCHUOY&*^(*)@HJDNM<NCKJHVUKHLKJ"
            , "())))))A(JXNZMWJ#*&^&YIUHDHKJHKJLNCnkfdhkfy32huukhksnc8310s"
            , "|||||A!@2iuozukx|djklu8udjlm3n3888****8ijlfkdjvnchiuyuis';'"
            , "56%^%^%^%^4dnmcnjjk3NJKNKJCiejnhfj[]][}{[][]f[][]d]299ikkjn"
            , "fjlkdjklfozi898888****2jkmdc,x.//,.,/asl;oi39uijdp'''qwjkjh"
            ]

    (os0, grab) <- listOutputStream
    os <- gzip 5 os0

    fromList [l] >>= connectTo os

    out <- liftM L.fromChunks grab



    let o1 = L.fromChunks [l]
    let o2 = GZ.decompress out

    when (o1 /= o2) $ do
        putStrLn "o1 /= o2"
        putStrLn $ "o1 = " ++ (show $ S.concat $ L.toChunks $ L.take 1000 o1)
                     ++ "..."
        putStrLn $ "o2 = " ++ (show $ S.concat $ L.toChunks $ L.take 1000 o2)
                     ++ "..."

        putStrLn $ "len(o1)=" ++ show (L.length o1)
        putStrLn $ "len(o2)=" ++ show (L.length o2)

    assertBool "bigString1" $ o1 == o2

    is2 <- fromList ([""] ++ L.toChunks out ++ [""]) >>= gunzip
    (os1, grab') <- listOutputStream
    connect is2 os1
    out' <- liftM L.fromChunks grab'

    assertBool "bigString2" $ o1 == out'



------------------------------------------------------------------------------
testBuilderFlushGZip :: Test
testBuilderFlushGZip = testProperty "zlib/builderFlushGZip" $ monadicIO $
                       forAllM arbitrary prop
  where
    prop :: (ByteString, ByteString) -> PropertyM IO ()
    prop (a,b) = propBuilderFlush "gzip" GZ.decompress gzipBuilder a b


------------------------------------------------------------------------------
testBuilderFlushCompress :: Test
testBuilderFlushCompress = testProperty "zlib/builderFlushCompress" $
                           monadicIO $ forAllM arbitrary prop
  where
    prop :: (ByteString, ByteString) -> PropertyM IO ()
    prop (a,b) = propBuilderFlush "zlib" Z.decompress compressBuilder a b


------------------------------------------------------------------------------
propBuilderFlush :: String
                 -> (L.ByteString -> L.ByteString)
                 -> (CompressionLevel
                     -> OutputStream Builder -> IO (OutputStream Builder))
                 -> ByteString
                 -> ByteString
                 -> PropertyM IO ()
propBuilderFlush name inf comp a b = do
    pre (not (S.null a) && not (S.null b))
    liftQ $ do
        t 7 [ byteString a, flush, flush, byteString b
            , flush, flush ]

        t 4 [ byteString a, flush, flush, byteString b ]

  where
    t expected input = do
        (os0, grab) <- listOutputStream
        os          <- builderStream os0 >>= comp defaultCompressionLevel

        fromList input >>= connectTo os
        xs <- grab

        when (length xs /= expected) $ putStrLn $ "xs is " ++ show xs
        assertEqual (name ++ "/len") expected (length xs)

        let outp = inf $ L.fromChunks xs

        assertEqual (name ++ "/eq") (L.fromChunks [a,b]) outp


------------------------------------------------------------------------------
testTrivials :: Test
testTrivials = testCase "zlib/trivials" $ do
    let cl = CompressionLevel 4
    coverReadInstance cl
    coverShowInstance cl
    coverEqInstance   cl
