/*
 * Author:
 *         Kendy Kutzner <kutzner@ira.uka.de>
 *
 * (c) 2005 Research Group Dr. Fuhrmann, University of Karlsruhe, Germany
 * All rights reserved.
 *
 * $Id: callbacklist.cc,v 1.18 2005/11/29 16:14:48 kutzner Exp $
 *
 */

#include <map>
#include <sys/select.h>
#include <assert.h>
#include <math.h>
#include <errno.h>

#include "callback.h"
#include "connection.h"
#include "igor.h"
#include "callbacklist.h"
#include "debug.h"
#include "utils.h"

using namespace std;

/** \file
 * implement class members of callbacklist
 */

/**
 * create a new entry in the list
 * (internal function)
 */

struct cblentry *
callbacklist::makeentry(callbackbase *cb, double timeout){
	dbg.print(dbg.CBL, 8, "create cbl entry for '%s' at %f\n", cb->desc().c_str(), timeout);
	struct cblentry *e;
	struct timeval tv;
	struct timeval t2;
	e = new struct cblentry;
	assert(e);
	e->cb = cb;
	e->fd_r = e->fd_w = e->fd_e = 0;
	e->id = next_id();
	if (timeout > 0.0){
		timeout = add_jitter(timeout);
		if (gettimeofday(&tv, NULL)) PERRORANDEXIT;
		double2tv(&t2, timeout);
		timeradd(&tv, &t2, &tv);
		e->timeout = tv;
	} else {
		timerclear(&e->timeout);
	}
	_entries++;
	return e;
}

/**
 * register a callback.
 * there are four flavours:
 * reg, reg_e, reg_r, reg_w
 * which are: just a timer, timer and wait for connection exception, and timer+readable/writeable connection.
 * returns a handle, with which the registration can be undone.
 */

int
callbacklist::reg(callbackbase * cb, double timeout){
	dbg.print(dbg.CBL, 8, "register callback '%s' at %f\n", cb->desc().c_str(), timeout);
	struct cblentry *e;
	e = makeentry(cb, timeout);
	assert(e);
	list.insert(std::pair<int, struct cblentry*> (e->id, e));
	return e->id;
}

int
callbacklist::reg_r(callbackbase * cb, conn * c, double timeout){
	dbg.print(dbg.CBL, 8, "register read callback '%s' at %f\n", cb->desc().c_str(), timeout);
	struct cblentry *e;
	e = makeentry(cb, timeout);
	assert(e);
	e->fd_r = c->getfd();
	list.insert(std::pair<int, struct cblentry*> (e->id, e));
	return e->id;
}

	
int
callbacklist::reg_w(callbackbase * cb, conn * c, double timeout){
	dbg.print(dbg.CBL, 8, "register write callback '%s' at %f\n", cb->desc().c_str(), timeout);
	struct cblentry *e;
	e = makeentry(cb, timeout);
	assert(e);
	e->fd_w = c->getfd();
	list.insert(std::pair<int, struct cblentry*> (e->id, e));
	return e->id;
}

	
int
callbacklist::reg_e(callbackbase * cb, conn * c, double timeout){
	dbg.print(dbg.CBL, 8, "register exception callback '%s' at %f\n", cb->desc().c_str(), timeout);
	struct cblentry *e;
	e = makeentry(cb, timeout);
	assert(e);
	e->fd_e = c->getfd();
	list.insert(std::pair<int, struct cblentry*> (e->id, e));
	return e->id;
}

/**
 * undo a registration, cancel timers
 */

int
callbacklist::unreg(int handle){
	struct cblentry * e;
	e = (list.find(handle))->second;
	if (e){
		dbg.print(dbg.CBL, 8, "unregister callback '%s'\n", e->cb->desc().c_str());
		list.erase(handle);
		delete e;
		_entries--;
		return 0;
	} else {
		dbg.print(dbg.CBL, 5, "can't unregister callback with bogus handle %i\n", handle);
		return -1;
	}
}


/**
 * repeat a registration
 * after a callback fired, the registration is cleared
 * with this function, a new registration is not neccessary
 */

int
callbacklist::rereg(int handle){
	// just checking
	struct cblentry * e = (list.find(handle))->second;
	assert(e);
	assert(e->fd_e|e->fd_r|e->fd_w); // make sure that at least one fd is there
	dbg.print(dbg.CBL, 8, "reregister callback '%s' without timeout\n", e->cb->desc().c_str());
	// do the work
	return rereg(handle, 0.0);
}

/**
 * repeat a registration
 * after a callback fired, the registration is cleared
 * with this function, a new registration is not neccessary
 */

int 
callbacklist::rereg(int handle, double timeout){
	struct cblentry *e ;
	struct timeval tv;
	struct timeval t2;
	e = (list.find(handle))->second;
	assert(e);
	dbg.print(dbg.CBL, 8, "reregister callback '%s' at %f\n", e->cb->desc().c_str(), timeout);
	e->cb->restart();
	if (timeout > 0.0){
		timeout = add_jitter(timeout);
		if (gettimeofday(&tv, NULL)) PERRORANDEXIT;
		double2tv(&t2, timeout);
		timeradd(&tv, &t2, &(e->timeout));
	} else {
		timerclear(&e->timeout);
	}
	return handle;
}

/**
 * This rather large method compiles a list of all watched file descriptors and computes the earliest timer.
 * Then it gives both information to select() (this call is blocking!).
 * After select() returned, this method fires callbacks depending on the result of select()
 */

int
callbacklist::wait_and_execute(){
	// FIXME: split this large function in pieces
	dbg.print(dbg.CBL, 6, "enter wait_and_execute\n");
	fd_set sr, sw, se;
	int max_fd = 0;
	int fds_found = 0;
	int timers_found = 0;
	struct timeval tv;
	struct timeval mintv;
	struct timeval *ptv;
	struct cblentry *e;
	struct cblentry *e_to_execute = NULL;
	FD_ZERO(&sr);
	FD_ZERO(&sw);
	FD_ZERO(&se);
	gettimeofday(&tv, NULL);
	timerclear(&mintv);
	dbg.print(dbg.CBL, 6, "start iterating over cbs at %f\n", tv2double(&tv));
	for (list_t::const_iterator i = list.begin(); i!=list.end(); ++i){
		e = (*i).second;
		dbg.print(dbg.CBL, 7, "considering %s\n", e->cb->desc().c_str());
		if (e->fd_r){
			FD_SET(e->fd_r, &sr);
			max_fd = max(max_fd, e->fd_r);
			fds_found++;
			dbg.print(dbg.CBL, 8, "registered fd %i for reading (sum %i)\n", e->fd_r, fds_found);
		}
		if (e->fd_w){
			FD_SET(e->fd_w, &sw);
			max_fd = max(max_fd, e->fd_w);
			fds_found++;
			dbg.print(dbg.CBL, 8, "registered fd %i for writing (sum %i)\n", e->fd_w, fds_found);
		}
		if (e->fd_e){
			FD_SET(e->fd_e, &se);
			max_fd = max(max_fd, e->fd_e);
			fds_found++;
			dbg.print(dbg.CBL, 8, "registered fd %i for exception (sum %i)\n", e->fd_e, fds_found);
		}
		if (timerisset(&(e->timeout))){
			timers_found++;
			dbg.print(dbg.CBL, 8, "timer %i found\n", timers_found);
			if (!timerisset(&mintv)){ //very first
				mintv = e->timeout;
				e_to_execute = e;
				dbg.print(dbg.CBL, 8, "set mintv to %f\n", tv2double(&mintv));
			} else if (timercmp(&mintv, &e->timeout, >)){
				mintv = e->timeout;
				e_to_execute = e;
				dbg.print(dbg.CBL, 8, "update mintv to %f\n", tv2double(&mintv));
			}
		}
	}
	fd_count_stats.add(fds_found);
	timer_count_stats.add(timers_found);
	dbg.print(dbg.CBL, 6, "end iterating over cbs, %i fds and %i timers found\n", fds_found, timers_found);
	if ((0 == timers_found) && (0 == fds_found)){
		dbg.print(dbg.CBL, 3, "nothing found in callbacklist, returning 0\n");
		return 0;
	}
	dbg.print(dbg.CBL, 8, "before timersub min %li %li tv %li %li\n", mintv.tv_sec, mintv.tv_usec, tv.tv_sec, tv.tv_usec);
	timersub(&mintv, &tv, &tv);
	dbg.print(dbg.CBL, 8, "after  timersub min %li %li tv %li %li\n", mintv.tv_sec, mintv.tv_usec, tv.tv_sec, tv.tv_usec);
	ptv = &tv;
	if (timerisset(&mintv) && tv2double(ptv)<0) { // the earliest timer is overdue
		dbg.print(dbg.CBL, 4, "calling overdue callback '%s' %fs to late\n", e_to_execute->cb->desc().c_str(), -tv2double(ptv));
		e_to_execute->cb->go(e_to_execute->id);
		dbg.print(dbg.CBL, 6, "exit wait_and_execute (overdue timer)\n");
		return 1;
	}
	if (timerisset(&mintv)){
		timer_val_stats.add(tv2double(ptv));
		dbg.print(dbg.CBL, 6, "set timeout to %fs\n", tv2double(ptv));
		assert(tv2double(ptv)>=0);
	} else {
		ptv = NULL;
		dbg.print(dbg.CBL, 6, "set timeout to NULL\n");
	}
	dbg.print(dbg.CBL, 6, "calling select with maxfd+1 %i\n", max_fd+1);
	if (ptv)
		dbg.print(dbg.CBL, 6, "... and timeout s %li u %li\n", ptv->tv_sec, ptv->tv_usec);
	// now we call select. This should be the only blocking call ever.
	int selectret = select((max_fd + 1), &sr, &sw, &se, ptv);
	// wow, select returned!
	dbg.print(dbg.CBL, 6, "select returned %i\n", selectret);
	if(selectret < 0) {
		dbg.print(dbg.CBL, 4, "select: %s\n", strerror(errno));
		// exit(-1);
		return -1;
	}
	if (selectret == 0) { //timeout; call the earliest
		dbg.print(dbg.CBL, 6, "calling callback '%s'\n", e_to_execute->cb->desc().c_str());
		e_to_execute->cb->go(e_to_execute->id);
		dbg.print(dbg.CBL, 6, "exit wait_and_execute (timer)\n");
		return 1;
	}
	// something to do on at least one fd
	e_to_execute = NULL;
	for (list_t::const_iterator i = list.begin(); i!=list.end(); ++i){
		e = (*i).second;
		if (e->fd_r && FD_ISSET(e->fd_r, &sr)){
			e_to_execute = e;
			break;
		}
		if (e->fd_w && FD_ISSET(e->fd_w, &sw)){
			e_to_execute = e;
			break;
		}
		if (e->fd_e && FD_ISSET(e->fd_e, &se)){
			e_to_execute = e;
			break;
		}
	}
	assert(e_to_execute);
	dbg.print(dbg.CBL, 6, "calling callback '%s'\n", e_to_execute->cb->desc().c_str());
	e_to_execute->cb->go(e_to_execute->id);
	dbg.print(dbg.CBL, 6, "exit wait_and_execute (fd)\n");
		
	return 1;
}

int
callbacklist::next_id(){
	return _next_id++;
}

callbacklist::callbacklist(){
	_next_id = 1;
	_entries = 0;
}

callbacklist::~callbacklist(){
	assert(0==_entries);
	dbg.print(dbg.CBL, 5, "CBL statistics: average fds found %f, average timers found %f, average mintimer val %f\n", fd_count_stats.get(), timer_count_stats.get(), timer_val_stats.get());
}

double
callbacklist::add_jitter(double timeout){
	double jitter = (double)random() / (double)RAND_MAX + .5; // produce a number in [.5,1.5]
	double newtimeout = timeout * jitter;
	dbg.print(dbg.CBL, 7, "modify timeout %f with jitter %f to %f\n", timeout, jitter, newtimeout);
	return newtimeout;
}

