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

    #include "timer.h"

    namespace dlib
    {

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

        timer_global_clock::
        timer_global_clock(
        ):
            s(m),
            shutdown(false),
            running(false)
        {
        }

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

        timer_global_clock::
        ~timer_global_clock()
        {
            // The only time this destructor is called is when
            //
            // a) the process terminates
            // b) the dynamic library(.so/.dll) is unloaded (could be a part of a))
            //
            // in case of a)
            //   windows: the process termination is especially painful, since threads are killed
            //     before destructors of the process image .dll's are called.
            //     Thus, for the windows platform, there is no threads running, so the only thing
            //     to do here is just let the standard memberwise destructors run
            //   linux: it's ok to just signal shutdown and wait for the running thread, to exit
            //
            // in case of b)
            //   windows:
            //     if it's part of the termination process, a) applies
            //     if its part of user doing manual load_library/unload_library
            //     there is no (safe/robust)solution, but best practices are described here
            //          https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971.aspx
            //     to support such a clean shutdown, you are required to make a call prior to
            //     unload dll, that shutdown all the threads in the contained dll.
            //     This could be done in this module by providing a global_delete_clock()
            //
            // linux: the destructor for linux will do it's usual job regardless.
            //

            #ifndef _WIN32
            m.lock();
            shutdown = true;
            s.signal();
            m.unlock();
            wait();
            #endif
        }

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

        void timer_global_clock::
        add (
            timer_base* r
        )
        {
            if (r->in_global_clock == false)
            {
                // if the thread isn't running then start it up
                if (!running)
                {
                    start();
                    running = true;
                }

                uint64 t = ts.get_timestamp() + r->delay*1000;
                tm.reset();
                if (!tm.move_next() || t < tm.element().key())
                {
                    // we need to make the thread adjust its next time to
                    // trigger if this new event occurrs sooner than the
                    // next event in tm
                    s.signal();
                }
                timer_base* rtemp = r;
                uint64 ttemp = t;
                tm.add(ttemp,rtemp);
                r->next_time_to_run = t;
                r->in_global_clock = true;
            }
        }

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

        void timer_global_clock::
        remove (
            timer_base* r
        )
        {
            if (r->in_global_clock)
            {
                tm.position_enumerator(r->next_time_to_run-1);
                do
                {
                    if (tm.element().value() == r)
                    {
                        uint64 t;
                        timer_base* rtemp;
                        tm.remove_current_element(t,rtemp);
                        r->in_global_clock = false;
                        break;
                    }
                } while (tm.move_next());
            }
        }

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

        void timer_global_clock::
        adjust_delay (
            timer_base* r,
            unsigned long new_delay
        )
        {
            if (r->in_global_clock)
            {
                remove(r);
                // compute the new next_time_to_run and store it in t
                uint64 t = r->next_time_to_run;
                t -= r->delay*1000;
                t += new_delay*1000;

                tm.reset();
                if (!tm.move_next() || t < tm.element().key())
                {
                    // we need to make the thread adjust its next time to
                    // trigger if this new event occurrs sooner than the
                    // next event in tm
                    s.signal();
                }

                // set this incase add throws
                r->running = false;
                r->delay = new_delay;

                timer_base* rtemp = r;
                uint64 ttemp = t;
                tm.add(ttemp,rtemp);
                r->next_time_to_run = t;
                r->in_global_clock = true;

                // put this back now that we know add didn't throw
                r->running = true;

            }
            else
            {
                r->delay = new_delay;
            }
        }

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

        void timer_global_clock::
        thread()
        {
            auto_mutex M(m);
            while (!shutdown)
            {
                unsigned long delay = 100000;

                tm.reset();
                tm.move_next();
                // loop and start all the action functions for timers that should have
                // triggered.
                while(tm.current_element_valid())
                {
                    const uint64 cur_time = ts.get_timestamp();
                    uint64 t = tm.element().key();
                    // if the next event in tm is ready to trigger
                    if (t <= cur_time + 999)
                    {
                        // remove this event from the tm map
                        timer_base* r = tm.element().value();
                        timer_base* rtemp;
                        tm.remove_current_element(t,rtemp);
                        r->in_global_clock = false;

                        // if this timer is still "running" then start its action function
                        if (r->running)
                        {
                            r->restart();
                        }
                    }
                    else
                    {
                        // there aren't any more timers that should trigger so we compute
                        // the delay to the next timer event.
                        delay = static_cast<unsigned long>((t - cur_time)/1000);
                        break;
                    }
                }

                s.wait_or_timeout(delay);
            }
        }

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

        std::shared_ptr<timer_global_clock> get_global_clock()
        {
            static std::shared_ptr<timer_global_clock> d(new timer_global_clock);
            return d;
        }

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

        // do this just to make sure get_global_clock() gets called at program startup
        class timer_global_clock_helper
        {
        public:
            timer_global_clock_helper()
            {
                get_global_clock();
            }
        };
        static timer_global_clock_helper call_get_global_clock;

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

    }

    #endif // DLIB_TIMER_cPPh_