/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "InflaterTest.h"

#include <decaf/util/zip/Deflater.h>
#include <decaf/util/zip/Inflater.h>
#include <decaf/util/zip/Adler32.h>
#include <decaf/util/zip/CRC32.h>

#include <vector>

using namespace std;
using namespace decaf;
using namespace decaf::lang;
using namespace decaf::lang::exceptions;
using namespace decaf::util;
using namespace decaf::util::zip;

////////////////////////////////////////////////////////////////////////////////
InflaterTest::InflaterTest() {
}

////////////////////////////////////////////////////////////////////////////////
InflaterTest::~InflaterTest() {
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::setUp() {
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::tearDown() {
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testEnd() {

    // test method of java.util.zip.inflater.end()
    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };

    int r = 0;
    Inflater inflate;
    inflate.setInput( byteArray, 5, 0, 5 );
    inflate.end();

    try {
        inflate.reset();
        inflate.setInput( byteArray, 5, 0, 5 );
    } catch( IllegalStateException& e ) {
        r = 1;
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "inflate can still be used after end is called", 1, r );

    Inflater i;
    i.end();
    // check for exception
    i.end();
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testFinished() {

    static const std::size_t SIZE = 10;
    unsigned char byteArray[] = { 1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5' };

    std::vector<unsigned char> outPutBuf( 500 );
    std::vector<unsigned char> outPutInf( 500 );

    Deflater deflater;
    deflater.setInput( byteArray, SIZE, 0, SIZE );
    deflater.finish();
    while( !deflater.finished() ) {
        deflater.deflate( outPutBuf );
    }

    Inflater inflate;
    try {

        while( !( inflate.finished() ) ) {
            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }

            inflate.inflate( outPutInf );
        }
        CPPUNIT_ASSERT_MESSAGE(
            "the method finished() returned false when no more data needs to be decompressed",
            inflate.finished() );
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL( "Invalid input to be decompressed" );
    }

    for( std::size_t i = 0; i < SIZE; i++ ) {
        CPPUNIT_ASSERT_MESSAGE( "Final decompressed data does not equal the original data",
                                byteArray[i] == outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE(
        "final decompressed data contained more bytes than original - finished()",
        0, (int)outPutInf[SIZE]);
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testGetAdler() {

    static const int DICT_SIZE = 7;
    static const int ARRAY_SIZE = 15;

    unsigned char dictionary[] = { 'e', 'r', 't', 'a', 'b', 2, 3 };
    unsigned char byteArray[] = { 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r' };
    std::vector<unsigned char> outPutBuf( 100 );
    std::vector<unsigned char> outPutInf( 100 );

    Deflater defl;
    defl.setDictionary( dictionary, DICT_SIZE, 0, DICT_SIZE );

    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    defl.end();

    // getting the checkSum value through the Adler32 class
    Adler32 adl;
    adl.update( dictionary, DICT_SIZE, 0, DICT_SIZE );
    long long checkSumR = adl.getValue();

    Inflater inflateDiction;
    inflateDiction.setInput( outPutBuf );
    inflateDiction.inflate( outPutInf );

    CPPUNIT_ASSERT_MESSAGE( "Inflater did not detect the need for a Dictionary",
                            inflateDiction.needsDictionary() );

    CPPUNIT_ASSERT_MESSAGE(
        "the checksum value returned by getAdler() is not the same as the checksum returned "
        "by creating the adler32 instance",
        checkSumR == inflateDiction.getAdler() );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testGetRemaining() {

    unsigned char byteArray[] = { 1, 3, 5, 6, 7 };
    Inflater inflate;
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "upon creating an instance of inflate, getRemaining returned a non zero value",
                                  0, (int)inflate.getRemaining());
    inflate.setInput( byteArray, 5, 0, 5 );
    CPPUNIT_ASSERT_MESSAGE(
        "getRemaining returned zero when there is input in the input buffer",
        inflate.getRemaining() != 0 );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testInflateVector() {

    static const std::size_t SIZE = 39;

    unsigned char byteArray[] = { 120, (unsigned char) -38, 75, (unsigned char) -54, 73,
                                  (unsigned char) -52, 80, 40, 46, 41, (unsigned char) -54,
                                  (unsigned char) -52, 75, 87, 72, (unsigned char) -50,
                                  (unsigned char) -49, 43, 73, (unsigned char) -52,
                                  (unsigned char) -52, 43, 86, 72, 2, 10, 34, 99,
                                  (unsigned char) -123, (unsigned char) -60, (unsigned char) -68, 20,
                                  (unsigned char) -80, 32, 0, (unsigned char) -101, (unsigned char) -69,
                                  17, 84 };

    std::string codedString = "blah string contains blahblahblahblah and blah";

    std::vector<unsigned char> outPutBuf( byteArray, byteArray + SIZE );
    std::vector<unsigned char> outPutInf( 500, 0 );

    Inflater inflate;
    try {
        while( !( inflate.finished() ) ) {
            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }
            inflate.inflate( outPutInf );
        }
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    }

    for( std::size_t i = 0; i < codedString.length(); i++ ) {
        CPPUNIT_ASSERT_MESSAGE( "Final decompressed data does not equal the original data",
                                codedString[i] == outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "final decompressed data contained more bytes than original - inflateB",
                                  0, (int)outPutInf[codedString.length()] );
    // testing for an empty input array
    outPutBuf.clear();
    outPutBuf.resize( 500 );
    outPutInf.assign( outPutInf.size(), 0 );
    std::vector<unsigned char> emptyArray( 11, 0 );
    int x = 0;
    Deflater defEmpty( 3 );
    defEmpty.setInput( emptyArray );
    while( !( defEmpty.needsInput() ) ) {
        x += defEmpty.deflate( outPutBuf, x, (int)outPutBuf.size() - x );
    }
    defEmpty.finish();
    while( !( defEmpty.finished() ) ) {
        x += defEmpty.deflate( outPutBuf, x, (int)outPutBuf.size() - x );
    }
    CPPUNIT_ASSERT_MESSAGE( "the total number of unsigned char from deflate did not equal "
                            "getTotalOut - inflate(unsigned char)",
                            (long long)x == defEmpty.getBytesWritten() );
    CPPUNIT_ASSERT_MESSAGE(
                "the number of input unsigned char from the array did not correspond with getTotalIn - inflate(unsigned char)",
                (std::size_t)defEmpty.getBytesRead() == emptyArray.size() );
    Inflater infEmpty;
    try {
        while( !( infEmpty.finished() ) ) {
            if( infEmpty.needsInput() ) {
                infEmpty.setInput( outPutBuf );
            }
            infEmpty.inflate( outPutInf );
        }
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL( "Invalid input to be decompressed" );
    }

    for( std::size_t i = 0; i < 11; i++ ) {
        CPPUNIT_ASSERT_MESSAGE( "Final decompressed data does not equal the original data",
                                emptyArray[i] == outPutInf[i] );
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "Final decompressed data does not equal zero",
                                      0, (int)outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Final decompressed data contains more element than original data",
                                  0, (int)outPutInf[11] );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testInflateB1() {

    static const std::size_t CODEDATA_SIZE = 39;

    unsigned char codedData[] = { 120, (unsigned char) -38, 75, (unsigned char) -54, 73,
                                  (unsigned char) -52, 80, 40, 46, 41, (unsigned char) -54,
                                  (unsigned char) -52, 75, 87, 72, (unsigned char) -50,
                                  (unsigned char) -49, 43, 73, (unsigned char) -52, (unsigned char) -52,
                                  43, 86, 72, 2, 10, 34, 99, (unsigned char) -123, (unsigned char) -60,
                                  (unsigned char) -68, 20, (unsigned char) -80, 32, 0, (unsigned char) -101,
                                  (unsigned char) -69, 17, 84 };
    std::string codedString = "blah string contains blahblahblahblah and blah";

    Inflater infl1;
    Inflater infl2;

    std::vector<unsigned char> result( 100 );
    std::size_t decLen = 0;

    infl1.setInput( codedData, CODEDATA_SIZE, 0, CODEDATA_SIZE );
    try {
        decLen = infl1.inflate( result );
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Unexpected DataFormatException");
    }

    infl1.end();
    CPPUNIT_ASSERT_EQUAL( codedString, std::string( result.begin(), result.begin() + decLen ) );
    codedData[5] = 0;

    infl2.setInput( codedData, CODEDATA_SIZE, 0, CODEDATA_SIZE );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "Expected DataFormatException",
        decLen = infl2.inflate( result ),
        DataFormatException );

    infl2.end();
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testInflateBII() {

    static const std::size_t SIZE = 39;

    unsigned char byteArray[] = { 120, (unsigned char) -38, 75, (unsigned char) -54, 73,
                                  (unsigned char) -52, 80, 40, 46, 41, (unsigned char) -54,
                                  (unsigned char) -52, 75, 87, 72, (unsigned char) -50,
                                  (unsigned char) -49, 43, 73, (unsigned char) -52,
                                  (unsigned char) -52, 43, 86, 72, 2, 10, 34, 99,
                                  (unsigned char) -123, (unsigned char) -60, (unsigned char) -68,
                                  20, (unsigned char) -80, 32, 0, (unsigned char) -101,
                                  (unsigned char) -69, 17, 84 };

    std::string codedString = "blah string contains blahblahblahblah and blah";

    std::vector<unsigned char> outPutBuf( byteArray, byteArray + SIZE );
    std::vector<unsigned char> outPutInf( 100 );
    int y = 0;
    Inflater inflate;
    try {
        while( !( inflate.finished() ) ) {
            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }
            y += inflate.inflate( outPutInf, y, (int)outPutInf.size() - y );
        }
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    }
    for( std::size_t i = 0; i < codedString.length(); i++ ) {
        CPPUNIT_ASSERT_MESSAGE(
                        "Final decompressed data does not equal the original data",
                        codedString[i] == outPutInf[i]);
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "final decompressed data contained more bytes than original - inflateB",
                                  0, (int)outPutInf[codedString.length()]);

    // test boundary checks
    inflate.reset();
    int r = 0;
    int offSet = 0;
    int lengthError = 101;
    try {
        if( inflate.needsInput() ) {
            inflate.setInput( outPutBuf );
        }
        inflate.inflate( outPutInf, offSet, lengthError );

    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    } catch( IndexOutOfBoundsException& e ) {
        r = 1;
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "out of bounds error did not get caught", 1, r );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testInflateBII1() {

    static const std::size_t CODEDATA_SIZE = 39;
    unsigned char codedData[] = { 120, (unsigned char) -38, 75, (unsigned char) -54, 73,
                                  (unsigned char) -52, 80, 40, 46, 41, (unsigned char) -54,
                                  (unsigned char) -52, 75, 87, 72, (unsigned char) -50,
                                  (unsigned char) -49, 43, 73, (unsigned char) -52,
                                  (unsigned char) -52, 43, 86, 72, 2, 10,
                                  34, 99, (unsigned char) -123, (unsigned char) -60,
                                  (unsigned char) -68, 20, (unsigned char) -80, 32, 0,
                                  (unsigned char) -101, (unsigned char) -69, 17, 84 };

    std::string codedString = "blah string";

    Inflater infl1;
    Inflater infl2;

    std::vector<unsigned char> result( 100 );
    std::size_t decLen = 0;

    infl1.setInput( codedData, CODEDATA_SIZE, 0, CODEDATA_SIZE );
    try {
        decLen = infl1.inflate( result, 10, 11 );
        CPPUNIT_ASSERT( decLen != 0 );
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Unexpected DataFormatException");
    }

    std::string outputStr;
    for( std::size_t ix = 10; ix < decLen + 10; ++ix ) {
        outputStr += (char)result[ix];
    }

    infl1.end();
    CPPUNIT_ASSERT_EQUAL( codedString, outputStr );
    codedData[5] = 0;

    infl2.setInput( codedData, CODEDATA_SIZE, 0, CODEDATA_SIZE );
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "Expected DataFormatException",
        decLen = infl2.inflate( result, 10, 11 ),
        DataFormatException );

    infl2.end();
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testConstructor() {

    Inflater inflate;
    CPPUNIT_ASSERT_MESSAGE( "failed to create the instance of inflater",
                            inflate.getBytesRead() == 0LL );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testConstructorZ() {

    static const std::size_t SIZE = 10;

    // note does not throw exception if deflater has a header, but inflater
    // doesn't or vice versa.
    unsigned char byteArray[] = { 1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5' };
    std::vector<unsigned char> outPutBuf( 500 );

    Deflater deflater;

    deflater.setInput( byteArray, SIZE, 0, SIZE );
    deflater.finish();

    int read = 0;
    while( !deflater.finished() ) {
        read = deflater.deflate( outPutBuf, read, (int)outPutBuf.size() - read );
    }
    deflater.end();

    Inflater inflate( true );
    std::vector<unsigned char> outPutInf( 500 );

    int r = 0;
    try {

        while( !( inflate.finished() ) ) {

            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }

            inflate.inflate( outPutInf );
        }
        for( std::size_t i = 0; i < SIZE; i++ ) {
            CPPUNIT_ASSERT_EQUAL_MESSAGE(
                "the output array from inflate should contain 0 because the header of inflate "
                "and deflate did not match, but this failed",
                0, (int)outPutBuf[i] );
        }
    } catch( DataFormatException& e ) {
        r = 1;
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Error: exception should be thrown because of header inconsistency", 1, r );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testNeedsDictionary() {

    static const int DICT_SIZE = 7;
    static const int ARRAY_SIZE = 15;

    // This test is very close to getAdler()
    std::vector<unsigned char> dictionary( DICT_SIZE );
    dictionary[0] = 'e';
    dictionary[1] = 'r';
    dictionary[2] = 't';
    dictionary[3] = 'a';
    dictionary[4] = 'b';
    dictionary[5] = 2;
    dictionary[6] = 3;

    unsigned char byteArray[] = { 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r' };
    std::vector<unsigned char> outPutBuf( 100 );

    Deflater defl;
    defl.setDictionary( dictionary, 0, DICT_SIZE );

    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }

    // note: this flag is set after inflate is called
    std::vector<unsigned char> outPutInf( 500 );

    // testing with dictionary set.
    Inflater inflateDiction;
    if( inflateDiction.needsInput() ) {
        inflateDiction.setInput( outPutBuf );
    }
    try {
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "should return 0 because needs dictionary",
                                      0, (int)inflateDiction.inflate( outPutInf ) );
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Should not cause exception");
    }
    CPPUNIT_ASSERT_MESSAGE(
        "method needsDictionary returned false when dictionary was used in deflater",
        inflateDiction.needsDictionary() );

    // Recompress without a Dictionary
    outPutBuf.assign( outPutBuf.size(), 0 );
    outPutInf.assign( outPutInf.size(), 0 );

    defl.reset();
    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    defl.end();

    // testing without dictionary
    Inflater inflate;
    try {
        inflate.setInput( outPutBuf );
        inflate.inflate( outPutInf );
        CPPUNIT_ASSERT_MESSAGE(
            "method needsDictionary returned true when dictionary was not used in deflater",
            !inflate.needsDictionary() );
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL( "Input to inflate is invalid or corrupted - needsDictionary" );
    }

    Inflater inf;
    CPPUNIT_ASSERT( !inf.needsDictionary() );
    CPPUNIT_ASSERT_EQUAL( 0LL, inf.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( 0LL, inf.getBytesWritten() );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testNeedsInput() {

    Inflater inflate;
    CPPUNIT_ASSERT_MESSAGE(
        "needsInput give the wrong bool value as a result of no input buffer",
        inflate.needsInput() );

    static const std::size_t SIZE = 12;

    unsigned char byteArray[] = { 2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9 };
    inflate.setInput( byteArray, SIZE, 0, SIZE );
    CPPUNIT_ASSERT_MESSAGE(
        "methodNeedsInput returned true when the input buffer is full",
        !inflate.needsInput() );

    inflate.reset();
    std::vector<unsigned char> byteArrayEmpty( 0 );;
    inflate.setInput( byteArrayEmpty );
    inflate.needsInput();
    CPPUNIT_ASSERT_MESSAGE(
        "needsInput give wrong bool value as a result of an empty input buffer",
        inflate.needsInput() );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testReset() {

    static const std::size_t SIZE = 10;

    // note does not throw exception if deflater has a header, but inflater
    // doesn't or vice versa.
    unsigned char byteArray[] = { 1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5' };
    std::vector<unsigned char> outPutBuf( 500 );

    Deflater deflater;

    deflater.setInput( byteArray, SIZE, 0, SIZE );
    deflater.finish();

    int read = 0;
    while( !deflater.finished() ) {
        read = deflater.deflate( outPutBuf, read, (int)outPutBuf.size() - read );
    }
    deflater.end();

    std::vector<unsigned char> outPutInf( 100 );
    int y = 0;
    Inflater inflate;
    try {

        while( !( inflate.finished() ) ) {
            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }
            y += inflate.inflate( outPutInf, y, (int)outPutInf.size() - y );
        }

    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    }

    for( std::size_t i = 0; i < SIZE; i++ ) {
        CPPUNIT_ASSERT_MESSAGE( "Final decompressed data does not equal the original data",
                                byteArray[i] == outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "final decompressed data contained more bytes than original - reset",
                                  0, (int)outPutInf[SIZE] );

    // testing that resetting the inflater will also return the correct
    // decompressed data

    inflate.reset();
    try {

        while( !( inflate.finished() ) ) {
            if( inflate.needsInput() ) {
                inflate.setInput( outPutBuf );
            }
            inflate.inflate( outPutInf );
        }

    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    }

    for( std::size_t i = 0; i < SIZE; i++ ) {
        CPPUNIT_ASSERT_MESSAGE( "Final decompressed data does not equal the original data",
                                byteArray[i] == outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "final decompressed data contained more bytes than original - reset",
                                  0, (int)outPutInf[SIZE]);
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testSetInputB() {

    static const std::size_t SIZE = 12;
    unsigned char byteArray[] = { 2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9 };
    Inflater inflate;
    inflate.setInput( byteArray, SIZE, 0, SIZE );
    CPPUNIT_ASSERT_MESSAGE( "setInputB did not deliver any unsigned char to the input buffer",
                            inflate.getRemaining() != 0 );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testSetInputBIII() {

    static const std::size_t SIZE = 12;
    unsigned char byteArray[] = { 2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9 };
    int offSet = 6;
    int length = 6;

    Inflater inflate;
    inflate.setInput( byteArray, SIZE, offSet, length );
    CPPUNIT_ASSERT_MESSAGE( "setInputBII did not deliver the right number of bytes to the input buffer",
                            inflate.getRemaining() == length );

    // boundary check
    inflate.reset();
    int r = 0;
    try {
        inflate.setInput( byteArray, SIZE, 100, 100 );
    } catch( IndexOutOfBoundsException& e ) {
        r = 1;
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "boundary check is not present for setInput", 1, r );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testGetBytesRead() {

    Deflater def;
    Inflater inf;
    CPPUNIT_ASSERT_EQUAL( 0LL, def.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( 0LL, def.getBytesRead() );

    // Encode a String into bytes
    std::string inputString = "blahblahblah??";
    std::vector<unsigned char> input( inputString.begin(), inputString.end() );

    // Compress the bytes
    std::vector<unsigned char> output( 100 );

    def.setInput( input );
    def.finish();
    def.deflate( output );
    inf.setInput( output );
    int compressedDataLength = inf.inflate( input );
    CPPUNIT_ASSERT_EQUAL( (long long)compressedDataLength, inf.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( 16LL, inf.getBytesRead() );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testGetBytesWritten() {

    Deflater def;
    Inflater inf;
    CPPUNIT_ASSERT_EQUAL( 0LL, def.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( 0LL, def.getBytesWritten() );

    // Encode a String into bytes
    std::string inputString = "blahblahblah??";
    std::vector<unsigned char> input( inputString.begin(), inputString.end() );

    // Compress the bytes
    std::vector<unsigned char> output( 100 );
    def.setInput( input );
    def.finish();
    def.deflate( output );
    inf.setInput( output );
    int compressedDataLength = inf.inflate( input );
    CPPUNIT_ASSERT_EQUAL( 16LL, inf.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( compressedDataLength, (int)inf.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( 14LL, inf.getBytesWritten() );
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testInflate() {

    Inflater inf;
    std::vector<unsigned char> empty(0);
    int res = inf.inflate( empty );

    CPPUNIT_ASSERT_EQUAL( 0, (int)res );

    // Regression for HARMONY-2508
    Inflater inflater;
    std::vector<unsigned char> b( 1024 );
    CPPUNIT_ASSERT_EQUAL( 0, (int)inflater.inflate( b ) );
    inflater.end();

    {
        Inflater inflater;
        unsigned char array[] = { (unsigned char) -1 };
        inflater.setInput( array, 1, 0, 1 );
        try {
            inflater.inflate( b );

            // The RI detects malformed data on the malformed input { -1 }. Both
            // this implementation and the native zlib API return "need input"
            // on that data. This is an error if the stream is exhausted, but
            // not one that results in an exception in the Inflater API.
            CPPUNIT_ASSERT( inflater.needsInput() );
        } catch( DataFormatException& e ) {
            // expected
        }
    }

    {
        Inflater inflater2;
        unsigned char array[] = { (unsigned char) -1, (unsigned char) -1, (unsigned char) -1 };
        inflater2.setInput( array, 3, 0, 3);
        try {
            inflater2.inflate( b );
        } catch( DataFormatException& e ) {
            // expected
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testSetDictionaryB() {

    int i = 0;
    std::string inputString = "blah string contains blahblahblahblah and blah";
    std::vector<unsigned char> input( inputString.begin(), inputString.end() );
    std::string dictionary1String = "blah";
    std::vector<unsigned char> dictionary1( dictionary1String.begin(), dictionary1String.end() );
    std::string dictionary2String = "1234";
    std::vector<unsigned char> dictionary2( dictionary2String.begin(), dictionary2String.end() );

    std::vector<unsigned char> outputNo( 100 );
    std::vector<unsigned char> output1( 100 );
    std::vector<unsigned char> output2( 100 );

    Deflater defDictNo( 9 );
    Deflater defDict1( 9 );
    Deflater defDict2( 9 );

    defDict1.setDictionary( dictionary1 );
    defDict2.setDictionary( dictionary2 );

    defDictNo.setInput( input );
    defDict1.setInput( input );
    defDict2.setInput( input );

    defDictNo.finish();
    defDict1.finish();
    defDict2.finish();

    int dataLenNo = defDictNo.deflate( outputNo );
    int dataLen1 = defDict1.deflate( output1 );
    int dataLen2 = defDict2.deflate( output2 );

    bool passNo1 = false;
    bool passNo2 = false;
    bool pass12 = false;

    for( i = 0; i < ( dataLenNo < dataLen1 ? dataLenNo : dataLen1 ); i++ ) {
        if( outputNo[i] != output1[i] ) {
            passNo1 = true;
            break;
        }
    }
    for( i = 0; i < ( dataLenNo < dataLen1 ? dataLenNo : dataLen2 ); i++ ) {
        if( outputNo[i] != output2[i] ) {
            passNo2 = true;
            break;
        }
    }
    for( i = 0; i < ( dataLen1 < dataLen2 ? dataLen1 : dataLen2 ); i++ ) {
        if( output1[i] != output2[i] ) {
            pass12 = true;
            break;
        }
    }

    CPPUNIT_ASSERT_MESSAGE( "Compressed data the same for stream with dictionary and without it.", passNo1 );
    CPPUNIT_ASSERT_MESSAGE( "Compressed data the same for stream with dictionary and without it.", passNo2 );
    CPPUNIT_ASSERT_MESSAGE( "Compressed data the same for stream with different dictionaries.", pass12 );

    Inflater inflNo;
    Inflater infl1;
    Inflater infl2;

    std::vector<unsigned char> result(100);
    int decLen;

    inflNo.setInput( outputNo, 0, dataLenNo );
    decLen = inflNo.inflate( result );

    CPPUNIT_ASSERT( !inflNo.needsDictionary() );
    inflNo.end();
    CPPUNIT_ASSERT_EQUAL( inputString, std::string( result.begin(), result.begin() + decLen ) );

    infl1.setInput( output1, 0, dataLen1 );
    decLen = infl1.inflate( result );

    CPPUNIT_ASSERT( infl1.needsDictionary() );
    infl1.setDictionary( dictionary1 );
    decLen = infl1.inflate( result );
    infl1.end();
    CPPUNIT_ASSERT_EQUAL( inputString, std::string( result.begin(), result.begin() + decLen ) );

    infl2.setInput( output2, 0, dataLen2 );
    decLen = infl2.inflate( result );

    CPPUNIT_ASSERT(infl2.needsDictionary());
    infl2.setDictionary( dictionary2 );
    decLen = infl2.inflate( result );
    infl2.end();
    CPPUNIT_ASSERT_EQUAL(inputString, std::string( result.begin(), result.begin() + decLen ) );

    {
        Inflater inflNo;
        Inflater infl1;
        inflNo.setInput( outputNo, 0, dataLenNo );

        CPPUNIT_ASSERT_THROW_MESSAGE(
            "IllegalArgumentException expected.",
            infl1.setDictionary( dictionary1 ),
            IllegalArgumentException );

        inflNo.end();

        infl1.setInput( output1, 0, dataLen1 );
        decLen = infl1.inflate( result );

        CPPUNIT_ASSERT( infl1.needsDictionary() );

        CPPUNIT_ASSERT_THROW_MESSAGE(
            "IllegalArgumentException expected.",
            infl1.setDictionary( dictionary2 ),
            IllegalArgumentException );

        infl1.end();
    }
}

////////////////////////////////////////////////////////////////////////////////
void InflaterTest::testSetDictionaryBIII() {

    int i = 0;
    std::string inputString = "blah string contains blahblahblahblah and blah";
    std::vector<unsigned char> input( inputString.begin(), inputString.end() );
    std::string dictionary1String = "blah";
    std::vector<unsigned char> dictionary1( dictionary1String.begin(), dictionary1String.end() );
    std::string dictionary2String = "blahblahblah";
    std::vector<unsigned char> dictionary2( dictionary2String.begin(), dictionary2String.end() );

    std::vector<unsigned char> output1( 100 );
    std::vector<unsigned char> output2( 100 );
    std::vector<unsigned char> output3( 100 );

    Deflater defDict1( 9 );
    Deflater defDict2( 9 );
    Deflater defDict3( 9 );

    defDict1.setDictionary( dictionary1 );
    defDict2.setDictionary( dictionary2 );
    defDict3.setDictionary( dictionary2, 4, 4 );

    defDict1.setInput( input );
    defDict2.setInput( input );
    defDict3.setInput( input );

    defDict1.finish();
    defDict2.finish();
    defDict3.finish();

    int dataLen1 = defDict1.deflate( output1 );
    int dataLen2 = defDict2.deflate( output2 );
    int dataLen3 = defDict3.deflate( output3 );

    bool pass12 = false;
    bool pass23 = false;
    bool pass13 = true;

    for( i = 0; i < ( dataLen1 < dataLen2 ? dataLen1 : dataLen2 ); i++ ) {
        if( output1[i] != output2[i] ) {
            pass12 = true;
            break;
        }
    }
    for( i = 0; i < ( dataLen2 < dataLen3 ? dataLen2 : dataLen3 ); i++ ) {
        if( output2[i] != output3[i] ) {
            pass23 = true;
            break;
        }
    }
    for( i = 0; i < ( dataLen1 < dataLen3 ? dataLen1 : dataLen3 ); i++ ) {
        if( output1[i] != output3[i] ) {
            pass13 = false;
            break;
        }
    }

    CPPUNIT_ASSERT_MESSAGE( "Compressed data the same for stream with different dictionaries.", pass12 );
    CPPUNIT_ASSERT_MESSAGE( "Compressed data the same for stream with different dictionaries.", pass23 );
    CPPUNIT_ASSERT_MESSAGE( "Compressed data the differs for stream with the same dictionaries.", pass13 );

    Inflater infl1;
    Inflater infl2;
    Inflater infl3;

    std::vector<unsigned char> result(100);
    int decLen;

    infl1.setInput( output1, 0, dataLen1 );
    decLen = infl1.inflate( result );

    CPPUNIT_ASSERT( infl1.needsDictionary() );
    infl1.setDictionary( dictionary2, 4, 4 );
    decLen = infl1.inflate( result );
    infl1.end();
    CPPUNIT_ASSERT_EQUAL( inputString, std::string( result.begin(), result.begin() + decLen ) );

    infl2.setInput( output2, 0, dataLen2 );
    decLen = infl2.inflate( result );

    CPPUNIT_ASSERT(infl2.needsDictionary());
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException expected.",
        infl2.setDictionary( dictionary1 ),
        IllegalArgumentException );

    infl2.end();

    infl3.setInput( output3, 0, dataLen3 );
    decLen = infl3.inflate( result );

    CPPUNIT_ASSERT( infl3.needsDictionary() );
    infl3.setDictionary( dictionary1 );
    decLen = infl3.inflate( result );
    infl3.end();
    CPPUNIT_ASSERT_EQUAL( inputString, std::string( result.begin(), result.begin() + decLen ) );
}
