// 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_CPP
#define DLIB_TIMER_KERNEl_2_CPP

#include "timer_kernel_2.h"

namespace dlib
{

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

    timer_kernel_2_global_clock::
    timer_kernel_2_global_clock(
    ): 
        s(m),
        shutdown(false),
        running(false)
    {
        register_program_ending_handler(*this,&timer_kernel_2_global_clock::destruct);
    }

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

    void timer_kernel_2_global_clock::
    destruct (
    )
    {
        delete this;
    }
    
// ----------------------------------------------------------------------------------------

    timer_kernel_2_global_clock::
    ~timer_kernel_2_global_clock()
    {
        m.lock();
        shutdown = true;
        s.signal();
        m.unlock();
        wait();
    }

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

    void timer_kernel_2_global_clock::
    add (
        timer_kernel_2_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_kernel_2_base* rtemp = r;
            uint64 ttemp = t;
            tm.add(ttemp,rtemp);
            r->next_time_to_run = t;
            r->in_global_clock = true;
        }
    }

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

    void timer_kernel_2_global_clock::
    remove (
        timer_kernel_2_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_kernel_2_base* rtemp;
                    tm.remove_current_element(t,rtemp);
                    r->in_global_clock = false;
                    break;
                }
            } while (tm.move_next());
        }
    }

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

    void timer_kernel_2_global_clock::
    adjust_delay (
        timer_kernel_2_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_kernel_2_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_kernel_2_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_kernel_2_base* r = tm.element().value();
                    timer_kernel_2_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->start();
                    }
                }
                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);
        }
    }

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

    timer_kernel_2_global_clock& get_global_clock()
    {
        static timer_kernel_2_global_clock* d = new timer_kernel_2_global_clock;
        return *d;
    }

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

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

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

}

#endif // DLIB_TIMER_KERNEl_2_CPP

