/* select.c, liboop, copyright 1999 Dan Egnor

   This is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License, version 2.1 or later.
   See the file COPYING for details. */

#include "oop.h"

#include <assert.h>

struct select_set {
	fd_set rfd,wfd,xfd;
};

struct oop_adapter_select {
	oop_source *source;
	struct select_set watch,active;
	struct timeval timeout;
	int num_fd,do_timeout,is_active,num_fd_active;
	oop_call_select *call;
	void *data;
};

static oop_call_fd on_fd;
static oop_call_time on_timeout,on_collect;

oop_adapter_select *oop_select_new(
	oop_source *source, 
	oop_call_select *call,void *data)
{
	oop_adapter_select *s = oop_malloc(sizeof(*s));
	if (NULL == s) return s;
	s->source = source;
	FD_ZERO(&s->watch.rfd);
	FD_ZERO(&s->watch.wfd);
	FD_ZERO(&s->watch.xfd);
	FD_ZERO(&s->active.rfd);
	FD_ZERO(&s->active.wfd);
	FD_ZERO(&s->active.xfd);
	s->num_fd = 0;
	s->num_fd_active = 0;
	s->do_timeout = 0;
	s->is_active = 0;
	s->call = call;
	s->data = data;
	return s;
}

static void *activate(oop_adapter_select *s) {
	if (!s->is_active) {
		s->is_active = 1;
		s->source->on_time(s->source,OOP_TIME_NOW,on_collect,s);
	}
	return OOP_CONTINUE;
}

static void deactivate(oop_adapter_select *s) {
	if (s->is_active) {
		s->source->cancel_time(s->source,OOP_TIME_NOW,on_collect,s);
		s->is_active = 0;
		s->num_fd_active = 0;
		FD_ZERO(&s->active.rfd);
		FD_ZERO(&s->active.wfd);
		FD_ZERO(&s->active.xfd);
	}
}

void oop_select_set(
	oop_adapter_select *s,int num_fd,
	fd_set *rfd,fd_set *wfd,fd_set *xfd,struct timeval *timeout)
{
	int fd;
	for (fd = 0; fd < num_fd || fd < s->num_fd; ++fd) {
		int rfd_set = fd < num_fd && FD_ISSET(fd,rfd);
		int wfd_set = fd < num_fd && FD_ISSET(fd,wfd);
		int xfd_set = fd < num_fd && FD_ISSET(fd,xfd);
		int w_rfd_set = fd < s->num_fd && FD_ISSET(fd,&s->watch.rfd);
		int w_wfd_set = fd < s->num_fd && FD_ISSET(fd,&s->watch.wfd);
		int w_xfd_set = fd < s->num_fd && FD_ISSET(fd,&s->watch.xfd);

		if (rfd_set && !w_rfd_set) {
			s->source->on_fd(s->source,fd,OOP_READ,on_fd,s);
			FD_SET(fd,&s->watch.rfd);
		}

		if (!rfd_set && w_rfd_set) {
			s->source->cancel_fd(s->source,fd,OOP_READ);
			FD_CLR(fd,&s->watch.rfd);
		}

		if (wfd_set && !w_wfd_set) {
			s->source->on_fd(s->source,fd,OOP_WRITE,on_fd,s);
			FD_SET(fd,&s->watch.wfd);
		}

		if (!wfd_set && w_wfd_set) {
			s->source->cancel_fd(s->source,fd,OOP_WRITE);
			FD_CLR(fd,&s->watch.wfd);
		}

		if (xfd_set && !w_xfd_set) {
			s->source->on_fd(s->source,fd,OOP_EXCEPTION,on_fd,s);
			FD_SET(fd,&s->watch.xfd);
		}

		if (!xfd_set && w_xfd_set) {
			s->source->cancel_fd(s->source,fd,OOP_EXCEPTION);
			FD_CLR(fd,&s->watch.xfd);
		}
	}

	s->num_fd = num_fd;

	if (s->do_timeout) {
		s->source->cancel_time(s->source,s->timeout,on_timeout,s);
		s->do_timeout = 0;
	}

	if (NULL != timeout) {
		gettimeofday(&s->timeout,NULL);
		s->timeout.tv_sec += timeout->tv_sec;
		s->timeout.tv_usec += timeout->tv_usec;
		while (s->timeout.tv_usec >= 1000000) {
			++s->timeout.tv_sec;
			s->timeout.tv_usec -= 1000000;
		}
		s->do_timeout = 1;
		s->source->on_time(s->source,s->timeout,on_timeout,s);
	}

	deactivate(s);
}

void oop_select_delete(oop_adapter_select *s) {
	fd_set fd;
	FD_ZERO(&fd);
	oop_select_set(s,0,&fd,&fd,&fd,NULL);
	oop_free(s);
}

static void set_fd(int fd,fd_set *fds,int *num) {
	if (!FD_ISSET(fd,fds)) {
		FD_SET(fd,fds);
		if (fd >= *num) *num = fd + 1;
	}
}

static void *on_fd(oop_source *source,int fd,oop_event event,void *data) {
	oop_adapter_select *s = (oop_adapter_select *) data;
	switch (event) {
	case OOP_READ:
		assert(FD_ISSET(fd,&s->watch.rfd));
		set_fd(fd,&s->active.rfd,&s->num_fd_active);
		break;
	case OOP_WRITE:
		assert(FD_ISSET(fd,&s->watch.wfd));
		set_fd(fd,&s->active.wfd,&s->num_fd_active);
		break;
	case OOP_EXCEPTION:
		assert(FD_ISSET(fd,&s->watch.xfd));
		set_fd(fd,&s->active.xfd,&s->num_fd_active);
		break;
	default:
		assert(0);
		break;
	}
	return activate(s);
}

static void *on_timeout(oop_source *source,struct timeval when,void *data) {
	oop_adapter_select *s = (oop_adapter_select *) data;
	assert(s->do_timeout);
	return activate(s);
}

static void *on_collect(oop_source *source,struct timeval when,void *data) {
	oop_adapter_select *s = (oop_adapter_select *) data;
	struct select_set set = s->active;
	int num = s->num_fd_active;
	struct timeval now;
	gettimeofday(&now,NULL);
	deactivate(s);
	return s->call(s,num,&set.rfd,&set.wfd,&set.xfd,now,s->data);
}


syntax highlighted by Code2HTML, v. 0.9.1