// Copyright (C) 2007  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/timer.h>
#include <dlib/timeout.h>
#include "tester.h"

namespace  
{

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

    logger dlog("test.timer");

    class timer_test_helper
    {
    public:
        dlib::mutex m;
        int count;
        dlib::uint64 timestamp;
        dlib::timestamper ts;

        timer_test_helper():count(0), timestamp(0){}
        void add() 
        { 
            m.lock(); 
            ++count; 
            m.unlock(); 
        }

        void delayed_add()
        {
            dlib::sleep(1000);
            print_spinner();
            add();
        }

        void set_timestamp()
        {
            m.lock();
            timestamp = ts.get_timestamp();
            dlog << LTRACE << "in set_timestamp(), time is " << timestamp;
            dlib::sleep(1);
            print_spinner();
            m.unlock();
        }
    };

    template <
        typename timer_t
        >
    void timer_test2 (
    )
    /*!
        requires
            - timer_t is an implementation of dlib/timer/timer_abstract.h is instantiated 
              timer_test_helper
        ensures
            - runs tests on timer_t for compliance with the specs 
    !*/
    {        
        for (int j = 0; j < 4; ++j)
        {
            print_spinner();
            timer_test_helper h;

            timer_t t1(h,&timer_test_helper::set_timestamp);
            t1.set_delay_time(0);
            dlog << LTRACE << "t1.start()";
            t1.start();

            dlib::sleep(60);
            print_spinner();
            t1.stop_and_wait();

            dlib::uint64 cur_time = h.ts.get_timestamp();
            dlog << LTRACE << "get current time: " << cur_time;

            // make sure the action function has been called recently
            DLIB_TEST_MSG((cur_time-h.timestamp)/1000 < 30, (cur_time-h.timestamp)/1000);

        }
    }

    template <
        typename timer_t
        >
    void timer_test (
    )
    /*!
        requires
            - timer_t is an implementation of dlib/timer/timer_abstract.h is instantiated 
              timer_test_helper
        ensures
            - runs tests on timer_t for compliance with the specs 
    !*/
    {        

        print_spinner();
        for (int j = 0; j < 3; ++j)
        {
            timer_test_helper h;

            timer_t t1(h,&timer_test_helper::add);
            timer_t t2(h,&timer_test_helper::add);
            timer_t t3(h,&timer_test_helper::add);

            DLIB_TEST(t1.delay_time() == 1000);
            DLIB_TEST(t2.delay_time() == 1000);
            DLIB_TEST(t3.delay_time() == 1000);
            DLIB_TEST(t1.is_running() == false);
            DLIB_TEST(t2.is_running() == false);
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t1.action_function() == &timer_test_helper::add);
            DLIB_TEST(t2.action_function() == &timer_test_helper::add);
            DLIB_TEST(t3.action_function() == &timer_test_helper::add);
            DLIB_TEST(&t1.action_object() == &h);
            DLIB_TEST(&t2.action_object() == &h);
            DLIB_TEST(&t3.action_object() == &h);

            t1.set_delay_time(1000);
            t2.set_delay_time(500);
            t3.set_delay_time(1500);

            DLIB_TEST(t1.delay_time() == 1000);
            DLIB_TEST(t2.delay_time() == 500);
            DLIB_TEST(t3.delay_time() == 1500);
            DLIB_TEST(t1.is_running() == false);
            DLIB_TEST(t2.is_running() == false);
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t1.action_function() == &timer_test_helper::add);
            DLIB_TEST(t2.action_function() == &timer_test_helper::add);
            DLIB_TEST(t3.action_function() == &timer_test_helper::add);
            DLIB_TEST(&t1.action_object() == &h);
            DLIB_TEST(&t2.action_object() == &h);
            DLIB_TEST(&t3.action_object() == &h);
            dlib::sleep(1100);
            print_spinner();
            DLIB_TEST(h.count == 0);

            t1.stop_and_wait();
            t2.stop_and_wait();
            t3.stop_and_wait();

            dlib::sleep(1100);
            print_spinner();
            DLIB_TEST(h.count == 0);
            DLIB_TEST(t1.delay_time() == 1000);
            DLIB_TEST(t2.delay_time() == 500);
            DLIB_TEST(t3.delay_time() == 1500);
            DLIB_TEST(t1.is_running() == false);
            DLIB_TEST(t2.is_running() == false);
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t1.action_function() == &timer_test_helper::add);
            DLIB_TEST(t2.action_function() == &timer_test_helper::add);
            DLIB_TEST(t3.action_function() == &timer_test_helper::add);
            DLIB_TEST(&t1.action_object() == &h);
            DLIB_TEST(&t2.action_object() == &h);
            DLIB_TEST(&t3.action_object() == &h);

            t1.start();
            t2.start();
            t3.start();

            DLIB_TEST(t1.delay_time() == 1000);
            DLIB_TEST(t2.delay_time() == 500);
            DLIB_TEST(t3.delay_time() == 1500);
            DLIB_TEST(t1.is_running() == true);
            DLIB_TEST(t2.is_running() == true);
            DLIB_TEST(t3.is_running() == true);
            DLIB_TEST(t1.action_function() == &timer_test_helper::add);
            DLIB_TEST(t2.action_function() == &timer_test_helper::add);
            DLIB_TEST(t3.action_function() == &timer_test_helper::add);
            DLIB_TEST(&t1.action_object() == &h);
            DLIB_TEST(&t2.action_object() == &h);
            DLIB_TEST(&t3.action_object() == &h);

            t1.stop();
            t2.stop();
            t3.stop();

            DLIB_TEST(t1.delay_time() == 1000);
            DLIB_TEST(t2.delay_time() == 500);
            DLIB_TEST(t3.delay_time() == 1500);
            DLIB_TEST(t1.is_running() == false);
            DLIB_TEST(t2.is_running() == false);
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t1.action_function() == &timer_test_helper::add);
            DLIB_TEST(t2.action_function() == &timer_test_helper::add);
            DLIB_TEST(t3.action_function() == &timer_test_helper::add);
            DLIB_TEST(&t1.action_object() == &h);
            DLIB_TEST(&t2.action_object() == &h);
            DLIB_TEST(&t3.action_object() == &h);

            DLIB_TEST(h.count == 0);
            dlib::sleep(1100);
            print_spinner();
            DLIB_TEST(h.count == 0);

            for (int i = 1; i <= 3; ++i)
            {
                t1.start();
                t2.start();
                t3.start();

                DLIB_TEST(t1.is_running() == true);
                DLIB_TEST(t2.is_running() == true);
                DLIB_TEST(t3.is_running() == true);

                dlib::sleep(1800);
                print_spinner();
                // this should allow the timers to trigger 5 times
                t1.stop();
                t2.stop();
                t3.stop();

                DLIB_TEST_MSG(h.count == 5*i,"h.count: " << h.count << " i: " << i);
                dlib::sleep(1100);
                DLIB_TEST_MSG(h.count == 5*i,"h.count: " << h.count << " i: " << i);
            }


            t1.stop_and_wait();

            h.count = 0;
            t1.start();
            dlib::sleep(300);
            print_spinner();
            DLIB_TEST_MSG(h.count == 0,h.count);
            t1.set_delay_time(400);
            dlib::sleep(200);
            print_spinner();
            DLIB_TEST_MSG(h.count == 1,h.count);
            dlib::sleep(250);
            print_spinner();
            DLIB_TEST_MSG(h.count == 1,h.count);
            dlib::sleep(100);
            print_spinner();
            DLIB_TEST_MSG(h.count == 2,h.count);
            t1.set_delay_time(2000);
            DLIB_TEST_MSG(h.count == 2,h.count);
            dlib::sleep(1000);
            print_spinner();
            DLIB_TEST_MSG(h.count == 2,h.count);
            t1.clear();

            h.count = 0;
            t3.start();
            DLIB_TEST(t3.is_running() == true);
            DLIB_TEST(t3.delay_time() == 1500);
            DLIB_TEST_MSG(h.count == 0,h.count);
            t3.clear();
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t3.delay_time() == 1000);
            DLIB_TEST_MSG(h.count == 0,h.count);
            dlib::sleep(200);
            print_spinner();
            DLIB_TEST(t3.is_running() == false);
            DLIB_TEST(t3.delay_time() == 1000);
            DLIB_TEST_MSG(h.count == 0,h.count);


            {
                h.count = 0;
                timer_t t4(h,&timer_test_helper::delayed_add);
                t4.set_delay_time(100);
                t4.start();
                DLIB_TEST_MSG(h.count == 0,h.count);
                dlib::sleep(400);
                print_spinner();
                DLIB_TEST_MSG(h.count == 0,h.count);
                t4.stop_and_wait();
                DLIB_TEST_MSG(h.count == 1,h.count);
                DLIB_TEST(t4.is_running() == false);
            }

            {
                h.count = 0;
                timer_t t4(h,&timer_test_helper::delayed_add);
                t4.set_delay_time(100);
                t4.start();
                DLIB_TEST_MSG(h.count == 0,h.count);
                dlib::sleep(400);
                print_spinner();
                DLIB_TEST_MSG(h.count == 0,h.count);
                t4.clear();
                DLIB_TEST(t4.is_running() == false);
                DLIB_TEST_MSG(h.count == 0,h.count);
                t4.stop_and_wait();
                DLIB_TEST_MSG(h.count == 1,h.count);
                DLIB_TEST(t4.is_running() == false);
            }

            {
                h.count = 0;
                timer_t t5(h,&timer_test_helper::delayed_add);
                t5.set_delay_time(100);
                t5.start();
                DLIB_TEST_MSG(h.count == 0,h.count);
                dlib::sleep(400);
                print_spinner();
                DLIB_TEST_MSG(h.count == 0,h.count);
            }
            DLIB_TEST_MSG(h.count == 1,h.count);

        }

    }




    class timer_tester : public tester
    {
    public:
        timer_tester (
        ) :
            tester ("test_timer",
                    "Runs tests on the timer component.")
        {}

        void perform_test (
        )
        {
            dlog << LINFO << "testing timer_heavy with test_timer";
            timer_test<timer_heavy<timer_test_helper> >  ();
            dlog << LINFO << "testing timer_heavy with test_timer2";
            timer_test2<timer_heavy<timer_test_helper> >  ();

            dlog << LINFO << "testing timer with test_timer";
            timer_test<timer<timer_test_helper> >  ();
            dlog << LINFO << "testing timer with test_timer2";
            timer_test2<timer<timer_test_helper> >  ();
        }
    } a;

}