/* timer.pmc
 *  Copyright: 2001-2003 The Perl Foundation.  All Rights Reserved.
 *  CVS Info
 *     $Id: timer.pmc,v 1.7 2003/08/25 09:46:23 leo Exp $
 *  Overview:
 *     This is the Timer base class
 *  Data Structure and Algorithms:
 *     Running timers are kept in a linked list. Each timer has a
 *     tick count, which gets decremented if the system timer
 *     expires. If the tick count reaches zero, the timer handler gets
 *     invoked via Parrot's event handling code.
 *  History:
 *     Initial proposal by leo 2003.07.15
 *     Initial revision        2003.07.17
 *  Synopsis:
 *     new P0, .Timer
 *     set P0[.PARROT_TIMER_SEC], I_seconds        # whole seconds
 *     set P0[.PARROT_TIMER_USEC], I_micro seconds # and/or micro seconds
 *     set P0[.PARROT_TIMER_NSEC], N_seconds_frac  # or fraction in seconds
 *     set P0[.PARROT_TIMER_REPEAT], I_repeat      # 0 = one shot ...
 *     set_addr I0, _subroutine
 *     set P0, I0                                  # set timer handler sub
 *     invoke P0                                   # start timer
 *     set P0[.PARROT_TIMER_RUNNING], 1            # same
 *
 *     new P0, .Timer, P1                          # set everything
 *
 *     set I0, P0[.PARROT_TIMER_SEC]               # query current timer status
 *     set N0, P0[.PARROT_TIMER_NSEC]
 *     ...
 *     set P0[.PARROT_TIMER_RUNNING], 0            # turn timer off
 *
 *  Notes:
 *     The Timer resolution is operating system dependent. It's only
 *     guaranteed that the Timer will fire some time after the
 *     programmed interval.
 *     The Timer stops after invoking the handler (repeat+1) times.
 *     To create a Timer that will run forever, set "repeat" to -1.
 *     Turning the Timer off preserves set values; the Timer is
 *     not destroyed.
 *     When setting both PARROT_TIMER_SEC and PARROT_TIMER_USEC it
 *     must be done in that sequence, whole seconds first.
 *     If a timer is constructed with an initializer and it should
 *     be run immediately, the PARROT_TIMER_RUNNING value has to be in the
 *     last key of the initializer.
 *
 *  References:
 */

#include "parrot/parrot.h"
#include "parrot/method_util.h"

/*
 * for now we keep all stuff inside the pmc
 */

/* interface constants */
/* &gen_from_enum(timer.pasm) */
typedef enum {
    PARROT_TIMER_SEC,
    PARROT_TIMER_USEC,
    PARROT_TIMER_NSEC,
    PARROT_TIMER_REPEAT,
    PARROT_TIMER_RUNNING,
    PARROT_TIMER_HANDLER,
    PARROT_TIMER_MAX
} parrot_timer_enum_t;
/* &end_gen */

/* internal run state */
typedef enum {
    PARROT_TIMER_STATE_STOPPED,
    PARROT_TIMER_STATE_NEW,
    PARROT_TIMER_STATE_RUNNING,
    PARROT_TIMER_STATE_FIRE
} parrot_timer_state_t;

typedef struct parrot_timer_t {
    int flags;
    int ticks;
    int value;
    int repeat;
    int state;
    union {
	INTVAL pasm_code;
	void *c_code;
    } handler;
    PMC *self;
    struct parrot_timer_t *next;
    struct parrot_timer_t *prev;
} parrot_timer_t;

/* XXX next 2 should probably go into interpreter */
/* linked list of running timers */
static parrot_timer_t *rtimer = 0;
/* handle of system timer */
static void *handle = (void *) -1;

/* add timer to running */
static void
add_timer(parrot_timer_t *t)
{
    parrot_timer_t *next = rtimer;
    rtimer = t;
    t->next = next;
    if (next) {
	next->prev = t;
    }
    t->state = PARROT_TIMER_STATE_RUNNING;
    PObj_needs_early_DOD_SET(t->self);
}

static int
timer_is_running(parrot_timer_t *tp)
{
    parrot_timer_t *t;
    /* be paranoid, check run lis too */
    for (t = rtimer; t; t = t->next)
	if (t == tp)
	    return 1;
    return 0;
}

/* del timer from running */
static void
del_timer(parrot_timer_t *t)
{
    parrot_timer_t *next = t->next;
    parrot_timer_t *prev = t->prev;

    if (!timer_is_running(t))
	return;
    if (prev)
	prev->next = next;
    else
	rtimer = next;
    if (next)
	next->prev = prev;
    t->ticks = 0;
    t->state = PARROT_TIMER_STATE_STOPPED;
    PObj_needs_early_DOD_CLEAR(t->self);
}

/* TODO move this to misc.c (or util.c?), s. also math.ops */
static UINTVAL
gcd(UINTVAL a, UINTVAL b)
{

    UINTVAL q = 0;
    UINTVAL c = 0;
    while (b != 0) {
	q = (UINTVAL)floor( (FLOATVAL)a/b );
	c = a - b*q;
	a = b;
	b = c;
    }
    return a;
}

static void
recalc_ticks(void)
{
    UINTVAL ms, oms;
    parrot_timer_t *t;

    oms = get_sys_timer_ms(handle);
    t = rtimer;
    if (t) {
	if (t->state == PARROT_TIMER_STATE_NEW)
	    t->state = PARROT_TIMER_STATE_STOPPED;
	if (oms && t->ticks)
	    ms = oms*t->ticks;	/* time left */
	else
	    ms = t->value;
	if (t->next) {
	    for (t = t->next; t; t = t->next) {
		if (t->state == PARROT_TIMER_STATE_NEW)
		    t->state = PARROT_TIMER_STATE_STOPPED;
		if (oms && t->ticks) {
		    ms = gcd(oms*t->ticks, ms);
		}
		else
		    ms = gcd(t->value, ms);
	    }
	}
	for (t = rtimer; t; t = t->next) {
	    int ot = t->ticks;
	    if (oms && ot)
		t->ticks = t->ticks * ((double)oms / (double)ms);
	    else
		t->ticks = t->value / ms;
	    if (!t->ticks) {
		internal_exception(1, "Timer ticks are zero\n");
	    }
	}
    }
    else
	ms = 0;

    if (ms)
	start_sys_timer_ms(handle, ms);
    else
	stop_sys_timer_ms(handle);
}

static void
do_alarm_handler(void)
{
    parrot_timer_t *t;
    UINTVAL ms;
    int recalc = 0;

    ms = get_sys_timer_ms(handle);
    for (t = rtimer; t; t = t->next) {
	if (!--t->ticks) {
	    t->state = PARROT_TIMER_STATE_FIRE;
	    if (t->repeat) {
		t->ticks = t->value/ms;
		if (t->repeat != -1)
		    t->repeat--;
	    }
	}
    }
    /* do this in a separate loop, and always start over:
     * the timer handler function might add/stop timers
     */
again:
    for (t = rtimer; t; t= t->next) {
	if (t->state == PARROT_TIMER_STATE_FIRE) {
	    t->state = PARROT_TIMER_STATE_RUNNING;
	    /* TODO setup event */
	    goto again;
	}
	if (!t->ticks && !t->repeat) {
	    del_timer(t);
	    recalc = 1;
	    goto again;
	}
    }
    if (recalc)
	recalc_ticks();
}

pmclass Timer {
    void class_init() {
	if (handle == (void *) -1)
	    handle = new_sys_timer_ms();
    }

    void init() {
	parrot_timer_t *t = mem_sys_allocate_zeroed(sizeof(parrot_timer_t));
	t->state = PARROT_TIMER_STATE_NEW;
	t->self = SELF;
	SELF->cache.struct_val = t;
        PObj_active_destroy_SET(SELF);
	interpreter->has_early_DOD_PMCs = 1;
    }

    void init_pmc(PMC *init) {
	/*
	 * s. pdd02_vtables for intializers
	 */
	INTVAL n = VTABLE_get_integer(interpreter, init);
	INTVAL i;
	INTVAL val;
	FLOATVAL nval;

	SELF.init();
	for (i = 0; i < n; i += 2) {
	    INTVAL key = VTABLE_get_integer_keyed_int(interpreter, init, i);
	    switch (key) {
		case PARROT_TIMER_SEC:
		case PARROT_TIMER_USEC:
		case PARROT_TIMER_REPEAT:
		case PARROT_TIMER_RUNNING:
		case PARROT_TIMER_HANDLER:
		    val =
			VTABLE_get_integer_keyed_int(interpreter, init, i+1);
		    SELF.set_integer_keyed_int(key, val);
		    break;
		case PARROT_TIMER_NSEC:
		    nval =
			VTABLE_get_number_keyed_int(interpreter, init, i+1);
		    SELF.set_number_keyed_int(key, nval);
		    break;
	    }
	}
    }

    void destroy() {
	parrot_timer_t *t = SELF->cache.struct_val;
	del_timer(t);
	mem_sys_free(t);
    }

    INTVAL get_integer_keyed_int(INTVAL key) {
	parrot_timer_t *t = SELF->cache.struct_val;

	switch(key) {
	    case PARROT_TIMER_SEC:
		return t->value/1000;
		break;
	    case PARROT_TIMER_USEC:
		return (t->value % 1000) * 1000;
		break;
	    case PARROT_TIMER_REPEAT:
		return t->repeat;
		break;
	    case PARROT_TIMER_RUNNING:
		return timer_is_running(t);
		break;
	    case PARROT_TIMER_HANDLER:
		return t->handler.pasm_code;
		break;
	}
	return -1;
    }

    FLOATVAL get_number_keyed_int(INTVAL key) {
	parrot_timer_t *t = SELF->cache.struct_val;

	switch(key) {
	    case PARROT_TIMER_NSEC:
		return (FLOATVAL)t->value/1000.0;
		break;
	}
	return -1.0;
    }

    void set_integer_native(INTVAL value) {
	parrot_timer_t *t = SELF->cache.struct_val;
	t->handler.pasm_code = value;
    }

    void set_integer_keyed_int(INTVAL key, INTVAL value) {
	parrot_timer_t *t = SELF->cache.struct_val;

	switch(key) {
	    case PARROT_TIMER_SEC:
		t->value = value * 1000;
		break;
	    case PARROT_TIMER_USEC:
		t->value += value / 1000;
		break;
	    case PARROT_TIMER_REPEAT:
		t->repeat = value;
		break;
	    case PARROT_TIMER_RUNNING:
		if (value) {
		    if (!timer_is_running(t)) {
			add_timer(t);
			recalc_ticks();
		    }
		}
		else {
		    if (timer_is_running(t)) {
			del_timer(t);
			recalc_ticks();
		    }
		}
		break;
	    case PARROT_TIMER_HANDLER:
		t->handler.pasm_code = value;
		break;
	}
    }

    void* invoke(void *next) {
	parrot_timer_t *t = SELF->cache.struct_val;
	if (!timer_is_running(t)) {
	    add_timer(t);
	    recalc_ticks();
	}
	return next;
    }

    void set_number_keyed_int(INTVAL key, FLOATVAL value) {
	parrot_timer_t *t = SELF->cache.struct_val;

	switch(key) {
	    case PARROT_TIMER_NSEC:
		t->value = (INTVAL) (value * 1000.0);
		break;
	}
    }
}
