// Copyright (C) 2007  Davis E. King (davisking@users.sourceforge.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_TIMER_KERNEl_2_
#define DLIB_TIMER_KERNEl_2_

#include "../threads.h"
#include "../algs.h"
#include "../misc_api.h"
#include "timer_kernel_abstract.h"
#include "../uintn.h"
#include "../binary_search_tree.h"

namespace dlib
{

    struct timer_kernel_2_base : public threaded_object
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object contains the base members of the timer_kernel_2 object.
                It exists so that we can access them from outside any templated functions.
        !*/

        unsigned long delay;
        // these are only modified by the global_clock
        uint64 next_time_to_run;
        timestamper ts;
        bool running;
        bool in_global_clock;
    };

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

    class timer_kernel_2_global_clock : private threaded_object
    {
        /*!
            This object sets up a timer that triggers the action function
            for timer_kernel_2 objects that are tracked inside this object. 
            INITIAL VALUE
                - shutdown == false
                - running == false

            CONVENTION
                - if (shutdown) then
                    - thread() should terminate
                - else (running) then
                    - thread() is running

                - tm[time] == pointer to a timer_kernel_2_base object 
        !*/
        typedef binary_search_tree<uint64,timer_kernel_2_base*,memory_manager<char>::kernel_2b>::kernel_2a_c time_map;
    public:

        ~timer_kernel_2_global_clock();

        void add (
            timer_kernel_2_base* r
        );
        /*!
            requires
                - m is locked
            ensures
                - starts the thread if it isn't already started
                - adds r to tm
                - #r->in_global_clock == true
                - updates r->next_time_to_run appropriately according to
                    r->delay
        !*/

        void remove (
            timer_kernel_2_base* r
        );
        /*!
            requires
                - m is locked
            ensures
                - if (r is in tm) then
                    - removes r from tm
                - #r->in_global_clock == false
        !*/

        void adjust_delay (
            timer_kernel_2_base* r,
            unsigned long new_delay
        );
        /*!
            requires
                - m is locked
            ensures
                - #r->delay == new_delay
                - if (r->in_global_clock) then
                    - the time to the next event will have been appropriately adjusted
        !*/

        mutex m;

        friend timer_kernel_2_global_clock& get_global_clock();

    private:
        timer_kernel_2_global_clock();

        void destruct();
        /*!
            ensures
                - calls delete this;
        !*/

        time_map tm;  
        signaler s;
        bool shutdown;
        bool running;
        timestamper ts;

        void thread();
        /*!
            ensures
                - spawns timer tasks as is appropriate
        !*/
    };
    timer_kernel_2_global_clock& get_global_clock();
    /*!
        ensures
            - returns the global instance of the timer_kernel_2_global_clock object
    !*/

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

    template <
        typename T
        >
    class timer_kernel_2 : private timer_kernel_2_base 
    {
        /*!
            INITIAL VALUE
                - running   == false
                - delay     == 1000
                - ao        == a pointer to the action_object()
                - af        == a pointer to the action_function()
                - in_global_clock == false
                - next_time_to_run == 0

            CONVENTION
                - the mutex used to lock everything is get_global_clock().m
                - running == is_running()
                - delay == delay_time()
                - *ao == action_object()
                - af == action_function()    
                - if (!running) then
                    - in_global_clock == false
                - else 
                    - next_time_to_run == the next time this timer should run according
                      to the timestamper in the global_clock
        !*/

    public:

        typedef void (T::*af_type)();

        timer_kernel_2(  
            T& ao_,
            af_type af_
        );

        virtual ~timer_kernel_2(
        );

        void clear(
        );

        af_type action_function (
        ) const;

        const T& action_object (
        ) const;

        T& action_object (
        );

        bool is_running (
        ) const;

        unsigned long delay_time (
        ) const;

        void set_delay_time (
            unsigned long milliseconds
        );
        
        void start (            
        );

        void stop (
        );

        void stop_and_wait (
        );

    private: 

        void thread (
        );
        /*!
            ensures
                - calls the action function
        !*/

        // data members
        T& ao;
        const af_type af;

        // restricted functions
        timer_kernel_2(const timer_kernel_2&);        // copy constructor
        timer_kernel_2& operator=(const timer_kernel_2&);    // assignment operator

    };    

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    
    template <
        typename T
        >
    timer_kernel_2<T>::
    timer_kernel_2(  
        T& ao_,
        af_type af_
    ) : 
        ao(ao_),
        af(af_)
    {
        delay = 1000;
        next_time_to_run = 0;
        running = false;
        in_global_clock = false;
    }

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

    template <
        typename T
        >
    timer_kernel_2<T>::
    ~timer_kernel_2(
    )
    {
        clear();
        wait();
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    clear(
    )
    {
        auto_mutex M(get_global_clock().m);
        running = false;
        get_global_clock().remove(this);
        delay = 1000;        
        next_time_to_run = 0;
    }

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

    template <
        typename T
        >
    typename timer_kernel_2<T>::af_type timer_kernel_2<T>::
    action_function (
    ) const
    {
        return af;
    }

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

    template <
        typename T
        >
    const T& timer_kernel_2<T>::
    action_object (
    ) const
    {
        return ao;
    }

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

    template <
        typename T
        >
    T& timer_kernel_2<T>::
    action_object (
    )
    {
        return ao;
    }

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

    template <
        typename T
        >
    bool timer_kernel_2<T>::
    is_running (
    ) const
    {
        auto_mutex M(get_global_clock().m);
        return running;
    }

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

    template <
        typename T
        >
    unsigned long timer_kernel_2<T>::
    delay_time (
    ) const
    {
        auto_mutex M(get_global_clock().m);
        return delay;        
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    set_delay_time (
        unsigned long milliseconds
    )
    {
        auto_mutex M(get_global_clock().m);
        get_global_clock().adjust_delay(this,milliseconds);
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    start (            
    )
    {
        auto_mutex M(get_global_clock().m);
        if (!running)
        {
            get_global_clock().add(this);
            running = true;
        }
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    stop (
    )
    {
        get_global_clock().m.lock();
        running = false;
        get_global_clock().remove(this);
        get_global_clock().m.unlock();
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    thread (
    )
    {
        // call the action function
        (ao.*af)(); 
        auto_mutex M(get_global_clock().m);
        if (running)
        {
            get_global_clock().remove(this);
            get_global_clock().add(this);
        }
    }

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

    template <
        typename T
        >
    void timer_kernel_2<T>::
    stop_and_wait (
    )
    {
        get_global_clock().m.lock();
        running = false;
        get_global_clock().remove(this);
        get_global_clock().m.unlock();
        wait();
    }

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

}

#ifdef NO_MAKEFILE
#include "timer_kernel_2.cpp"
#endif

#endif // DLIB_TIMER_KERNEl_2_


