/* 
 * Timer Interface
 *
 * timer.c
 *
 * Implementation of the Timer programming interface simulating multiple timers.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>

#include "pqueue.h"


/*
 * Internal private global variables
 */

// the next timer id
static int Next_id;

// timers priority queue
static pqueue_t *Timer_list;

// mutex used to make sure all functions work in a synchronized way
static pthread_mutex_t Timer_mutex = PTHREAD_MUTEX_INITIALIZER;

// thread used to run expired timers
static pthread_t Timer_manager;


/*
 * Internal private auxiliary functions
 */


static void *timer_management(void *arg) {
	while (1) { /* busy waiting */		
		if (timer_next() == 0) {
			timer_execute_next();
		}
	}
}


/*
 * Timer API implementation
 */


int timer_set(int timeout, timer_handler_t handler, void *context) {
	pthread_mutex_lock(&Timer_mutex);

	if (Next_id == 0) { /* first timer, must initialize data structures */
		Timer_list = pqueue_create(1024);
		pthread_create(&Timer_manager, NULL, timer_management, NULL);
	}
		
	timer_t *timer = (timer_t*) malloc(sizeof(timer_t));
	timer->id = Next_id++;
	timer->handler = handler;
	timer->context = context;
	pqueue_enqueue(Timer_list, timer, time(NULL) + timeout);
	
	pthread_mutex_unlock(&Timer_mutex);
	
	return timer->id;
}


int timer_cancel(int id) {
	int res = -1;

	pthread_mutex_lock(&Timer_mutex);
	
	pqueue_decrease_prio(Timer_list, id, -1);
	timer_t *timer = pqueue_dequeue(Timer_list);

	if (timer != NULL) {
		/* memory leak: context is ignored */
		free(timer);
		res = 0;
	}
	
	pthread_mutex_unlock(&Timer_mutex);
	
	return res;
}


int timer_next() {
	pthread_mutex_lock(&Timer_mutex);

	int timeout = pqueue_minimum(Timer_list);
	int expires = timeout - time(NULL);
	
	pthread_mutex_unlock(&Timer_mutex);

	return ((timeout > 0) ? ((expires < 0) ? 0 : expires) : -1);
}


void timer_execute_next() {
	pthread_mutex_lock(&Timer_mutex);
	
	timer_t *timer = pqueue_dequeue(Timer_list);
	
	pthread_mutex_unlock(&Timer_mutex);
	
	if (timer != NULL) {
		timer->handler(timer->context);
		free(timer);
	}
}
