    // Copyright (C) 2003  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.

    #include <sstream>
    #include <string>
    #include <ctime>
    #include <cstdlib>

    #include <dlib/compress_stream.h>

    #include "tester.h"

    namespace
    {

        using namespace test;
        using namespace std;
        using namespace dlib;

        logger dlog("test.compress_stream");

        template <
            typename cs
            >
        void compress_stream_kernel_test (
            unsigned long seed
        )
        /*!
            requires
                - cs is an implementation of compress_stream/compress_stream_kernel_abstract.h
                  the alphabet_size for cc is 256
            ensures
                - runs tests on cs for compliance with the specs
        !*/
        {

            srand(seed);

            cs test;

            dlog << LTRACE << 1;

            int count = 0;
            while (count < 2)
            {
                print_spinner();
                istringstream sin;
                ostringstream sout;
                string buffer;
                buffer.reserve(10000);
                // fill sin with a bunch of random data in the range 0 to 63
                for (int i = 0; i < 10000; ++i)
                {
                    char temp = static_cast<char>(::rand()&0x3f);
                    buffer.push_back(temp);
                }

                print_spinner();
                sin.str(buffer);
                string old_buffer = buffer;

                test.compress(sin,sout);
                buffer = sout.str();

                print_spinner();
                // corrput the data in buffer
                buffer[buffer.size()/2]++;

                sin.str(buffer);
                sout.str("");

                bool detected_error = false;
                try {
                    test.decompress(sin,sout);
                } catch ( typename cs::decompression_error e )
                {
                    detected_error = true;
                    ++count;
                }

                DLIB_TEST_MSG(detected_error || sout.str() == old_buffer,(unsigned int)sout.str().size());

            } /**/

            dlog << LTRACE << 2;

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

                print_spinner();
                istringstream sin;
                ostringstream sout;

                string buffer;

                buffer.reserve(10);

                // make sure a single char can be compressed and decompressed
                for (int i = 0; i < 256; ++i)
                {
                    sin.str("");
                    sout.str("");
                    char ch = static_cast<char>(i);
                    buffer = ch;
                    sin.str(buffer);

                    test.compress(sin,sout);

                    sin.str(sout.str());
                    sout.str("");
                    test.decompress(sin,sout);
                    DLIB_TEST(sout.str() == buffer);
                }

                print_spinner();

                // make sure you can compress a single char, then append a new
                // compressed single char.  and make sure you can decode the
                // two streams.  Just to make sure the decoder doesn't leave
                // extra bytes behind or eat more than it should.
                for (int i = 0; i < 500; ++i)
                {
                    sin.str("");
                    sin.clear();
                    sout.str("");
                    sout.clear();
                    char ch = static_cast<char>(::rand()%256);
                    char ch2 = static_cast<char>(::rand()%256);

                    buffer = ch;
                    sin.str(buffer);

                    test.compress(sin,sout);

                    buffer = ch2;
                    sin.str(buffer);
                    test.compress(sin,sout);

                    sin.str(sout.str());

                    sout.str("");
                    test.decompress(sin,sout);
                    buffer = ch;
                    DLIB_TEST(sout.str() == buffer);

                    sout.str("");
                    test.decompress(sin,sout);
                    buffer = ch2;
                    DLIB_TEST(sout.str() == buffer);

                }
                print_spinner();

                // make sure you can compress and decompress the empty string
                sout.str("");
                sin.str("");
                test.compress(sin,sout);
                sin.str(sout.str());
                sout.str("");
                test.decompress(sin,sout);
                DLIB_TEST_MSG(sout.str() == "",sout.str());

                print_spinner();

                sin.str("");
                sout.str("");
                buffer = "";

                buffer.reserve(20000);
                // fill buffer with a bunch of random data in the range 0 to 63
                for (int i = 0; i < 20000; ++i)
                {
                    char temp = static_cast<char>(::rand()&0x3f);
                    buffer.push_back(temp);
                }

                sin.str(buffer);

                print_spinner();
                test.compress(sin,sout);

                sin.str(sout.str());
                sout.str("");

                print_spinner();
                test.decompress(sin,sout);

                DLIB_TEST(sout.str() == buffer);

                print_spinner();
            }

            dlog << LTRACE << 3;

            // this block will try to compress a bunch of 'a' chars
            {

                istringstream sin;
                ostringstream sout;

                string buffer;

                print_spinner();

                sin.str("");
                sout.str("");
                buffer = "";

                buffer.reserve(50000);
                // fill buffer with a bunch of 'a' chars
                for (int i = 0; i < 50000; ++i)
                {
                    char temp = 'a';
                    buffer.push_back(temp);
                }

                sin.str(buffer);

                print_spinner();
                test.compress(sin,sout);

                sin.str(sout.str());
                sout.str("");

                print_spinner();
                test.decompress(sin,sout);

                DLIB_TEST(sout.str() == buffer);

                print_spinner();

            }

            dlog << LTRACE << 4;

        }

        class compress_stream_tester : public tester
        {
        public:
            compress_stream_tester (
            ) :
                tester ("test_compress_stream",
                        "Runs tests on the compress_stream component.")
            {}

            void perform_test (
            )
            {
                const unsigned int seed = static_cast<unsigned int>(time(0));
                dlog << LINFO << "using seed: " << seed;

                dlog << LINFO << "testing kernel_1a";
                compress_stream_kernel_test<compress_stream::kernel_1a>(seed);
                dlog << LINFO << "testing kernel_1b";
                compress_stream_kernel_test<compress_stream::kernel_1b>(seed);
                dlog << LINFO << "testing kernel_1c";
                compress_stream_kernel_test<compress_stream::kernel_1c>(seed);
                dlog << LINFO << "testing kernel_1da";
                compress_stream_kernel_test<compress_stream::kernel_1da>(seed);
                dlog << LINFO << "testing kernel_1db";
                compress_stream_kernel_test<compress_stream::kernel_1db>(seed);
                dlog << LINFO << "testing kernel_1ea";
                compress_stream_kernel_test<compress_stream::kernel_1ea>(seed);
                dlog << LINFO << "testing kernel_1eb";
                compress_stream_kernel_test<compress_stream::kernel_1eb>(seed);
                dlog << LINFO << "testing kernel_1ec";
                compress_stream_kernel_test<compress_stream::kernel_1ec>(seed);
                dlog << LINFO << "testing kernel_2a";
                compress_stream_kernel_test<compress_stream::kernel_2a>(seed);
                dlog << LINFO << "testing kernel_3a";
                compress_stream_kernel_test<compress_stream::kernel_3a>(seed);
                dlog << LINFO << "testing kernel_3b";
                compress_stream_kernel_test<compress_stream::kernel_3b>(seed);
            }
        } a;

    }