// Copyright (C) 2011  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/bridge.h>
#include <dlib/type_safe_union.h>

#include "tester.h"

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

    logger dlog("test.bridge");

    const unsigned short testing_port = 41238;

    void do_test1()
    {
        dlib::pipe<int> in(0), out(0);

        bridge b1(connect_to_ip_and_port("127.0.0.1",testing_port), receive(in));
        bridge b2(listen_on_port(testing_port), transmit(out));

        for (int i = 0; i < 100; ++i)
        {
            int val = i;
            out.enqueue(val);
            val = 0;
            in.dequeue(val);
            DLIB_TEST(val == i);
        }
    }

    void do_test2()
    {
        dlib::pipe<int> in(0), out(0), echo_pipe(0);

        bridge b2(listen_on_port(testing_port), transmit(out), receive(in));
        bridge echo(connect_to_ip_and_port("127.0.0.1",testing_port), receive(echo_pipe), transmit(echo_pipe));

        for (int i = 0; i < 100; ++i)
        {
            int val = i;
            out.enqueue(val);
            val = 0;
            in.dequeue(val);
            DLIB_TEST(val == i);
        }
    }

    void do_test3()
    {
        dlib::pipe<int> in(10), out(10), echo_pipe(10);

        bridge b2(listen_on_port(testing_port), transmit(out), receive(in));
        bridge echo(connect_to_ip_and_port("127.0.0.1",testing_port), receive(echo_pipe), transmit(echo_pipe));

        b2.reconfigure(listen_on_port(testing_port), transmit(out), receive(in));

        for (int i = 0; i < 100; ++i)
        {
            int val = i;
            out.enqueue(val);
            val = 0;
            in.dequeue(val);
            DLIB_TEST(val == i);
        }
    }

    void do_test4()
    {
        dlib::pipe<int> in(0), out(0), echo_pipe(0);

        bridge b2, echo;
        b2.reconfigure(listen_on_port(testing_port), receive(in), transmit(out));
        echo.reconfigure(connect_to_ip_and_port("127.0.0.1",testing_port), transmit(echo_pipe), receive(echo_pipe));

        for (int i = 0; i < 100; ++i)
        {
            int val = i;
            out.enqueue(val);
            val = 0;
            in.dequeue(val);
            DLIB_TEST(val == i);
        }
    }

    void do_test5(int pipe_size)
    {
        typedef type_safe_union<int, bridge_status> tsu_type;

        dlib::pipe<tsu_type> out(pipe_size);
        dlib::pipe<tsu_type> in(pipe_size);
        dlib::pipe<bridge_status> out_status(pipe_size);

        bridge b1(connect_to_ip_and_port("127.0.0.1",testing_port), receive(in));
        tsu_type msg;

        msg = b1.get_bridge_status();
        DLIB_TEST(msg.contains<bridge_status>() == true);
        DLIB_TEST(msg.get<bridge_status>().is_connected == false);
        DLIB_TEST(msg.get<bridge_status>().foreign_ip == "");
        DLIB_TEST(msg.get<bridge_status>().foreign_port == 0);

        {
            bridge b2(listen_on_port(testing_port), transmit(out), receive(out_status));

            in.dequeue(msg);
            DLIB_TEST(msg.contains<bridge_status>() == true);
            DLIB_TEST(msg.get<bridge_status>().is_connected == true);
            DLIB_TEST(msg.get<bridge_status>().foreign_ip == "127.0.0.1");
            DLIB_TEST(msg.get<bridge_status>().foreign_port == testing_port);
            msg = b1.get_bridge_status();
            DLIB_TEST(msg.contains<bridge_status>() == true);
            DLIB_TEST(msg.get<bridge_status>().is_connected == true);
            DLIB_TEST(msg.get<bridge_status>().foreign_ip == "127.0.0.1");
            DLIB_TEST(msg.get<bridge_status>().foreign_port == testing_port);

            bridge_status temp;
            out_status.dequeue(temp);
            DLIB_TEST(temp.is_connected == true);
            DLIB_TEST(temp.foreign_ip == "127.0.0.1");

            for (int i = 0; i < 100; ++i)
            {
                msg = i;
                out.enqueue(msg);

                msg.get<int>() = 0;

                in.dequeue(msg);
                DLIB_TEST(msg.contains<int>() == true);
                DLIB_TEST(msg.get<int>() == i);
            }

        }

        in.dequeue(msg);
        DLIB_TEST(msg.contains<bridge_status>() == true);
        DLIB_TEST(msg.get<bridge_status>().is_connected == false);
        DLIB_TEST(msg.get<bridge_status>().foreign_ip == "127.0.0.1");
        DLIB_TEST(msg.get<bridge_status>().foreign_port == testing_port);
    }

    void do_test5_5(int pipe_size)
    {
        typedef type_safe_union<int, bridge_status> tsu_type;

        dlib::pipe<tsu_type> out(pipe_size);
        dlib::pipe<tsu_type> in(pipe_size);
        dlib::pipe<bridge_status> out_status(pipe_size);

        bridge b1(connect_to_ip_and_port("127.0.0.1",testing_port), receive(in));
        tsu_type msg;

        bridge b2(listen_on_port(testing_port), transmit(out), receive(out_status));

        in.dequeue(msg);
        DLIB_TEST(msg.contains<bridge_status>() == true);
        DLIB_TEST(msg.get<bridge_status>().is_connected == true);
        DLIB_TEST(msg.get<bridge_status>().foreign_ip == "127.0.0.1");
        DLIB_TEST(msg.get<bridge_status>().foreign_port == testing_port);

        bridge_status temp;
        out_status.dequeue(temp);
        DLIB_TEST(temp.is_connected == true);
        DLIB_TEST(temp.foreign_ip == "127.0.0.1");

        for (int i = 0; i < 100; ++i)
        {
            msg = i;
            out.enqueue(msg);

            msg.get<int>() = 0;

            in.dequeue(msg);
            DLIB_TEST(msg.contains<int>() == true);
            DLIB_TEST(msg.get<int>() == i);
        }

        b2.clear();
        msg = b2.get_bridge_status();
        DLIB_TEST(msg.contains<bridge_status>() == true);
        DLIB_TEST(msg.get<bridge_status>().is_connected == false);
        DLIB_TEST(msg.get<bridge_status>().foreign_ip == "");
        DLIB_TEST(msg.get<bridge_status>().foreign_port == 0);

        in.dequeue(msg);
        DLIB_TEST(msg.contains<bridge_status>() == true);
        DLIB_TEST(msg.get<bridge_status>().is_connected == false);
        DLIB_TEST(msg.get<bridge_status>().foreign_ip == "127.0.0.1");
        DLIB_TEST(msg.get<bridge_status>().foreign_port == testing_port);
    }

    void do_test6()
    {
        dlib::pipe<int> in(0), out(300);

        bridge b1(connect_to_ip_and_port("127.0.0.1",testing_port), receive(in));
        bridge b2(listen_on_port(testing_port), transmit(out));

        for (int i = 0; i < 100; ++i)
        {
            int val = i;
            out.enqueue(val);
        }

        int val = 10;
        in.dequeue(val);
        DLIB_TEST(val == 0);
        dlib::sleep(100);
        in.dequeue(val);
        DLIB_TEST(val == 1);
        dlib::sleep(100);
    }

    class test_bridge : public tester
    {
    public:
        test_bridge (
        ) :
            tester ("test_bridge",
                    "Runs tests on the bridge component.")
        {}

        void perform_test (
        )
        {
            dlog << LINFO << "testing bridge, using local port number of " << testing_port; 

            print_spinner();
            do_test1();
            print_spinner();
            do_test2();
            print_spinner();
            do_test3();
            print_spinner();
            do_test4();
            print_spinner();
            for (int i = 0; i < 5; ++i)
                do_test5(i);
            do_test5_5(1);
            print_spinner();
            do_test6();
        }
    } a;



}