#include "Jtk.h"
#include <vector>
#include <memory>
#include <list>

typedef struct
{
    uint64_t next;      /**< Time the timer next expires (us). */
    uint64_t interval;  /**< Timer interval (us). */
    size_t id;          /**< Timer ID. */
    void * user[2];     /**< User data pointers. */
} Timer;

/** Vector used to allocate timer IDs. */
static std::vector<std::shared_ptr<Timer>> g_timers;

/** Linked list used to traverse all active timers. */
static std::list<std::shared_ptr<Timer>> g_active_timers;

static size_t AllocateTimerID()
{
    for (size_t i = 0; i < g_timers.size(); i++)
    {
        if (!g_timers[i])
        {
            return i;
        }
    }
    g_timers.push_back(nullptr);
    return g_timers.size() - 1u;
}

/**
 * Add a timer.
 *
 * @param delay
 *   Delay time in milliseconds.
 * @param interval
 *   Interval time in milliseconds. A value of 0 indicates that the timer
 *   is not periodic.
 * @param user1
 *   User data pointer 1. This value is not used by Jtk but can be retrieved by
 *   the user when a timer expires.
 * @param user2
 *   User data pointer 2. This value is not used by Jtk but can be retrieved by
 *   the user when a timer expires.
 *
 * @return
 *   Timer ID.
 */
size_t Jtk_AddTimer(uint32_t delay, uint32_t interval, void * user1, void * user2)
{
    uint64_t current_system_time = Jtk_UsTime();
    size_t timer_id = AllocateTimerID();
    auto timer = std::make_shared<Timer>();
    timer->next = current_system_time + (delay * 1000u);
    timer->interval = interval * 1000u;
    timer->id = timer_id;
    timer->user[0] = user1;
    timer->user[1] = user2;
    g_timers[timer_id] = timer;
    g_active_timers.push_back(timer);
    return timer_id;
}

/**
 * Remove a timer.
 *
 * @param timer_id
 *   The ID of the timer to remove.
 */
void Jtk_RemoveTimer(size_t timer_id)
{
    if (timer_id < g_timers.size())
    {
        auto timer = g_timers[timer_id];
        g_active_timers.remove(timer);
        g_timers[timer_id] = nullptr;
    }
}

/**
 * Determine the amount of time until the next timer expires (in us).
 *
 * @return
 *   Time (in us) until the next timer expires. This will be 0 if an active
 *   timer has already expired, and will be (uint64_t)-1 if there are no
 *   active timers.
 */
uint64_t Jtk_TimeToNextTimerExpiration()
{
    uint64_t time = (uint64_t)-1;
    uint64_t current_system_time = Jtk_UsTime();
    for (auto & timer : g_active_timers)
    {
        if (timer->next <= current_system_time)
        {
            return 0;
        }
        uint64_t time_until_this_timer = timer->next - current_system_time;
        if (time_until_this_timer < time)
        {
            time = time_until_this_timer;
        }
    }
    return time;
}

/**
 * Service a timer.
 *
 * This will increment the timer's next activation time by its interval, or
 * for a timer with an interval of 0, will remove the timer.
 *
 * @param timer_id
 *   The ID of the timer to service.
 */
void Jtk_ServiceTimer(size_t timer_id)
{
    auto timer = g_timers[timer_id];
    if (timer->interval == 0u)
    {
        Jtk_RemoveTimer(timer_id);
    }
    else
    {
        timer->next += timer->interval;
    }
}

/**
 * Return the ID of an expired timer.
 *
 * @return
 *   The ID of an expired timer, or (size_t)-1 if no timer is expired.
 */
size_t Jtk_GetExpiredTimer()
{
    uint64_t current_system_time = Jtk_UsTime();
    for (auto & timer : g_active_timers)
    {
        if (timer->next <= current_system_time)
        {
            return timer->id;
        }
    }
    return (size_t)-1;
}

void * Jtk_GetTimerUserData(size_t timer_id, uint8_t index)
{
    if (timer_id < g_timers.size())
    {
        return g_timers[timer_id]->user[index];
    }
    return nullptr;
}
