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

#include <memory>

#include "../threads.h"
#include "../algs.h"
#include "../misc_api.h"
#include "timer_abstract.h"
#include "../uintn.h"
#include "../binary_search_tree.h"
#include "timer_heavy.h"

namespace dlib
{

    struct timer_base : public threaded_object
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object contains the base members of the timer 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_global_clock : private threaded_object
    {
        /*!
            This object sets up a timer that triggers the action function
            for timer 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_base object 
        !*/
        typedef binary_search_tree<uint64,timer_base*,memory_manager<char>::kernel_2b>::kernel_2a_c time_map;
    public:

        ~timer_global_clock();

        void add (
            timer_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_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_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 std::shared_ptr<timer_global_clock> get_global_clock();

    private:
        timer_global_clock();

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

        void thread();
        /*!
            ensures
                - spawns timer tasks as is appropriate
        !*/
    };
    std::shared_ptr<timer_global_clock> get_global_clock();
    /*!
        ensures
            - returns the global instance of the timer_global_clock object
    !*/

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

    template <
        typename T
        >
    class timer : private timer_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
                - gc == get_global_clock()

            CONVENTION
                - the mutex used to lock everything is gc->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:

        // These typedefs are here for backwards compatibility with previous versions of
        // dlib.
        typedef timer_heavy<T> kernel_1a;
        typedef timer kernel_2a;

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

        timer(  
            T& ao_,
            af_type af_
        );

        virtual ~timer(
        );

        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;
        std::shared_ptr<timer_global_clock> gc;

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

    };    

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

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

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

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

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

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

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

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

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

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

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

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

    template <
        typename T
        >
    bool timer<T>::
    is_running (
    ) const
    {
        auto_mutex M(gc->m);
        return running;
    }

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

    template <
        typename T
        >
    unsigned long timer<T>::
    delay_time (
    ) const
    {
        auto_mutex M(gc->m);
        return delay;        
    }

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

    template <
        typename T
        >
    void timer<T>::
    set_delay_time (
        unsigned long milliseconds
    )
    {
        auto_mutex M(gc->m);
        gc->adjust_delay(this,milliseconds);
    }

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

    template <
        typename T
        >
    void timer<T>::
    start (            
    )
    {
        auto_mutex M(gc->m);
        if (!running)
        {
            gc->add(this);
            running = true;
        }
    }

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

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

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

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

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

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

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

}

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

#endif // DLIB_TIMEr_Hh_