    // Copyright (C) 2005  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/hash_table.h>
    #include <dlib/binary_search_tree.h>

    #include <dlib/static_map.h>
    #include "tester.h"

    namespace
    {

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

        logger dlog("test.static_map");

        template <
            typename map
            >
        void static_map_kernel_test (
        )
        /*!
            requires
                - map is an implementation of static_map/static_map_kernel_abstract.h and
                  is instantiated to map int to int
            ensures
                - runs tests on map for compliance with the specs
        !*/
        {

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

            typedef binary_search_tree<int,int>::kernel_2a_c bst;
            typedef hash_table<int,int>::kernel_1a_c ht;

            const unsigned long table_4_max_size = 100;
            const unsigned long tree_max_size = 50000;
            ht table_4(4);
            ht table_8(8);
            bst tree;

            ht table_4b(4);
            ht table_8b(8);
            bst treeb;

            // just do the following to make sure operator[] doesn't hang
            // under some instances
            {
                int g = 1, h = 1;
                treeb.add(g,h);
                map test;
                map test2;

                DLIB_TEST(test.size() == 0);
                DLIB_TEST(test.at_start());
                DLIB_TEST(test.current_element_valid() == false);
                DLIB_TEST(test.move_next() == false);
                DLIB_TEST(test.current_element_valid() == false);
                DLIB_TEST(test.at_start() == false);

                swap(test,test2);
                DLIB_TEST(test2.at_start() == false);
                DLIB_TEST(test.at_start() == true);

                swap(test,test2);
                DLIB_TEST(test.at_start() == false);

                DLIB_TEST(test.size() == 0);
                DLIB_TEST(test[1] == 0);
                DLIB_TEST(test[2] == 0);
                DLIB_TEST(test[3] == 0);
                DLIB_TEST(test[0] == 0);

                test.load(treeb);
                DLIB_TEST(test.at_start());
                DLIB_TEST(test[1] != 0);
                DLIB_TEST(test[2] == 0);
                DLIB_TEST(test[3] == 0);
                DLIB_TEST(test[0] == 0);

                test2.clear();
                swap(test2,test);
                DLIB_TEST(test2[1] != 0);
                DLIB_TEST(test2[2] == 0);
                DLIB_TEST(test2[3] == 0);
                DLIB_TEST(test2[0] == 0);
                DLIB_TEST(test[1] == 0);
                DLIB_TEST(test[2] == 0);
                DLIB_TEST(test[3] == 0);
                DLIB_TEST(test[0] == 0);

                DLIB_TEST(treeb.size() == 0);
                treeb.clear();
            }

            for (unsigned long i = 0; i < table_4_max_size; ++i)
            {
                int a = ::rand()&0xFF;
                int b = a + 1;
                int ab = a;
                int bb = b;
                table_4.add(a,b);
                table_4b.add(ab,bb);
            }

            for (unsigned long i = 0; i < table_4_max_size; ++i)
            {
                int a = ::rand()&0xF;
                int b = a + 1;
                int ab = a;
                int bb = b;
                table_8.add(a,b);
                table_8b.add(ab,bb);
            }

            for (unsigned long i = 0; i < tree_max_size; ++i)
            {
                int a = ::rand()&0xFFF;
                int b = a + 1;
                int ab = a;
                int bb = b;
                tree.add(a,b);
                treeb.add(ab,bb);
            }

            map m_4;
            m_4.load(table_4);
            map m_8;
            m_8.load(table_8);
            map m_t;
            m_t.load(tree);
            map e;
            e.load(table_4);

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

            DLIB_TEST(m_4.size() == table_4b.size());
            DLIB_TEST(m_8.size() == table_8b.size());
            DLIB_TEST(m_t.size() == treeb.size());

            DLIB_TEST(m_4.at_start() == true);
            DLIB_TEST(m_8.at_start() == true);
            DLIB_TEST(m_t.at_start() == true);
            DLIB_TEST(m_4.current_element_valid() == false);
            DLIB_TEST(m_8.current_element_valid() == false);
            DLIB_TEST(m_t.current_element_valid() == false);

            DLIB_TEST(m_4.move_next() == true);
            DLIB_TEST(m_4.at_start() == false);
            DLIB_TEST(m_4.current_element_valid() == true);
            DLIB_TEST(m_8.move_next() == true);
            DLIB_TEST(m_8.at_start() == false);
            DLIB_TEST(m_8.current_element_valid() == true);
            DLIB_TEST(m_t.move_next() == true);
            DLIB_TEST(m_t.at_start() == false);
            DLIB_TEST(m_t.current_element_valid() == true);

            m_4.reset();
            m_8.reset();
            m_t.reset();

            while (m_4.move_next())
            {
                DLIB_TEST( table_4b[m_4.element().key()] != 0);
                DLIB_TEST( *table_4b[m_4.element().key()] == m_4.element().value());
            }

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

            while (table_4b.move_next())
            {
                DLIB_TEST( m_4[table_4b.element().key()] != 0);
                DLIB_TEST( *m_4[table_4b.element().key()] == table_4b.element().value());
            }

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

            while (m_8.move_next())
            {
                DLIB_TEST( table_8b[m_8.element().key()] != 0);
                DLIB_TEST( *table_8b[m_8.element().key()] == m_8.element().value());
            }

            while (table_8b.move_next())
            {
                DLIB_TEST( m_8[table_8b.element().key()] != 0);
                DLIB_TEST( *m_8[table_8b.element().key()] == table_8b.element().value());
            }

            while (m_t.move_next())
            {
                DLIB_TEST( treeb[m_t.element().key()] != 0);
                DLIB_TEST( *treeb[m_t.element().key()] == m_t.element().value());
            }

            // make sure operator[] doesn't hang
            for (int l = 1; l < 10000; ++l)
            {
                DLIB_TEST(m_t[l+0xFFF] == 0);
            }

            while (treeb.move_next())
            {
                DLIB_TEST( m_t[treeb.element().key()] != 0);
                DLIB_TEST( *m_t[treeb.element().key()] == treeb.element().value());
            }

            m_4.reset();
            m_8.reset();
            m_t.reset();

            int last = 0;
            while (m_4.move_next())
            {
                DLIB_TEST(last <= m_4.element().key());
                DLIB_TEST(m_4.element().key() + 1 == m_4.element().value());
                last = m_4.element().key();
            }

            last = 0;
            while (m_8.move_next())
            {
                DLIB_TEST(last <= m_8.element().key());
                DLIB_TEST(m_8.element().key() + 1 == m_8.element().value());
                last = m_8.element().key();
            }

            last = 0;
            while (m_t.move_next())
            {
                DLIB_TEST(last <= m_t.element().key());
                DLIB_TEST(m_t.element().key() + 1 == m_t.element().value());
                last = m_t.element().key();
            }

            // this is just to test swap
            m_4.swap(m_8);
            m_4.reset();
            table_4b.reset();
            while (m_8.move_next())
            {
                DLIB_TEST( table_4b[m_8.element().key()] != 0);
                DLIB_TEST( *table_4b[m_8.element().key()] == m_8.element().value());
            }

            while (table_4b.move_next())
            {
                DLIB_TEST( m_8[table_4b.element().key()] != 0);
                DLIB_TEST( *m_8[table_4b.element().key()] == table_4b.element().value());
            }

        }

        class static_map_tester : public tester
        {
        public:
            static_map_tester (
            ) :
                tester ("test_static_map",
                        "Runs tests on the static_map component.")
            {}

            void perform_test (
            )
            {
                dlog << LINFO << "testing kernel_1a";
                static_map_kernel_test<static_map<int,int>::kernel_1a>  ();
                dlog << LINFO << "testing kernel_1a_c";
                static_map_kernel_test<static_map<int,int>::kernel_1a_c>();
            }
        } a;

    }