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

#include <algorithm>
#include <memory>

#include "tester.h"
#include <dlib/sockets.h>
#include <dlib/threads.h>
#include <dlib/array.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 logger 
    // should start with "test."
    dlib::logger dlog("test.sockets2");


    class sockets2_tester : public tester, private multithreaded_object 
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object represents a unit test.  When it is constructed
                it adds itself into the testing framework.
        !*/

        short port_num;
        string data_to_send;

        bool test_failed;

        void write_thread (
        )
        {
            try
            {
                std::unique_ptr<connection> con(connect("127.0.0.1", port_num));

                // Send a copy of the data down the connection so we can test our the read() function
                // that uses timeouts in the main thread.
                if (con->write(data_to_send.data(), data_to_send.size()) != (int)data_to_send.size())
                {
                    test_failed = true;
                    dlog << LERROR << "failed to send all the data down the connection";
                }

                close_gracefully(con,300000);
            }
            catch (exception& e)
            {
                test_failed = true;
                dlog << LERROR << e.what();
            }
        }

        void no_write_thread (
        )
        {
            try
            {
                std::unique_ptr<connection> con(connect("127.0.0.1", port_num));

                // just do nothing until the connection closes
                char ch;
                con->read(&ch, 1);
                dlog << LDEBUG << "silent connection finally closing";
            }
            catch (exception& e)
            {
                test_failed = true;
                dlog << LERROR << e.what();
            }
        }

    public:
        sockets2_tester (
        ) :
            tester (
                "test_sockets2",       // the command line argument name for this test
                "Run sockets2 tests.", // the command line argument description
                0                     // the number of command line arguments for this test
            )
        {
            register_thread(*this, &sockets2_tester::write_thread);
            register_thread(*this, &sockets2_tester::write_thread);
            register_thread(*this, &sockets2_tester::write_thread);
            register_thread(*this, &sockets2_tester::write_thread);
            register_thread(*this, &sockets2_tester::write_thread);
            register_thread(*this, &sockets2_tester::no_write_thread);
        }

        void perform_test (
        )
        {
            run_tests(0);
            run_tests(40);
        }

        void run_tests (
            unsigned long timeout_to_use
        )
        {
            // make sure there aren't any threads running
            wait();

            port_num = 5000;
            test_failed = false;

            print_spinner();
            data_to_send = "oi 2m3ormao2m fo2im3fo23mi o2mi3 foa2m3fao23ifm2o3fmia23oima23iom3giugbiua";
            // make the block of data much larger
            for (int i = 0; i < 11; ++i)
                data_to_send = data_to_send + data_to_send;

            dlog << LINFO << "data block size: " << data_to_send.size();


            std::unique_ptr<listener> list;
            DLIB_TEST(create_listener(list, port_num, "127.0.0.1") == 0);
            DLIB_TEST(bool(list));

            // kick off the sending threads
            start();


            dlib::array<std::unique_ptr<connection> > cons;
            std::vector<long> bytes_received(6,0);
            std::unique_ptr<connection> con_temp;
            
            // accept the 6 connections we should get
            for (int i = 0; i < 6; ++i)
            {
                DLIB_TEST(list->accept(con_temp) == 0);
                cons.push_back(con_temp);
                print_spinner();
            }

            int finished_cons = 0;

            // now receive all the bytes from the sending threads
            while (finished_cons < 5)
            {
                for (unsigned long i = 0; i < cons.size(); ++i)
                {
                    if (cons[i])
                    {
                        const int buf_size = 3000;
                        char buf[buf_size];

                        int status = cons[i]->read(buf, buf_size, timeout_to_use);

                        if (status > 0)
                        {
                            DLIB_TEST(equal(buf, buf+status, data_to_send.begin()+bytes_received[i]));
                            bytes_received[i] += status;
                        }
                        else if (status == 0)
                        {
                            // the connection is closed to kill it
                            cons[i].reset();
                            ++finished_cons;
                        }
                    }
                }
                print_spinner();
            }

            for (unsigned long i = 0; i < bytes_received.size(); ++i)
            {
                DLIB_TEST(bytes_received[i] == (long)data_to_send.size() || cons[i]);
            }


            dlog << LINFO << "All data received correctly";

            cons.clear();


            print_spinner();

            DLIB_TEST(test_failed == false);


            // wait for all the sending threads to terminate
            wait();
        }
    };

    // Create an instance of this object.  Doing this causes this test
    // to be automatically inserted into the testing framework whenever this cpp file
    // is linked into the project.  Note that since we are inside an unnamed-namespace 
    // we won't get any linker errors about the symbol a being defined multiple times. 
    sockets2_tester a;

}