/*
 * 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 "DeflaterTest.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 <decaf/lang/Integer.h>

#include <decaf/lang/exceptions/IndexOutOfBoundsException.h>

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#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;

////////////////////////////////////////////////////////////////////////////////
DeflaterTest::DeflaterTest() {
}

////////////////////////////////////////////////////////////////////////////////
DeflaterTest::~DeflaterTest() {
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testDeflateVector() {

    unsigned char byteArray[5] = { 1, 3, 4, 7, 8 };

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

    int x = 0;

    Deflater defl;
    defl.setInput( byteArray, 5, 0, 5 );
    defl.finish();

    while( !defl.finished() ) {
        x += defl.deflate( outPutBuf );
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Deflater at end of stream, should return 0",
                                  0, (int)defl.deflate( outPutBuf ) );

    long long totalOut = defl.getBytesWritten();
    long long totalIn = defl.getBytesRead();

    CPPUNIT_ASSERT_EQUAL( (long long)x, totalOut );
    CPPUNIT_ASSERT_EQUAL( 5LL, totalIn );

    defl.end();

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

    CPPUNIT_ASSERT_EQUAL( totalIn, infl.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( totalOut, infl.getBytesRead() );

    for( int i = 0; i < 5; i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteArray[i], outPutInf[i] );
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Final decompressed data contained more bytes than original",
                                  (unsigned char) 0, outPutInf[5] );

    infl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testDeflateArray() {

    static const int BUFFER_SIZE = 50;
    static const int INPUT_SIZE = 5;

    unsigned char outPutBuf[BUFFER_SIZE];
    memset( outPutBuf, 0, BUFFER_SIZE );
    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };
    unsigned char outPutInf[BUFFER_SIZE];
    memset( outPutInf, 0, BUFFER_SIZE );

    int offSet = 1;
    int length = BUFFER_SIZE - 1;
    int x = 0;

    Deflater defl;

    defl.setInput( byteArray, 5, 0, 5 );
    defl.finish();

    while( !defl.finished() ) {
        x += defl.deflate( outPutBuf, BUFFER_SIZE, offSet, length );
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Deflater at end of stream, should return 0",
                                  0, (int)defl.deflate( outPutBuf, BUFFER_SIZE, offSet, length ) );

    long long totalOut = defl.getBytesWritten();
    long long totalIn = defl.getBytesRead();
    CPPUNIT_ASSERT_EQUAL( x, (int)totalOut );
    CPPUNIT_ASSERT_EQUAL( INPUT_SIZE, (int)totalIn );
    defl.end();

    Inflater infl;
    try {
        infl.setInput( outPutBuf, BUFFER_SIZE, offSet, length );
        while( !infl.finished() ) {
            infl.inflate( outPutInf, BUFFER_SIZE, 0, BUFFER_SIZE );
        }
    } catch( DataFormatException& e ) {
        CPPUNIT_FAIL("Invalid input to be decompressed");
    }

    CPPUNIT_ASSERT_EQUAL( totalIn, infl.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( totalOut, infl.getBytesRead() );
    for( int i = 0; i < INPUT_SIZE; i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteArray[i], outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Final decompressed data contained more bytes than original",
                                  0, (int) outPutInf[BUFFER_SIZE-1] );
    infl.end();

    // Set of tests testing the boundaries of the offSet/length
    Deflater deflater;
    static const int SIZE = 100;
    unsigned char outPutBuf2[SIZE];
    deflater.setInput( byteArray, 5, 0, 5 );

    for( int i = 0; i < 2; i++ ) {

        if( i == 0 ) {
            offSet = SIZE + 1;
            length = SIZE;
        } else {
            offSet = 0;
            length = SIZE + 1;
        }

        CPPUNIT_ASSERT_THROW_MESSAGE(
            "Should have thrown an IndexOutOfBoundsException",
            deflater.deflate( outPutBuf2, SIZE, offSet, length ),
            IndexOutOfBoundsException );
    }

    defl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testEnd() {

    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };
    std::vector<unsigned char> outPutBuf(100);

    Deflater defl;

    defl.setInput( byteArray, 5, 0, 5 );
    defl.finish();

    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }

    defl.end();

    helperEndTest( defl, "end" );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testFinish() {

    // This test already here, its the same as test_deflate()
    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };
    std::vector<unsigned char> outPutBuf(100);
    std::vector<unsigned char> outPutInf(100);

    int x = 0;
    Deflater defl;
    defl.setInput( byteArray, 5, 0, 5 );
    defl.finish();

    while( !defl.finished() ) {
        x += defl.deflate( outPutBuf );
    }

    long long totalOut = defl.getBytesWritten();
    long long totalIn = defl.getBytesRead();
    CPPUNIT_ASSERT_EQUAL( (long long)x, totalOut);
    CPPUNIT_ASSERT_EQUAL( 5LL, totalIn );
    defl.end();

    Inflater infl;
    infl.setInput( outPutBuf );
    while( !infl.finished() ) {
        infl.inflate( outPutInf );
    }
    CPPUNIT_ASSERT_EQUAL( totalIn, infl.getBytesWritten() );
    CPPUNIT_ASSERT_EQUAL( totalOut, infl.getBytesRead() );
    for( int i = 0; i < 5; i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteArray[i], outPutInf[i] );
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE( "Final decompressed data contained more bytes than original",
                                  0, (int)outPutInf[5] );
    infl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testFinished() {

    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };
    std::vector<unsigned char> outPutBuf( 100 );
    Deflater defl;

    CPPUNIT_ASSERT_MESSAGE( "Test 1: Deflater should not be finished.", !defl.finished() );
    defl.setInput( byteArray, 5, 0, 5 );
    CPPUNIT_ASSERT_MESSAGE( "Test 2: Deflater should not be finished.", !defl.finished() );
    defl.finish();
    CPPUNIT_ASSERT_MESSAGE( "Test 3: Deflater should not be finished.", !defl.finished() );
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    CPPUNIT_ASSERT_MESSAGE( "Test 4: Deflater should be finished.", defl.finished() );
    defl.end();
    CPPUNIT_ASSERT_MESSAGE( "Test 5: Deflater should be finished.", defl.finished() );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testGetAdler() {

    unsigned char byteArray[] = { 'a', 'b', 'c', 1, 2, 3 };
    std::vector<unsigned char> outPutBuf( 100 );
    Deflater defl;

    // getting the checkSum value using the Adler
    defl.setInput( byteArray, 5, 0, 5 );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    long long checkSumD = defl.getAdler();
    defl.end();

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

    CPPUNIT_ASSERT_EQUAL_MESSAGE( std::string() +
                                  "The checksum value returned by getAdler() is not the same " +
                                  "as the checksum returned by creating the adler32 instance",
                                  checkSumD, checkSumR );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testNeedsInput() {

    Deflater defl;
    CPPUNIT_ASSERT_MESSAGE( "needsInput give the wrong boolean value as a result of no input buffer",
                            defl.needsInput() );
    unsigned char byteArray[] = { 1, 2, 3 };
    defl.setInput( byteArray, 3, 0, 3 );
    CPPUNIT_ASSERT_MESSAGE( "needsInput give wrong boolean value as a result of a full input buffer",
                            !defl.needsInput() );
    std::vector<unsigned char> outPutBuf( 100 );
    while( !defl.needsInput() ) {
        defl.deflate( outPutBuf );
    }
    std::vector<unsigned char> emptyByteArray( 0 );
    defl.setInput( emptyByteArray );
    CPPUNIT_ASSERT_MESSAGE( "needsInput give wrong boolean value as a result of an empty input buffer",
                            defl.needsInput() );
    defl.setInput( byteArray, 3, 0, 3 );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    defl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testReset() {

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

    unsigned char curArray[5];
    unsigned char byteArray[] = { 1, 3, 4, 7, 8 };
    unsigned char byteArray2[] = { 8, 7, 4, 3, 1 };

    int x = 0;
    int orgValue = 0;
    Deflater defl;

    for( int i = 0; i < 3; i++ ) {

        if( i == 0 ) {
            memcpy( curArray, byteArray, 5 );
        } else if( i == 1 ) {
            memcpy( curArray, byteArray2, 5 );
        } else {
            defl.reset();
        }

        defl.setInput( curArray, 5, 0, 5 );
        defl.finish();
        while( !defl.finished() ) {
            x += defl.deflate( outPutBuf );
        }

        if( i == 0 ) {
            CPPUNIT_ASSERT_EQUAL( x, (int)defl.getBytesWritten() );
        } else if( i == 1 ) {
            CPPUNIT_ASSERT_EQUAL( x, orgValue );
        } else {
            CPPUNIT_ASSERT_EQUAL( x, orgValue * 2 );
        }

        if( i == 0 ) {
            orgValue = x;
        }

        try {
            Inflater infl;
            infl.setInput( outPutBuf );
            while( !infl.finished() ) {
                infl.inflate( outPutInf );
            }
            infl.end();
        } catch( DataFormatException e ) {
            CPPUNIT_FAIL( std::string( "Test " ) + Integer::toString( i ) +
                          ": Invalid input to be decompressed" );
        }

        if( i == 1 ) {
            memcpy( curArray, byteArray, 5 );
        }

        for( int j = 0; j < 5; j++ ) {
            CPPUNIT_ASSERT_EQUAL( curArray[j], outPutInf[j] );
        }

        CPPUNIT_ASSERT_EQUAL( 0, (int)outPutInf[5] );
    }
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetDictionaryVector() {

    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;
    long long deflAdler = defl.getAdler();
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "No dictionary set, no data deflated, getAdler should return 1",
                                  1LL, deflAdler );
    defl.setDictionary( dictionary, 0, DICT_SIZE );
    deflAdler = defl.getAdler();

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

    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    deflAdler = defl.getAdler();
    Adler32 adl2;
    adl2.update( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    realAdler = adl2.getValue();
    // Deflate is finished and there were bytes deflated that did not occur
    // in the dictionaryArray, therefore a new dictionary was automatically
    // set.
    CPPUNIT_ASSERT_EQUAL( realAdler, deflAdler );
    defl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetDictionaryBIII() {

    static const int DICT_SIZE = 9;
    static const int ARRAY_SIZE = 23;

    // This test is very close to getAdler()
    unsigned char dictionaryArray[] = { 'e', 'r', 't', 'a', 'b', 2, 3, 'o', 't' };
    unsigned char byteArray[] = { 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's',
                                  '3', 'w', 'r', 't', 'u', 'i', 'o', 4, 5, 6, 7 };
    std::vector<unsigned char> outPutBuf( 100 );

    int offSet = 4;
    int length = 5;

    Deflater defl;
    long long deflAdler = defl.getAdler();
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "No dictionary set, no data deflated, getAdler should return 1",
                                  1LL, deflAdler );
    defl.setDictionary( dictionaryArray, DICT_SIZE, offSet, length );
    deflAdler = defl.getAdler();

    // getting the checkSum value through the Adler32 class
    Adler32 adl;
    adl.update( dictionaryArray, DICT_SIZE, offSet, length );
    long long realAdler = adl.getValue();
    CPPUNIT_ASSERT_EQUAL( deflAdler, realAdler );

    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    while( !defl.needsInput() ) {
        defl.deflate( outPutBuf );
    }
    deflAdler = defl.getAdler();
    Adler32 adl2;
    adl2.update( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    realAdler = adl2.getValue();
    // Deflate is finished and there were bytes deflated that did not occur
    // in the dictionaryArray, therefore a new dictionary was automatically
    // set.
    CPPUNIT_ASSERT_EQUAL( realAdler, deflAdler );
    defl.end();

    // boundary check
    Deflater defl2;
    for( int i = 0; i < 2; i++ ) {

        if( i == 0 ) {
            offSet = 0;
            length = DICT_SIZE + 1;
        } else {
            offSet = DICT_SIZE + 1;
            length = 1;
        }

        try {
            defl2.setDictionary( dictionaryArray, DICT_SIZE, offSet, length );
            CPPUNIT_FAIL( std::string( "Test " ) + Integer::toString( i ) +
                          ": boundary check for setDictionary CPPUNIT_FAILed for offset " +
                          Integer::toString( offSet ) + " and length " +
                          Integer::toString( length ) );
        } catch( IndexOutOfBoundsException& e ) {
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetInputVector() {

    std::vector<unsigned char> byteVector( 3 );
    std::vector<unsigned char> outPutBuf( 100 );
    std::vector<unsigned char> outPutInf( 100 );

    byteVector[0] = 1;
    byteVector[1] = 2;
    byteVector[2] = 3;

    Deflater defl;
    defl.setInput( byteVector );
    CPPUNIT_ASSERT_MESSAGE( "the array buffer in setInput() is empty",
                    !defl.needsInput());
    // The second setInput() should be ignored since needsInput() return
    // false
    defl.setInput( byteVector );
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    defl.end();

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

    for( int i = 0; i < (int)byteVector.size(); i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteVector[i], outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL( (long long)byteVector.size(), infl.getBytesWritten() );
    infl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetInputBIII() {

    static const int SIZE = 5;

    unsigned char byteArray[] = { 1, 2, 3, 4, 5 };
    std::vector<unsigned char> outPutBuf( 50 );
    std::vector<unsigned char> outPutInf( 50 );

    int offSet = 1;
    int length = 3;

    Deflater defl;
    defl.setInput( byteArray, SIZE, offSet, length);

    CPPUNIT_ASSERT_MESSAGE( "the array buffer in setInput() is empty", !defl.needsInput() );

    // The second setInput() should be ignored since needsInput() return
    // false
    defl.setInput( byteArray, SIZE, offSet, length );
    defl.finish();

    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    defl.end();

    Inflater infl;
    infl.setInput( outPutBuf );
    while( !infl.finished() ) {
        infl.inflate( outPutInf );
    }

    for( int i = 0; i < length; i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteArray[i + offSet], outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL( length, (int)infl.getBytesWritten() );
    infl.end();

    // boundary check
    Deflater defl2;
    for( int i = 0; i < 2; i++ ) {

        if( i == 0 ) {
            offSet = 0;
            length = SIZE + 1;
        } else {
            offSet = SIZE + 1;
            length = 1;
        }

        try {
            defl2.setInput( byteArray, SIZE, offSet, length );
            CPPUNIT_FAIL( std::string( "Test " ) + Integer::toString( i ) +
                          ": boundary check for setInput CPPUNIT_FAILed for offset " +
                          Integer::toString( offSet ) + " and length " +
                          Integer::toString( length ) );
        } catch( IndexOutOfBoundsException& e ) {
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetLevel() {

    std::vector<unsigned char> byteArray( 100 );
    for( int ix = 0; ix < 100; ++ix ) {
        byteArray[ix] = (unsigned char)( ix + 1 );
    }

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

    long long totalOut;
    for( int i = 0; i < 10; i++ ) {

        {
            Deflater defl;
            defl.setLevel( i );
            outPutBuf.assign( outPutBuf.size(), 0 );
            defl.setInput( byteArray );
            while( !defl.needsInput() ) {
                defl.deflate( outPutBuf );
            }
            defl.finish();
            while( !defl.finished() ) {
                defl.deflate( outPutBuf );
            }
            totalOut = defl.getBytesWritten();
            defl.end();
        }
        {
            outPutBuf.assign( outPutBuf.size(), 0 );
            Deflater defl( i );
            defl.setInput( byteArray );
            while( !defl.needsInput() ) {
                defl.deflate( outPutBuf );
            }
            defl.finish();
            while( !defl.finished() ) {
                defl.deflate( outPutBuf );
            }
            CPPUNIT_ASSERT_EQUAL( totalOut, defl.getBytesWritten() );
            defl.end();
        }
    }

    Deflater boundDefl;

    // testing boundaries
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number < 0.",
        boundDefl.setLevel( -2 ),
        IllegalArgumentException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number > 9.",
        boundDefl.setLevel( 10 ),
        IllegalArgumentException );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testSetStrategy() {

    std::vector<unsigned char> byteArray( 100 );
    for( int ix = 0; ix < 100; ++ix ) {
        byteArray[ix] = (unsigned char)( ix + 1 );
    }

    for( int i = 0; i < 3; i++ ) {

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

        if( i == 0 ) {
            mdefl.setStrategy( Deflater::DEFAULT_STRATEGY );
        } else if( i == 1 ) {
            mdefl.setStrategy( Deflater::HUFFMAN_ONLY );
        } else {
            mdefl.setStrategy( Deflater::FILTERED );
        }

        mdefl.setInput( byteArray );
        while( !mdefl.needsInput() ) {
            mdefl.deflate( outPutBuf );
        }
        mdefl.finish();
        while( !mdefl.finished() ) {
            mdefl.deflate( outPutBuf );
        }

        if( i == 0 ) {
            CPPUNIT_ASSERT_MESSAGE( "getBytesWritten() for the default strategy did not produce data",
                                    0LL != mdefl.getBytesWritten() );
        } else if( i == 1 ) {
            CPPUNIT_ASSERT_MESSAGE( "getBytesWritten() for the Huffman strategy did not produce data",
                                    0LL != mdefl.getBytesWritten() );
        } else {
            CPPUNIT_ASSERT_MESSAGE( "getBytesWritten for the Filtered strategy did not produce data",
                                    0LL != mdefl.getBytesWritten() );
        }
        mdefl.end();
    }

    // Attempting to setStrategy to an invalid value
    Deflater boundDefl;

    // testing boundaries
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting strategy to an invalid value.",
        boundDefl.setStrategy( 424 ),
        IllegalArgumentException );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testConstructor() {

    std::vector<unsigned char> byteArray( 100 );
    for( int ix = 0; ix < 100; ++ix ) {
        byteArray[ix] = (unsigned char)( ix + 1 );
    }

    Deflater defl;
    std::vector<unsigned char> outPutBuf( 500 );
    defl.setInput( byteArray, 0, 100 );
    while( !defl.needsInput() ) {
        defl.deflate( outPutBuf );
    }
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    long long totalOut = defl.getBytesWritten();
    defl.end();

    // creating a Deflater using the DEFAULT_COMPRESSION as the int
    Deflater mdefl( 6 );
    outPutBuf.assign( outPutBuf.size(), 0 );
    mdefl.setInput( byteArray );
    while( !mdefl.needsInput() ) {
        mdefl.deflate( outPutBuf );
    }
    mdefl.finish();
    while( !mdefl.finished() ) {
        mdefl.deflate( outPutBuf );
    }
    CPPUNIT_ASSERT_EQUAL( totalOut, mdefl.getBytesWritten() );
    mdefl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testConstructorIB() {

    static const int ARRAY_SIZE = 15;

    unsigned char byteArray[] = { 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r' };

    Deflater defl;
    std::vector<unsigned char> outPutBuf( 500 );
    defl.setLevel( 2 );
    defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
    while( !defl.needsInput() ) {
        defl.deflate( outPutBuf );
    }
    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }
    long long totalOut = defl.getBytesWritten();
    defl.end();

    {
        outPutBuf.assign( outPutBuf.size(), 0 );
        Deflater defl( 2, false );
        defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
        while( !defl.needsInput() ) {
            defl.deflate( outPutBuf );
        }
        defl.finish();
        while( !defl.finished() ) {
            defl.deflate( outPutBuf );
        }
        CPPUNIT_ASSERT_EQUAL( totalOut, defl.getBytesWritten() );
        defl.end();
    }

    {
        outPutBuf.assign( outPutBuf.size(), 0 );
        Deflater defl( 2, true );
        defl.setInput( byteArray, ARRAY_SIZE, 0, ARRAY_SIZE );
        while( !defl.needsInput() ) {
            defl.deflate( outPutBuf );
        }
        defl.finish();
        while( !defl.finished() ) {
            defl.deflate( outPutBuf );
        }
        CPPUNIT_ASSERT_MESSAGE( "getBytesWritten() should not be equal comparing two Deflaters with different header options.",
                                defl.getBytesWritten() != totalOut );
        defl.end();
    }

    std::vector<unsigned char> outPutInf( 500 );
    Inflater infl( true );
    while( !infl.finished() ) {
        if( infl.needsInput() ) {
            infl.setInput( outPutBuf );
        }
        infl.inflate( outPutInf );
    }
    for( int i = 0; i < ARRAY_SIZE; i++ ) {
        CPPUNIT_ASSERT_EQUAL( byteArray[i], outPutInf[i] );
    }
    CPPUNIT_ASSERT_EQUAL_MESSAGE( "final decompressed data contained more bytes than original - constructorIZ",
                                  0, (int)outPutInf[ARRAY_SIZE] );
    infl.end();

    {
        Inflater infl( false );
        outPutBuf.assign( outPutBuf.size(), 0 );
        int r = 0;
        try {
            while( !infl.finished() ) {
                if( infl.needsInput() ) {
                    infl.setInput( outPutBuf );
                }
                infl.inflate( outPutInf );
            }
        } catch( DataFormatException e ) {
            r = 1;
        }
        CPPUNIT_ASSERT_EQUAL_MESSAGE( "header option did not correspond", 1, (int)r );
    }

    Deflater boundDefl;

    // testing boundaries
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number < 0.",
        boundDefl.setLevel( -2 ),
        IllegalArgumentException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number > 9.",
        boundDefl.setLevel( 10 ),
        IllegalArgumentException );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testConstructorI() {

    std::vector<unsigned char> byteArray( 100 );
    for( int ix = 0; ix < 100; ++ix ) {
        byteArray[ix] = (unsigned char)( ix + 1 );
    }

    std::vector<unsigned char> outPutBuf( 500 );
    Deflater defl( 3 );

    defl.setInput( byteArray );
    while( !defl.needsInput() ) {
        defl.deflate( outPutBuf );
    }

    defl.finish();
    while( !defl.finished() ) {
        defl.deflate( outPutBuf );
    }

    long long totalOut = defl.getBytesWritten();
    defl.end();

    // test to see if the compression ratio is the same as setting the level
    // on a deflater
    outPutBuf.assign( outPutBuf.size(), 0 );
    Deflater defl2;
    defl2.setLevel( 3 );
    defl2.setInput( byteArray );
    while( !defl2.needsInput() ) {
        defl2.deflate( outPutBuf );
    }

    defl2.finish();
    while( !defl2.finished() ) {
        defl2.deflate( outPutBuf );
    }
    CPPUNIT_ASSERT_EQUAL( totalOut, defl2.getBytesWritten() );
    defl2.end();

    Deflater boundDefl;

    // testing boundaries
    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number < 0.",
        boundDefl.setLevel( -2 ),
        IllegalArgumentException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        "IllegalArgumentException not thrown when setting level to a number > 9.",
        boundDefl.setLevel( 10 ),
        IllegalArgumentException );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::helperEndTest( Deflater& defl, const std::string& desc ) {

    // Help tests for test_end() and test_reset().
    unsigned char byteArray[] = { 5, 2, 3, 7, 8 };

    // Methods where we expect IllegalStateException or NullPointerException
    // to be thrown
    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.getBytesWritten() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.getBytesWritten(),
        IllegalStateException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.getAdler() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.getAdler(),
        IllegalStateException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.getBytesRead() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.getBytesRead(),
        IllegalStateException );

    unsigned char dict[] = {'a', 'b', 'c'};
    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.setDictionary() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.setDictionary( dict, 3, 0, 3 ),
        IllegalStateException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.deflate() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.deflate( byteArray, 5, 0, 5 ),
        IllegalStateException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.setInput() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.setInput( byteArray, 5, 0, 5 ),
        IllegalStateException );

    CPPUNIT_ASSERT_THROW_MESSAGE(
        std::string() + "defl.reset() can still be used after " + desc
                      + " is called in test_" + desc,
        defl.reset(),
        IllegalStateException );

    // Methods that should be allowed to be called after end() is called
    defl.needsInput();
    defl.setStrategy( 1 );
    defl.setLevel( 1 );
    defl.end();
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testInitialState() {

    Deflater inf;
    CPPUNIT_ASSERT_EQUAL( false, inf.finished() );
    CPPUNIT_ASSERT_EQUAL( 0LL, inf.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( 0LL, inf.getBytesWritten() );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testGetBytesRead() {

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

    // Encode a String into bytes
    std::string inputString = "blahblahblah??";

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

    def.setInput( (unsigned char*)inputString.c_str(), (int)inputString.size(), 0, (int)inputString.size() );
    def.finish();

    long long compressedDataLength = (long long)def.deflate( output );

    CPPUNIT_ASSERT_EQUAL( 14LL, def.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( compressedDataLength, def.getBytesWritten() );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testGetBytesWritten() {

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

    // Encode a String into bytes
    std::string inputString = "blahblahblah??";

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

    def.setInput( (unsigned char*)inputString.c_str(), (int)inputString.size(), 0, (int)inputString.size() );
    def.finish();

    long long compressedDataLength = (long long)def.deflate( output );

    CPPUNIT_ASSERT_EQUAL( 14LL, def.getBytesRead() );
    CPPUNIT_ASSERT_EQUAL( compressedDataLength, def.getBytesWritten() );
}

////////////////////////////////////////////////////////////////////////////////
void DeflaterTest::testDeflateBeforeSetInput() {

    Deflater deflater;
    deflater.finish();

    std::vector<unsigned char> buffer( 1024 );

    CPPUNIT_ASSERT_EQUAL( 8, (int)deflater.deflate( buffer ) );

    unsigned char expectedBytes[] = { 120, (unsigned char) -100, 3, 0, 0, 0, 0, 1 };

    for( int i = 0; i < 8; i++ ) {
        CPPUNIT_ASSERT_EQUAL( expectedBytes[i], buffer[i] );
    }
}
