    // 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 <cstdlib>
    #include <ctime>
    #include <dlib/queue.h>
    #include <dlib/memory_manager_global.h>

    #include "tester.h"

    // This is called an unnamed-namespace and it has the effect of making everything inside this file "private"
    // so that everything you declare will have static linkage.  Thus we won't have any multiply
    // defined symbol errors coming out of the linker when we try to compile the test suite.
    namespace
    {

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

        // Declare the logger we will use in this test.  The name of the tester
        // should start with "test."
        logger dlog("test.queue");

        template <
            typename queue
            >
        void queue_sort_test (
        )
        /*!
            requires
                - queue is an implementation of queue/queue_sort_abstract.h
                  is instantiated with int
            ensures
                - runs tests on queue for compliance with the specs
        !*/
        {

            print_spinner();
            srand(static_cast<unsigned int>(time(0)));

            queue q,q2;

            enumerable<int>& e = q;

            // I will use these DLIB_TEST_MSG macros to assert that conditions are true.  If they are
            // false then it means we have detected an error in the queue object.  CASSERT
            // will then throw an exception which we will catch at the end of this function and
            // report as an error/failed test.
            DLIB_TEST(e.at_start() == true);

            int a = 0;

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q.at_start() == true);
            DLIB_TEST(q.current_element_valid() == false);

            q.sort();

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q.at_start() == true);
            DLIB_TEST(q.current_element_valid() == false);

            DLIB_TEST (q.move_next() == false);
            DLIB_TEST (q.move_next() == false);
            DLIB_TEST (q.move_next() == false);
            DLIB_TEST (q.move_next() == false);
            DLIB_TEST (q.move_next() == false);
            DLIB_TEST (q.move_next() == false);

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q.at_start() == false);
            DLIB_TEST(q.current_element_valid() == false);

            q.reset();

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q.at_start() == true);
            DLIB_TEST(q.current_element_valid() == false);

            q.clear();
            q2.clear();
            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 0);

            for (int i = 0; i < 10000; ++i)
            {
                int a = i;
                q.enqueue(a);
            }

            q2.cat(q);

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 10000);

            int g = 0;
            while (q2.move_next())
            {
                DLIB_TEST_MSG(q2.element() == g,g);
                ++g;
            }

            for (int i = 0;i < 10000; ++i)
            {
                int a = 0;
                q2.dequeue(a);
                DLIB_TEST(a == i);
            }

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 0);
            q.clear();
            q2.clear();

            print_spinner();

            dlog << LTRACE << "creating big pre-sorted queue";
            q.clear();
            DLIB_TEST(q.size() == 0);

            for (int i = 0; i < 10000; ++i)
            {
                int a = i;
                q.enqueue(a);
            }

            dlog << LTRACE << "sorting already sorted queue";
            q.sort();

            dlog << LTRACE << "done sorting, checking the results";
            for (int i = 0; i < 10000; ++i)
            {
                q.dequeue(a);
                DLIB_TEST(a == i);
            }

            q.clear();
            dlog << LTRACE << "done with the big pre-sorted queue test";

            q.clear();
            q2.clear();
            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 0);

            for (int i = 0; i < 1; ++i)
            {
                int a = i;
                q.enqueue(a);
            }

            q2.cat(q);

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 1);

            g = 0;
            while (q2.move_next())
            {
                DLIB_TEST_MSG(q2.element() == g,g);
                ++g;
            }

            for (int i = 0;i < 1; ++i)
            {
                int a = 0;
                q2.dequeue(a);
                DLIB_TEST(a == i);
            }

            DLIB_TEST(q.size() == 0);
            DLIB_TEST(q2.size() == 0);
            q.clear();
            q2.clear();

            print_spinner();

            for (int j = 0; j < 3; ++j)
            {
                for (int i = 0; i < 10000; ++i)
                {
                    a = ::rand();
                    q.enqueue(a);
                }

                while (q.move_next()) ;

                DLIB_TEST(q.at_start() == false);

                q.sort();

                DLIB_TEST(q.at_start() == true);

                // serialize the state of q, then clear q, then
                // load the state back into q.
                ostringstream sout;
                serialize(q,sout);
                DLIB_TEST(q.at_start() == true);
                istringstream sin(sout.str());
                q.clear();
                deserialize(q,sin);

                DLIB_TEST(q.at_start() == true);

                a = 0;
                int last = 0;
                while (q.move_next())
                {
                    ++a;
                    DLIB_TEST_MSG(last <= q.element(),"items weren't actually sorted");
                    last = q.element();
                    DLIB_TEST(q.current_element_valid() == true);
                    DLIB_TEST(q.at_start() == false);
                    DLIB_TEST(q.current_element_valid() == true);

                }
                DLIB_TEST_MSG(a == 10000,"some items were lost between the sorting and iterating");

                DLIB_TEST(q.size() == 10000);
                swap(q,q2);
                DLIB_TEST(q2.at_start() == false);
                DLIB_TEST(q2.current_element_valid() == false);

                DLIB_TEST (q2.move_next() == false);
                DLIB_TEST (q2.move_next() == false);
                DLIB_TEST (q2.move_next() == false);
                DLIB_TEST (q2.move_next() == false);
                DLIB_TEST (q2.move_next() == false);
                DLIB_TEST (q2.move_next() == false);

                DLIB_TEST(q2.size() == 10000);
                DLIB_TEST(q2.at_start() == false);
                DLIB_TEST(q2.current_element_valid() == false);

                q2.clear();

                q.swap(q2);

                DLIB_TEST(q.size() == 0);
                DLIB_TEST(q.at_start() == true);
                DLIB_TEST(q.current_element_valid() == false);
            }

            print_spinner();

            // try the above code but this time with just one element
            // in the queue
            for (int j = 0; j < 3; ++j)
            {
                for (int i = 0; i < 1; ++i)
                {
                    a = ::rand();
                    q.enqueue(a);
                }

                q.sort();

                a = 0;
                int last = 0;
                while (q.move_next())
                {
                    ++a;
                    DLIB_TEST_MSG(last <= q.element(),"items weren't actually sorted");
                    DLIB_TEST(q.current_element_valid() == true);

                }
                DLIB_TEST_MSG(a == 1,"some items were lost between the sorting and iterating");

                DLIB_TEST(q.size() == 1);
                DLIB_TEST(q.at_start() == false);
                DLIB_TEST(q.current_element_valid() == false);

                q.clear();

                DLIB_TEST(q.size() == 0);
                DLIB_TEST(q.at_start() == true);
                DLIB_TEST(q.current_element_valid() == false);
            }

            print_spinner();

            {
                q.clear();
                remover<int>& go = q;
                for (int i = 0; i < 100; ++i)
                {
                    int a = 3;
                    q.enqueue(a);
                }
                DLIB_TEST(go.size() == 100);
                for (int i = 0; i < 100; ++i)
                {
                    int a = 9;
                    q.remove_any(a);
                    DLIB_TEST(a == 3);
                }
                DLIB_TEST(go.size() == 0);
            }

        }

        struct factory
        {
            template <typename U>
            struct return_type {
                typedef typename memory_manager<U>::kernel_3c type;
            };

            template <typename U>
            static typename return_type<U>::type* get_instance (
            )
            {
                static typename return_type<U>::type a;
                return &a;
            }
        };

        class queue_tester : public tester
        {
            /*!
                WHAT THIS OBJECT REPRESENTS
                    This object represents a test for the queue object.  When it is constructed
                    it adds itself into the testing framework.  The command line switch is
                    specified as test_queue by passing that string to the tester constructor.
            !*/
        public:
            queue_tester (
            ) :
                tester ("test_queue",
                        "Runs tests on the queue component.")
            {}

            void perform_test (
            )
            {
                // There are multiple implementations of the queue object so use
                // the templated function defined above to test them all and report
                // a failed test if any of them don't pass.

                typedef dlib::memory_manager_global<char,factory>::kernel_1a mm;

                dlog << LINFO << "testing sort_1a_c";
                queue_sort_test<queue<int, mm>::sort_1a_c>  ();
                dlog << LINFO << "testing sort_1a";
                queue_sort_test<queue<int, mm>::sort_1a>();
                dlog << LINFO << "testing sort_1b";
                queue_sort_test<queue<int, mm>::sort_1b>  ();
                dlog << LINFO << "testing sort_1b_c";
                queue_sort_test<queue<int, mm>::sort_1b_c>();
                dlog << LINFO << "testing sort_1c";
                queue_sort_test<queue<int, mm>::sort_1c>  ();
                dlog << LINFO << "testing sort_1c_c";
                queue_sort_test<queue<int, mm>::sort_1c_c>();
            }
        } a;

    }