// 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);
        }
    }

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

    shared_ptr_thread_safe<timer_global_clock> get_global_clock()
    {
        static shared_ptr_thread_safe<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_

