    // Copyright (C) 2009  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/type_safe_union.h>

    #include "tester.h"

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

        logger dlog("test.type_safe_union");

        struct can_not_copy: noncopyable {};
        void serialize(const can_not_copy&, std::ostream&) {}
        void deserialize(can_not_copy&, std::istream&) {}

        void swap(can_not_copy&, can_not_copy&) {}

        class test
        {

        private:

            enum kind
            {
                FLOAT, DOUBLE, CHAR, STRING, NONE
            };

            void operator() (float val)
            {
                DLIB_TEST(val == f_val);
                last_kind = FLOAT;
            }

            void operator() (double val)
            {
                DLIB_TEST(val == d_val);
                last_kind = DOUBLE;
            }

            void operator() (char val)
            {
                DLIB_TEST(val == c_val);
                last_kind = CHAR;
            }

            void operator()(std::string& val)
            {
                DLIB_TEST(val == s_val);
                last_kind = STRING;
            }

            void operator()(const std::string& val)
            {
                DLIB_TEST(val == s_val);
                last_kind = STRING;
            }

        // ------------------------------

            friend class type_safe_union<float, double, char, std::string>;
            typedef      type_safe_union<float, double, char, std::string> tsu;
            tsu a, b, c;

            float f_val;
            double d_val;
            char c_val;
            std::string s_val;

            kind last_kind;

        public:
            void test_stuff()
            {
                DLIB_TEST(a.is_empty() == true);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(a.contains<double>() == false);
                DLIB_TEST(a.contains<std::string>() == false);
                DLIB_TEST(a.contains<long>() == false);

                DLIB_TEST(a.get_type_id<int>() == -1);
                DLIB_TEST(a.get_type_id<float>() == 1);
                DLIB_TEST(a.get_type_id<double>() == 2);
                DLIB_TEST(a.get_type_id<char>() == 3);
                DLIB_TEST(a.get_type_id<std::string>() == 4);
                DLIB_TEST(a.get_type_id<tsu>() == -1);

                f_val = 4.345f;
                a.get<float>() = f_val;
                DLIB_TEST(a.cast_to<float>() == f_val);
                DLIB_TEST(const_cast<const tsu&>(a).cast_to<float>() == f_val);
                bool exception_thrown = false;
                try {a.cast_to<char>(); }
                catch (bad_type_safe_union_cast&) { exception_thrown = true;}
                DLIB_TEST(exception_thrown);

                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(a.contains<float>() == true);
                DLIB_TEST(a.contains<double>() == false);
                DLIB_TEST(a.contains<std::string>() == false);
                DLIB_TEST(a.contains<long>() == false);

                last_kind = NONE;
                const_cast<const tsu&>(a).apply_to_contents(*this);
                DLIB_TEST(last_kind == FLOAT);

            // -----------

                d_val = 4.345;
                a.get<double>() = d_val;
                last_kind = NONE;
                a.apply_to_contents(*this);
                DLIB_TEST(last_kind == DOUBLE);

            // -----------

                c_val = 'a';
                a.get<char>() = c_val;
                last_kind = NONE;
                const_cast<const tsu&>(a).apply_to_contents(*this);
                DLIB_TEST(last_kind == CHAR);

            // -----------

                s_val = "test string";
                a.get<std::string>() = s_val;
                last_kind = NONE;
                a.apply_to_contents(*this);
                DLIB_TEST(last_kind == STRING);

                DLIB_TEST(a.cast_to<std::string>() == s_val);
                exception_thrown = false;
                try {a.cast_to<float>(); }
                catch (bad_type_safe_union_cast&) { exception_thrown = true;}
                DLIB_TEST(exception_thrown);

            // -----------
                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(a.contains<double>() == false);
                DLIB_TEST(a.contains<std::string>() == true);
                DLIB_TEST(a.contains<long>() == false);
            // -----------

                a.swap(b);

                DLIB_TEST(a.is_empty() == true);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(a.contains<double>() == false);
                DLIB_TEST(a.contains<std::string>() == false);
                DLIB_TEST(a.contains<long>() == false);

                DLIB_TEST(b.is_empty() == false);
                DLIB_TEST(b.contains<char>() == false);
                DLIB_TEST(b.contains<float>() == false);
                DLIB_TEST(b.contains<double>() == false);
                DLIB_TEST(b.contains<std::string>() == true);
                DLIB_TEST(b.contains<long>() == false);

                last_kind = NONE;
                b.apply_to_contents(*this);
                DLIB_TEST(last_kind == STRING);

            // -----------

                b.swap(a);

                DLIB_TEST(b.is_empty() == true);
                DLIB_TEST(b.contains<char>() == false);
                DLIB_TEST(b.contains<float>() == false);
                DLIB_TEST(b.contains<double>() == false);
                DLIB_TEST(b.contains<std::string>() == false);
                DLIB_TEST(b.contains<long>() == false);

                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(a.contains<double>() == false);
                DLIB_TEST(a.contains<std::string>() == true);
                DLIB_TEST(a.contains<long>() == false);

                last_kind = NONE;
                a.apply_to_contents(*this);
                DLIB_TEST(last_kind == STRING);
                last_kind = NONE;
                b.apply_to_contents(*this);
                DLIB_TEST(last_kind == NONE);

                a.get<char>() = 'a';
                b.get<char>() = 'b';

                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == true);
                DLIB_TEST(b.is_empty() == false);
                DLIB_TEST(b.contains<char>() == true);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(b.contains<float>() == false);

                DLIB_TEST(a.get<char>() == 'a');
                DLIB_TEST(b.get<char>() == 'b');

                swap(a,b);

                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == true);
                DLIB_TEST(b.is_empty() == false);
                DLIB_TEST(b.contains<char>() == true);
                DLIB_TEST(a.contains<float>() == false);
                DLIB_TEST(b.contains<float>() == false);

                DLIB_TEST(a.get<char>() == 'b');
                DLIB_TEST(b.get<char>() == 'a');

            // -----------

                a.get<char>() = 'a';
                b.get<std::string>() = "a string";

                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == true);
                DLIB_TEST(b.is_empty() == false);
                DLIB_TEST(b.contains<char>() == false);
                DLIB_TEST(a.contains<std::string>() == false);
                DLIB_TEST(b.contains<std::string>() == true);

                DLIB_TEST(a.get<char>() == 'a');
                DLIB_TEST(b.get<std::string>() == "a string");

                swap(a,b);

                DLIB_TEST(b.is_empty() == false);
                DLIB_TEST(b.contains<char>() == true);
                DLIB_TEST(a.is_empty() == false);
                DLIB_TEST(a.contains<char>() == false);
                DLIB_TEST(b.contains<std::string>() == false);
                DLIB_TEST(a.contains<std::string>() == true);

                DLIB_TEST(b.get<char>() == 'a');
                DLIB_TEST(a.get<std::string>() == "a string");

                {
                    type_safe_union<char, float, std::string> a, b, empty_union;

                    ostringstream sout;
                    istringstream sin;

                    a.get<char>() = 'd';

                    serialize(a, sout);

                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<char>() == true);
                    DLIB_TEST(b.get<char>() == 'd');

                    DLIB_TEST(a.contains<int>() == false);
                    DLIB_TEST(a.contains<float>() == false);
                    DLIB_TEST(a.contains<char>() == true);
                    DLIB_TEST(a.get<char>() == 'd');

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

                    a.get<std::string>() = "davis";

                    serialize(a, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<std::string>() == true);
                    DLIB_TEST(b.get<std::string>() == "davis");

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

                    serialize(empty_union, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.is_empty() == true);

                }

                {
                    type_safe_union<char, float, std::string> a, b, empty_union;

                    ostringstream sout;
                    istringstream sin;

                    a = 'd';

                    serialize(a, sout);

                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<char>() == true);
                    DLIB_TEST(b.get<char>() == 'd');

                    DLIB_TEST(a.contains<int>() == false);
                    DLIB_TEST(a.contains<float>() == false);
                    DLIB_TEST(a.contains<char>() == true);
                    DLIB_TEST(a.get<char>() == 'd');

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

                    a = std::string("davis");

                    serialize(a, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<std::string>() == true);
                    DLIB_TEST(b.get<std::string>() == "davis");

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

                    serialize(empty_union, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.is_empty() == true);

                }

                {
                    typedef type_safe_union<char, float, std::string, can_not_copy> tsu_type;
                    tsu_type a('d'), aa(std::string("davis")), b, empty_union;

                    ostringstream sout;
                    istringstream sin;

                    serialize(a, sout);

                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<char>() == true);
                    DLIB_TEST(b.get<char>() == 'd');

                    DLIB_TEST(a.contains<int>() == false);
                    DLIB_TEST(a.contains<float>() == false);
                    DLIB_TEST(a.contains<char>() == true);
                    DLIB_TEST(a.get<char>() == 'd');

                    DLIB_TEST(aa.contains<int>() == false);
                    DLIB_TEST(aa.contains<float>() == false);
                    DLIB_TEST(aa.contains<char>() == false);
                    DLIB_TEST(aa.contains<std::string>() == true);

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

                    serialize(aa, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.contains<int>() == false);
                    DLIB_TEST(b.contains<float>() == false);
                    DLIB_TEST(b.contains<std::string>() == true);
                    DLIB_TEST(b.get<std::string>() == "davis");

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

                    serialize(empty_union, sout);
                    sin.str(sout.str());
                    deserialize(b, sin);

                    DLIB_TEST(b.is_empty() == true);

                    a.get<can_not_copy>();
                    DLIB_TEST(a.contains<can_not_copy>() == true);

                }
            }

        };

        class type_safe_union_tester : public tester
        {
        public:
            type_safe_union_tester (
            ) :
                tester ("test_type_safe_union",
                        "Runs tests on the type_safe_union object")
            {}

            void perform_test (
            )
            {
                for (int i = 0; i < 10; ++i)
                {
                    test a;
                    a.test_stuff();
                }
            }
        } a;

    }