/*
 * $Id: timer.c,v 1.5 1999/10/19 15:01:16 labovit Exp $
 */

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

int nn;

/* #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);
}

 
#ifdef notdef
sigset_t
block_signal (int sig)
{
    sigset_t new, old;

    sigemptyset (&new);
    sigaddset (&new, sig);
#ifdef HAVE_LIBPTHREAD
    pthread_sigmask (SIG_BLOCK, &new, &old);
#else
    sigprocmask (SIG_BLOCK, &new, &old);
#endif /* HAVE_LIBPTHREAD */
    return (old);
}


void
recover_signal (sigset_t old)
{
#ifdef HAVE_LIBPTHREAD
    pthread_sigmask (SIG_SETMASK, &old, NULL);
#else
    sigprocmask (SIG_SETMASK, &old, NULL);
#endif /* HAVE_LIBPTHREAD */
}
#endif


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

    trace (TR_TIMER, TIMER_MASTER->trace, "Timer_Master fired!\n");

#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));

    LL_Iterate (TIMER_MASTER->ll_timers, timer) {

        if (timer->time_next_fire == 0 || timer->time_next_fire > now)
	    break; /* assume that hey 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);
    	    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;
    	}
    }

    if (fired)
        LL_Sort (TIMER_MASTER->ll_timers);

    if ((timer = LL_GetHead (TIMER_MASTER->ll_timers)) && 
	    timer->time_next_fire > 0) {
#ifdef DEBUG_TIMER
        printf ("Head of list = %s:%d\n", timer->name, 
					  timer->time_next_fire);
#endif
        TIMER_MASTER->time_interval = timer->time_interval;
        TIMER_MASTER->time_next_fire = timer->time_next_fire;
    }
    else {
#ifdef DEBUG_TIMER
        if (timer)
            printf ("Head of list = %s:%d but OFF\n", timer->name, 
		    timer->time_next_fire);
#endif
#ifdef notdef
        TIMER_MASTER->time_interval = 10;        /* always fire just in case */
        TIMER_MASTER->time_next_fire = now + 10; /* always fire just in case */
#else
        TIMER_MASTER->time_interval = 0;
        TIMER_MASTER->time_next_fire = 0;	/* stop running */
        trace (TR_TIMER, TIMER_MASTER->trace, "Timer_Master stopped running\n");
#endif
    }

    /* save it to be changed by others */
    time_next_fire = TIMER_MASTER->time_next_fire;
    pthread_mutex_unlock (&TIMER_MASTER->mutex_lock);

    if (ll) {
	event_t *event;
        /* okay, finally schedule timer calls */
        LL_Iterate (ll, event) {
	    
	  strcpy (TIMER_MASTER->current_function, event->description);
	  /* this destroy the event */
	  schedule_event_dispatch (event);
	  strcpy (TIMER_MASTER->current_function, "---");
        }
        LL_Destroy (ll);

    }

    /* next alarm set here 
       because we don't want to be called before returning */
    if (time_next_fire > 0) {
        time_next_fire -= time (NULL);
        if (time_next_fire <= 0)
	    time_next_fire = 1;
        alarm (time_next_fire);
        trace (TR_TIMER, TIMER_MASTER->trace, 
	       "Timer_Master will be fired %d seconds later\n", time_next_fire);
    }

#ifdef notdef
    if (time_next_fire <= 1) {
        while (1); /* loop so gdb can break here */
    }
#endif
}


/*
 * 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.
 */
void Timer_Master_Update (mtimer_t *timer)
{
  

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

    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);
    }
}


#ifndef HAVE_LIBPTHREAD
/* Thread uses sigwait(), instead */

static int alarm_pending = 0;

/* This is the only function that's invoked by asynchronous signal.
   most functions are not asynchronous signal safe, so don't call
   them during interrupt. Timers MRT uses are second order so that
   they don't require rapid real-time processing */
static void 
alarm_interrupt (void)
{
    alarm_pending++;
}
#endif /* HAVE_LIBPTHREAD */

/* check to see if alarm is pending */
void
mrt_alarm (void)
{
#ifdef HAVE_LIBPTHREAD
    int sig;
    sigset_t set;

    sigemptyset (&set);
    sigaddset (&set, SIGALRM);

    /* wait on alarm */
    if (sigwait (&set, &sig) < 0) {
       trace (TR_TIMER, TIMER_MASTER->trace, "sigwait: %s\n", 
	      strerror (errno));
    }
    else {
	if (sig == SIGALRM)
	    Timer_Master_Fire ();
	else if (sig >= 0)
	    mrt_process_signal (sig);
    }
#else
    if (alarm_pending) {
	alarm_pending = 0;
	Timer_Master_Fire ();
    }
#endif /* HAVE_LIBPTHREAD */
}


/*
 * Allocate memory and initialize global TIMER_MASTER variable
 */
void
init_timer (trace_t *tr)
{
    sigset_t set;

    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 = tr;
    pthread_mutex_init (&TIMER_MASTER->mutex_lock, NULL);

#ifndef HAVE_LIBPTHREAD
#ifdef HAVE_SIGACTION /* POSIX actually */
    {
    	struct sigaction act;

        memset (&act, 0, sizeof (act));
        (void *)act.sa_handler = alarm_interrupt;
#ifdef SA_RESTART
        act.sa_flags = SA_RESTART; 
#endif
        sigaction (SIGALRM, &act, NULL);
    }
#else
#ifdef HAVE_SIGSET
    sigset (SIGALRM, alarm_interrupt);
#else
    signal (SIGALRM, alarm_interrupt);
#endif /* HAVE_SIGSET */
#endif /* POSIX signals */
#endif /* HAVE_LIBPTHREAD */

   /* This init routine must be called from the main thread
      that handles alarm interrupts */
    sigemptyset (&set);
    sigaddset (&set, SIGALRM);
#ifdef HAVE_LIBPTHREAD
    /* sigwait() will be used so that ALARM must be blocked 
       even in the main thread */
    pthread_sigmask (SIG_BLOCK, &set, NULL);
#else
    /* enables alarm interrupts */
    pthread_sigmask (SIG_UNBLOCK, &set, NULL);
#endif /* HAVE_LIBPTHREAD */
}


#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);
} 


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 */

   /* I expect that description will not be freed */
    event->description = timer->name;

    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);
    Delete (timer->name);
    Delete (timer);
}
