/*
 * $Id: timer.c,v 1.2 2000/08/04 04:14:16 labovit Exp $
 */

#include <mrt.h>
#include <timer.h>


/* #define DEBUG_TIMER	1 */
 
Timer_Master *TIMER_MASTER;


/*
 * return some random number based on timer jitter values and rand()
 * Jitter values -20 and +30 generates a value between -20% and +30% of
 * the base interval.
 */
static int
timer_jitter (mtimer_t *timer)
{
    int range;
    int value = 0;
  
    /* old interface to set a jitter +/- abosolute value */
    if (timer->jitter > 0) {
	value = (rand() % (timer->jitter * 2 + 1)) - timer->jitter;
    }

    /* new way */
    if ((range = timer->time_jitter_high - timer->time_jitter_low) > 0) {
         value += (timer->time_interval_base * 
	          (timer->time_jitter_low + (rand () % (range + 1))) / 100);
    }
    return (value);
}


/*
 * return the number of seconds left before timer will fire
 */
int
time_left (mtimer_t *timer)
{
    assert (timer);
    return (timer->time_next_fire - time (NULL));
}
  

/*
 * Compare two timers for use in sorting. A timer that is 
 * ON always comes before OFF timers
 */
static int
Timer_Compare (mtimer_t *t1, mtimer_t *t2)
{

#ifdef DEBUG_TIMER
    printf("SORTING: Comparing %s:%d and %s:%d\n",
	   t1->name, t1->time_next_fire,
	   t2->name, t2->time_next_fire);
#endif
   
    if (t1->time_next_fire == 0 && t2->time_next_fire != 0)
         return (1);
    if (t1->time_next_fire != 0 && t2->time_next_fire == 0)
         return (-1);

    return (t1->time_next_fire - t2->time_next_fire);
}

 
/*
 * Update global TIMER_MASTER structure with new, or changed timer. 
 * if not given, sort anyway. Resort timer list and
 * change next_fire if timer fire before, and set alarm.
 */
static void
Timer_Master_Update (mtimer_t *timer)
{

    /* should be protected */
    assert (pthread_mutex_trylock (&TIMER_MASTER->mutex_lock));

    if (LL_GetCount (TIMER_MASTER->ll_timers) <= 0)
	return;

    /* sort the timers */
    if (timer)
	LL_ReSort (TIMER_MASTER->ll_timers, timer);

    timer = LL_GetHead (TIMER_MASTER->ll_timers);

    if (timer->time_next_fire == 0) {
        if (TIMER_MASTER->time_next_fire) {
            TIMER_MASTER->time_interval = 0;
            TIMER_MASTER->time_next_fire = 0;
            alarm (0);
            trace (TR_TIMER, TIMER_MASTER->trace, 
		   "Timer_Master stopped running\n");
        }
        return;
    }

    if (TIMER_MASTER->time_next_fire <= 0 /* not running */||
            TIMER_MASTER->time_next_fire > timer->time_next_fire) {
	time_t seconds;

        TIMER_MASTER->time_next_fire = timer->time_next_fire;
        TIMER_MASTER->time_interval = timer->time_interval;

	assert (TIMER_MASTER->time_next_fire > 0);

        seconds = TIMER_MASTER->time_next_fire - time (NULL);
        if (seconds <= 0)
            seconds = 1;
        alarm (seconds);
        trace (TR_TIMER, TIMER_MASTER->trace, 
	       "Timer_Master will be fired in %d seconds\n", seconds);
    }
}


/*
 * Multiplex alarm signal to list of timers. 
 * Fire timers if next fire time before current time
 */
static void
Timer_Master_Fire (void)
{
    mtimer_t *timer;
    time_t now;
    LINKED_LIST *ll = NULL;
    int fired = 0;

#ifdef DEBUG_TIMER
    printf ("Master TIMER FIRE!\n");
#endif
    pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
    time (&now);
    assert (LL_GetCount (TIMER_MASTER->ll_timers));
    TIMER_MASTER->time_next_fire = 0;

    LL_Iterate (TIMER_MASTER->ll_timers, timer) {

        if (timer->time_next_fire == 0 || timer->time_next_fire > now)
	    break; /* assume that they are sorted correctly */

	fired++;

	if (timer->schedule) {
	    /* schedule it right now while locking */
	    schedule_event3 (timer->schedule, Ref_Event (timer->event));
	}
	else {
	    /* delay it because execution time is unknown 
	       and more impotantly the function may call timer routines
	       that requires the lock (deadlock happens) */
	    LL_Add2 (ll, Ref_Event (timer->event));
	}

        if (BIT_TEST (timer->flags, TIMER_AUTO_DELETE)) {
	    mtimer_t *prev = LL_GetPrev (TIMER_MASTER->ll_timers, timer);
	    LL_RemoveFn (TIMER_MASTER->ll_timers, timer, NULL);
	    Deref_Event (timer->event);
	    if (timer->name)
    	        Delete (timer->name);
    	    Delete (timer);
	    timer = prev;
	}
        else if (BIT_TEST (timer->flags, TIMER_ONE_SHOT)) {
             timer->time_next_fire = 0;
        }
        else {
	    timer->time_interval = timer->time_interval_base;
	
            if (BIT_TEST (timer->flags, TIMER_EXPONENT)) {
		/* I don't care of overflow because it's not realistic */
	        timer->time_interval <<= timer->time_interval_exponent;
		if (timer->time_interval_exponent < 
			timer->time_interval_exponent_max)
		    timer->time_interval_exponent++;
	    }
	    timer->time_interval += timer_jitter (timer);
	    timer->time_next_fire += timer->time_interval;
    	}
    }

    /* unlock timer_master before calling their functions */
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);

    if (ll) {
	event_t *event;
        /* okay, finally call timer functions */
        LL_Iterate (ll, event) {
	    /* this destroy the event */
	    schedule_event_dispatch (event);
        }
        LL_Destroy (ll);
    }

    /* lock timer_master again to get time for the next fire */
    /* this is separated since the above function calls may destroy the timer */
    pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
    /* check to see if the master timer is updated */
    if (TIMER_MASTER->time_next_fire) {
        pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
	return;
    }

    if (fired)
        LL_Sort (TIMER_MASTER->ll_timers);
    Timer_Master_Update (NULL);
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
}


/*
 * Allocate memory and initialize global TIMER_MASTER variable
 */
void
init_timer (trace_t *tr)
{
    assert (TIMER_MASTER == NULL);

    TIMER_MASTER = New (Timer_Master);
    TIMER_MASTER->ll_timers = LL_Create (LL_CompareFunction, Timer_Compare,
			                 LL_AutoSort, True, 0);
    TIMER_MASTER->time_interval = 0;
    TIMER_MASTER->time_next_fire = 0;
    TIMER_MASTER->trace = trace_copy (tr);
    set_trace (TIMER_MASTER->trace, TRACE_PREPEND_STRING, "TIMER", 0);
    pthread_mutex_init (&TIMER_MASTER->mutex_lock, NULL);
    init_signal (tr, Timer_Master_Fire);
}


#ifdef notdef
void 
Destroy_Timer_Master (Timer_Master *timer)
{
    assert (timer);
    LL_Destroy (timer->ll_timers);
    Destroy (timer);
}
#endif


/*
 * Turn a timer off and update the TIMER_MASTER info
 */
void 
Timer_Turn_OFF (mtimer_t *timer)
{
    assert (timer);
   
    if (timer->time_next_fire > 0) {
        pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
        timer->time_next_fire = 0;
        Timer_Master_Update (timer);
        trace (TR_TIMER, TIMER_MASTER->trace, "Timer %s OFF\n", timer->name);
        pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
    }
}


/*
 * Reset timer to fire in timer->time_interval and 
 * notify TIMER_MASTER of change.
 */

void 
Timer_Reset_Time (mtimer_t *timer)
{
    assert (timer);

    if (timer->time_interval_base == 0 && timer->jitter == 0) {
	Timer_Turn_OFF (timer);
	return;
    }

    timer->time_interval = timer->time_interval_base;
    if (BIT_TEST (timer->flags, TIMER_EXPONENT)) {
	/* I don't care of overflow because it's not realistic */
        timer->time_interval <<= timer->time_interval_exponent;
	if (timer->time_interval_exponent < 
		timer->time_interval_exponent_max)
	    timer->time_interval_exponent++;
    }
    timer->time_interval += timer_jitter (timer);

    pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
    /* time_next_fire is a key to sort so that it must be protected */
    timer->time_next_fire = time (NULL) + timer->time_interval;
    trace (TR_TIMER, TIMER_MASTER->trace, "Timer %s restarted with %d\n", 
           timer->name, timer->time_interval);
    Timer_Master_Update (timer);
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
}


/*
 * Turn a timer ON and update TIMER_MASTER info
 */
void 
Timer_Turn_ON (mtimer_t *timer)
{
    Timer_Reset_Time (timer);
}


/*
 * Change intreval of timer and notify TIMER_MASTER of new info.
 * never starts the timer
 */
void
Timer_Set_Time (mtimer_t *timer, int interval)
{
    assert (timer);
    trace (TR_TIMER, TIMER_MASTER->trace, 
	   "Timer %s interval %d changed to %d\n", 
           timer->name, timer->time_interval_base, interval);
    timer->time_interval_base = interval;
    if (timer->time_next_fire > 0 /* running */)
        Timer_Reset_Time (timer);
} 


/* Unlike Timer_Set_Time, if running, adjust the next fire time */
void
Timer_Adjust_Time (mtimer_t *timer, int interval)
{
    assert (timer);
    trace (TR_TIMER, TIMER_MASTER->trace, 
	   "Timer %s interval %d adjusted to %d\n", 
           timer->name, timer->time_interval_base, interval);
    timer->time_interval_base = interval;
    if (timer->time_next_fire > 0 /* running */) {
	/* adjust next fire time */
	timer->time_next_fire += (interval - timer->time_interval);
        pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
        Timer_Master_Update (timer);
        pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
    }
} 


void 
timer_set_flags (mtimer_t *timer, u_long flags, ...)
{
    va_list ap;

    BIT_SET (timer->flags, flags);
    va_start (ap, flags);

    /* these are a new way so that the code may not take care of these flags,
       but it works as it was */
    switch (flags) {
    case TIMER_JITTER1:
	timer->jitter = va_arg (ap, int);
        break;
    case TIMER_JITTER2:
	timer->time_jitter_low = va_arg (ap, int);
        timer->time_jitter_high = va_arg (ap, int);
        break;
    case TIMER_EXPONENT_SET:
        timer->time_interval_exponent = va_arg (ap, int);
        break;
    case TIMER_EXPONENT_MAX:
        timer->time_interval_exponent_max = va_arg (ap, int);
        break;
    default:
	break;
    }
}


void
timer_set_jitter (mtimer_t *timer, int jitter)
{
    if (jitter != 0)
        BIT_SET (timer->flags, TIMER_JITTER1);
    timer->jitter = jitter;
}


void
timer_set_jitter2 (mtimer_t *timer, int low, int high)
{
    if (low != high)
        BIT_SET (timer->flags, TIMER_JITTER2);
    timer->time_jitter_low = low;
    timer->time_jitter_high = high;
}


/*
 * Allocate memory and initialize a mtimer_t structure. 
 * Also add new mtimer_t info to global TIMER_MASTER
 * NOTE: timers are created OFF!
 * if there is schedule, the function will be scheduled to be called later
 */
mtimer_t *
New_Timer2 (char *name, int interval, u_long flags, schedule_t *schedule, 
	    event_fn_t call_fn, int narg, ...)
{
    mtimer_t *timer;
    va_list ap;
    event_t *event;
    int i = 0;

    timer = New (mtimer_t);
/*
    timer->call_fn = call_fn;
    timer->arg = arg;
*/
    timer->time_interval_base = interval;
    timer->time_interval = 0;
    timer->time_interval_exponent = 0;
    timer->time_interval_exponent_max = 0;
    timer->time_next_fire = 0; /* off */
    timer->time_jitter_low = 0;
    timer->time_jitter_high = 0;
    timer->name = (name)? strdup (name): NULL;
    timer->flags = flags;

    va_start (ap, narg);

    if (BIT_TEST (flags, TIMER_PUSH_OWNARG))
	narg++;
    event = New_Event (narg);
    /* TIMER_PUSH_OWNARG & TIMER_AUTO_DELETE have to be set on creation */

    event->description = (timer->name)? strdup (timer->name): NULL;

    if (BIT_TEST (flags, TIMER_PUSH_OWNARG))
        event->args[i++] = timer;
    for (;i < narg; i++)
       event->args[i] = va_arg (ap, void *);

    event->call_fn = call_fn;

    va_end (ap);
    timer->event = event;
    timer->schedule = schedule;

    pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
    LL_Add (TIMER_MASTER->ll_timers, timer);
    Timer_Master_Update (NULL); /* already sorted */
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);
    return (timer);
}


/* old interface with only one argument */
mtimer_t *
New_Timer (event_fn_t call_fn, int interval, char *name, void *arg)
{
    return (New_Timer2 (name, interval, TIMER_PUSH_OWNARG, NULL, 
			call_fn, 1, arg));
}


void
Destroy_Timer (mtimer_t *timer)
{
    Timer_Turn_OFF (timer);
    pthread_mutex_lock (&TIMER_MASTER->mutex_lock);
    LL_Remove (TIMER_MASTER->ll_timers, timer);
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);

    Deref_Event (timer->event);
    if (timer->name)
        Delete (timer->name);
    Delete (timer);
}


syntax highlighted by Code2HTML, v. 0.9.1