// Copyright (C) 2010  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/misc_api.h>
#include <dlib/threads.h>

#include "tester.h"

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

    logger dlog("test.read_write_mutex");

    class read_write_mutex_tester : public tester, multithreaded_object
    {
    public:
        read_write_mutex_tester (
        ) :
            tester ("test_read_write_mutex",
                    "Runs tests on the read_write_mutex component.")
        {
            register_thread(*this, &read_write_mutex_tester::thread_write);
            register_thread(*this, &read_write_mutex_tester::thread_write);
            register_thread(*this, &read_write_mutex_tester::thread_write);

            register_thread(*this, &read_write_mutex_tester::thread_readonly);
            register_thread(*this, &read_write_mutex_tester::thread_readonly);
            register_thread(*this, &read_write_mutex_tester::thread_readonly);
            register_thread(*this, &read_write_mutex_tester::thread_readonly2);
            register_thread(*this, &read_write_mutex_tester::thread_readonly2);
            register_thread(*this, &read_write_mutex_tester::thread_readonly2);

        }

        read_write_mutex m;

        dlib::mutex mut;
        int num_write;
        int num_read;
        int max_read;

        bool failure;

        void thread_write ()
        {
            // do this so that the readonly threads can get into their loops first.  This way
            // we can see if the mutex lets many readers into their area
            dlib::sleep(250);
            for (int i = 0; i < 6; ++i)
            {
                auto_mutex lock(m);

                mut.lock();
                ++num_write;
                mut.unlock();

                // only one write thread should ever be active at once
                if (num_write != 1)
                {
                    failure = true;
                    dlog << LERROR << "1";
                }

                dlib::sleep(300);

                // only one write thread should ever be active at once
                if (num_write != 1)
                {
                    failure = true;
                    dlog << LERROR << "2";
                }

                mut.lock();
                --num_write;
                mut.unlock();

                print_spinner();
            }
            dlog << LINFO << "exit thread_write()";
        }

        void do_readonly_stuff()
        {
            mut.lock();
            ++num_read;
            max_read = max(num_read, max_read);
            mut.unlock();

            if (num_write != 0)
            {
                failure = true;
                dlog << LERROR << "3";
            }

            dlib::sleep(300);

            if (num_write != 0)
            {
                failure = true;
                dlog << LERROR << "4";
            }

            mut.lock();
            max_read = max(num_read, max_read);
            --num_read;
            mut.unlock();

            print_spinner();
        }

        void thread_readonly ()
        {
            for (int i = 0; i < 6; ++i)
            {
                auto_mutex_readonly lock(m);
                DLIB_TEST(lock.has_read_lock());
                DLIB_TEST(!lock.has_write_lock());
                do_readonly_stuff();

                lock.lock_readonly();
                DLIB_TEST(lock.has_read_lock());
                DLIB_TEST(!lock.has_write_lock());
                lock.unlock();
                DLIB_TEST(!lock.has_read_lock());
                DLIB_TEST(!lock.has_write_lock());
                lock.lock_readonly();
                DLIB_TEST(lock.has_read_lock());
                DLIB_TEST(!lock.has_write_lock());
                lock.lock_write();
                DLIB_TEST(!lock.has_read_lock());
                DLIB_TEST(lock.has_write_lock());
                lock.lock_write();
                DLIB_TEST(!lock.has_read_lock());
                DLIB_TEST(lock.has_write_lock());
            }

            dlog << LINFO << "exit thread_readonly()";
        }

        void thread_readonly2 ()
        {
            for (int i = 0; i < 6; ++i)
            {
                m.lock_readonly();
                auto_unlock_readonly unlock(m);

                do_readonly_stuff();
            }
            dlog << LINFO << "exit thread_readonly2()";
        }


        void perform_test (
        )
        {
            num_write = 0;
            num_read = 0;
            max_read = 0;
            failure = false;

            // doing this big block of weird stuff should have no effect.  
            {
                m.unlock();

                m.lock_readonly();
                m.lock_readonly();
                m.unlock();
                m.unlock_readonly();
                m.unlock();
                m.unlock_readonly();

                m.unlock();
                m.unlock_readonly();

                m.lock();
                m.unlock_readonly();
                m.unlock_readonly();
                m.unlock();
            }


            // start up our testing threads
            start();

            // wait for the threads to finish
            wait();


            DLIB_TEST(failure == false);
            DLIB_TEST_MSG(max_read == 6, "max_read: "<< max_read);

        }

    } a;


}