/* Copyright (C) 1999 Beau Kuiper

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"

/* Note that i may one day replace all this with the poll funcutions */

#define		MAXFDNUM	16	/* Set startup maximum file descriptor number */
   
/* Creates a new select structure */

extern int signumber;

SELECTER *select_new(void)
{
	SELECTER *new = mallocwrapper(sizeof(SELECTER));
	
	debuglog("select_new - starting new select structure");
	
	new->fdtable = mallocwrapper(sizeof(struct selectorobj *) * (MAXFDNUM + 1));
	memset(new->fdtable, 0, sizeof(struct selectorobj *) * (MAXFDNUM + 1));
	new->maxfds = MAXFDNUM;
	new->firstfd = -1;
	new->smax = 0;
	return(new);
}

/* The following adds a FD to the fdtable. If the table is too small,
   then increase it. Favours long running times */
   
void select_addfd(SELECTER *sel, int newport)
{
	debuglog("select_addfd - adding fd %d", newport);

	if (newport > sel->maxfds)
	{
		int temp = newport + 32;	/* give some breathing space */
		reallocwrapper(sizeof(struct selectorobj *) * (temp + 1), (void *)&(sel->fdtable));
		memset(&(sel->fdtable[sel->maxfds + 1]), 0, sizeof(struct selectorobj *) * (temp - sel->maxfds));
		sel->maxfds = temp;
	}
	if (sel->fdtable[newport] != NULL)
		errormsg("select.c: select_addfd - trying to add a file descriptor already in table.", __FILE__, __LINE__);
	else
	{
		struct selectorobj *temp = mallocwrapper(sizeof(struct selectorobj));
		temp->readsockopt = NULL;
		temp->writesockopt = NULL;
		temp->readdata = NULL;
		temp->writedata = NULL;
		temp->last = -1;
		temp->next = sel->firstfd;
		if (sel->firstfd != -1)
			sel->fdtable[sel->firstfd]->last = newport;
		sel->firstfd = newport;
		sel->fdtable[newport] = temp;
	}
}

void select_delfd(SELECTER *sel, int deadport)
{
	int result;
	struct selectorobj *temp = sel->fdtable[deadport];

	debuglog("select_delfd - removing fd %d", deadport);

	result = close(deadport);		/* make sure the port is closed :) */

/*	if (result == -1)
		errormsg(strerror(errno), __FILE__, __LINE__);
*/	
	select_takeread(sel, deadport);
	select_takewrite(sel, deadport);
	
	if (sel->firstfd == deadport)
	{
		sel->firstfd = temp->next;
		if (sel->firstfd != -1)
			sel->fdtable[sel->firstfd]->last = -1;
	}
	else
	{
		sel->fdtable[temp->last]->next = temp->next;
		if (temp->next != -1)
			sel->fdtable[temp->next]->last = temp->last;
	}

	freewrapper(temp);
	sel->fdtable[deadport] = NULL;
}
	
void select_addread(SELECTER *sel, int port, int (* proc)(SELECTER *, int, void *), void *dat)
{
	debuglog("select_addread - fd %d", port);

	sel->fdtable[port]->readsockopt = proc;
	sel->fdtable[port]->readdata = dat;
	
	sel->smax = MAXIMUM(sel->smax, port); 
}

void select_addwrite(SELECTER *sel, int port, int (* proc)(SELECTER *, int, void *), void *dat)
{
	debuglog("select_addwrite - fd %d", port);

	sel->fdtable[port]->writesockopt = proc;
	sel->fdtable[port]->writedata = dat;

	sel->smax = MAXIMUM(sel->smax, port);
}

void select_takeread(SELECTER *sel, int port)
{
	debuglog("select_takeread - fd %d", port);

	sel->fdtable[port]->readsockopt = NULL;
	sel->fdtable[port]->readdata = NULL;
	
/*	FD_CLR(port, &(sel->readset)); */
}

void select_takewrite(SELECTER *sel, int port)
{
	debuglog("select_takewrite - fd %d", port);

	sel->fdtable[port]->writesockopt = NULL;
	sel->fdtable[port]->writedata = NULL;
	
/*	FD_CLR(port, &(sel->writeset)); */
}

int select_do(SELECTER *sel, int *signum, int timeout)
{
	fd_set readset, writeset;
	struct timeval tv, *tv2;
	int result = 0;

	FD_ZERO(&readset);
	FD_ZERO(&writeset);

	debuglog("do_select - waiting on fd's");

	*signum = 0; signumber = 0;
	while (result == 0)
	{
		int pos = sel->firstfd;

		while(pos != -1)
		{
			struct selectorobj *temp = sel->fdtable[pos];
			
			if (temp->readsockopt)
				FD_SET(pos, &readset);
			if (temp->writesockopt) 
				FD_SET(pos, &writeset);
			
			pos = temp->next;			
		}

		if (timeout != -1)
		{
			tv.tv_sec = timeout;
			tv.tv_usec = 0;
			tv2 = &tv;
		}
		else
			tv2 = NULL;
		
		if (signumber != 0)
		{
			*signum = signumber;
			return(-1);
		}
		
		result = select(sel->smax + 1, &readset, &writeset, NULL, tv2);
		
		if (result == 0)
			return(0);
		else if (result == -1)
		{
			if (errno == EINTR)
			{
				*signum = signumber;
				
				return(-1);
			}
			else
			{
				errormsg(strerror(errno), __FILE__, __LINE__);
				return(-1);
			}
		}
		else
		{
			pos = sel->firstfd;
			result = 0;
			while((pos != -1) && (result == 0))
			{
				struct selectorobj *temp = sel->fdtable[pos];
				
				if (temp->readsockopt)
					if (FD_ISSET(pos, &readset))
						result = temp->readsockopt(sel, pos, temp->readdata);
				if ((temp->writesockopt) && (result == 0))
					if (FD_ISSET(pos, &writeset))
						result = temp->writesockopt(sel, pos, temp->writedata);
				
				FD_CLR(pos, &writeset);
				FD_CLR(pos, &readset);
				
				if (result == 2)
				{
					int pos2 = temp->next;	
					select_delfd(sel, pos);
					result = 0;
					pos = pos2;
				}
				else
					pos = temp->next;
						
			}
		}			 		
	}

	return(result);
}

void select_shutdown(SELECTER *sel)
{
	int count;
	
	for (count = 0; count < sel->maxfds; count++)
		if (sel->fdtable[count])
			select_delfd(sel, count);	

	freewrapper(sel->fdtable);
	freewrapper(sel);
}
