#include "ibm.h"

#include "timer.h"

uint64_t TIMER_USEC;
uint32_t timer_target;

/*Enabled timers are stored in a linked list, with the first timer to expire at
  the head.*/
static pc_timer_t *timer_head = NULL;

void timer_enable(pc_timer_t *timer) {
        pc_timer_t *timer_node = timer_head;

        //	pclog("timer->enable %p %i\n", timer, timer->enabled);
        if (timer->enabled)
                timer_disable(timer);

        if (timer->next || timer->prev)
                fatal("timer_enable - timer->next\n");

        timer->enabled = 1;

        /*List currently empty - add to head*/
        if (!timer_head) {
                timer_head = timer;
                timer->next = timer->prev = NULL;
                timer_target = timer_head->ts_integer;
                return;
        }

        timer_node = timer_head;

        while (1) {
                /*Timer expires before timer_node. Add to list in front of timer_node*/
                if (TIMER_LESS_THAN(timer, timer_node)) {
                        timer->next = timer_node;
                        timer->prev = timer_node->prev;
                        timer_node->prev = timer;
                        if (timer->prev)
                                timer->prev->next = timer;
                        else {
                                timer_head = timer;
                                timer_target = timer_head->ts_integer;
                        }
                        return;
                }

                /*timer_node is last in the list. Add timer to end of list*/
                if (!timer_node->next) {
                        timer_node->next = timer;
                        timer->prev = timer_node;
                        return;
                }

                timer_node = timer_node->next;
        }
}
void timer_disable(pc_timer_t *timer) {
        //	pclog("timer->disable %p\n", timer);
        if (!timer->enabled)
                return;

        if (!timer->next && !timer->prev && timer != timer_head)
                fatal("timer_disable - !timer->next\n");

        timer->enabled = 0;

        if (timer->prev)
                timer->prev->next = timer->next;
        else
                timer_head = timer->next;
        if (timer->next)
                timer->next->prev = timer->prev;
        timer->prev = timer->next = NULL;
}
static void timer_remove_head() {
        if (timer_head) {
                pc_timer_t *timer = timer_head;
                //		pclog("timer_remove_head %p %p\n", timer_head, timer_head->next);
                timer_head = timer->next;
                timer_head->prev = NULL;
                timer->next = timer->prev = NULL;
                timer->enabled = 0;
        }
}

void timer_process() {
        if (!timer_head)
                return;

        while (1) {
                pc_timer_t *timer = timer_head;

                if (!TIMER_LESS_THAN_VAL(timer, (uint32_t)tsc))
                        break;

                timer_remove_head();
                timer->callback(timer->p);
        }

        timer_target = timer_head->ts_integer;
}

void timer_reset() {
        pclog("timer_reset\n");
        timer_target = 0;
        tsc = 0;
        timer_head = NULL;
}

void timer_add(pc_timer_t *timer, void (*callback)(void *p), void *p, int start_timer) {
        memset(timer, 0, sizeof(pc_timer_t));

        timer->callback = callback;
        timer->p = p;
        timer->enabled = 0;
        timer->prev = timer->next = NULL;
        if (start_timer)
                timer_set_delay_u64(timer, 0);
}
