/*
 * newio.c: This is some handy stuff to deal with file descriptors in a way
 * much like stdio's FILE pointers 
 *
 * IMPORTANT NOTE:  If you use the routines here-in, you shouldn't switch to
 * using normal reads() on the descriptors cause that will cause bad things
 * to happen.  If using any of these routines, use them all 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "irc.h"
#include "ircaux.h"
#include "newio.h"

#ifdef ISC22
#include <sys/bsdtypes.h>
#endif /* ISC22 */

#include "irc_std.h"

#define IO_BUFFER_SIZE 512

#ifdef HAVE_SYSCONF
#define IO_ARRAYLEN sysconf(_SC_OPEN_MAX)
#else
#ifdef FD_SETSIZE
#define IO_ARRAYLEN FDSETSIZE
#else
#define IO_ARRAYLEN NFDBITS
#endif
#endif

typedef struct myio_struct
{
	char buffer[IO_BUFFER_SIZE + 1];
	unsigned int read_pos, write_pos;
	unsigned misc_flags;
}
MyIO;

#define IO_SOCKET 1

static struct timeval right_away =
{0L, 0L};
static MyIO **io_rec = NULL;

static struct timeval dgets_timer =
{0, 0};
static struct timeval *timer = NULL;
int dgets_errno = 0;

static void init_io (void);

/*
 * dgets_timeout: does what you'd expect.  Sets a timeout in seconds for
 * dgets to read a line.  if second is -1, then make it a poll.
 */
extern time_t 
dgets_timeout (int sec)
{
	time_t old_timeout = dgets_timer.tv_sec;

	if (sec)
	{
		dgets_timer.tv_sec = (sec == -1) ? 0 : sec;
		dgets_timer.tv_usec = 0;
		timer = &dgets_timer;
	}
	else
		timer = NULL;
	return old_timeout;
}

static void 
init_io (void)
{
	static int first = 1;

	if (first)
	{
		int c, max_fd = IO_ARRAYLEN;

		io_rec = (MyIO **) new_malloc (sizeof (MyIO *) * max_fd);
		for (c = 0; c < max_fd; c++)
			io_rec[c] = NULL;
		(void) dgets_timeout (-1);
		first = 0;
	}
}

/*
 * dgets: works much like fgets except on descriptor rather than file
 * pointers.  Returns the number of character read in.  Returns 0 on EOF and
 * -1 on a timeout (see dgets_timeout()) 
 */
int 
dgets (char *str, int len, int des, char *specials)
{
#if 1
	int cnt = 0, c;
	fd_set rd;
	int BufferEmpty;

	init_io ();
	if (io_rec[des] == NULL)
	{
		io_rec[des] = (MyIO *) new_malloc (sizeof (MyIO));
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;
		io_rec[des]->misc_flags = 0;
	}

	while (1)
	{
		if ((BufferEmpty = (io_rec[des]->read_pos == io_rec[des]->write_pos)))
		{
			if (BufferEmpty)
			{
				io_rec[des]->read_pos = 0;
				io_rec[des]->write_pos = 0;
			}
			FD_ZERO (&rd);
			FD_SET (des, &rd);
			switch (select (des + 1, &rd, NULL, NULL, timer))
			{
			case 0:
				{
					str[cnt] = (char) 0;
					dgets_errno = 0;
					return (-1);
				}
			default:
				{
					c = read (des, io_rec[des]->buffer +
						  io_rec[des]->write_pos,
						  IO_BUFFER_SIZE - io_rec[des]->write_pos);

					if (c <= 0)
					{
						if (c == 0)
							dgets_errno = -1;
						else
							dgets_errno = errno;
						return 0;
					}
					io_rec[des]->write_pos += c;
					break;
				}
			}
		}
		while (io_rec[des]->read_pos < io_rec[des]->write_pos)
		{
			if (((str[cnt++] = io_rec[des]->buffer[(io_rec[des]->read_pos)++])
			     == '\n') || (cnt == len))
			{
				dgets_errno = 0;
				str[cnt] = (char) 0;
				return (cnt);
			}
		}
	}
#else
	int cnt = 0, c, s;
	fd_set rd;
	int BufferEmpty;

	init_io ();
	if (io_rec[des] == NULL)
	{
		io_rec[des] = (MyIO *) new_malloc (sizeof (MyIO));
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;
		io_rec[des]->misc_flags = 0;
	}

	if (io_rec[des]->read_pos == io_rec[des]->write_pos)
	{
		io_rec[des]->read_pos = 0;
		io_rec[des]->write_pos = 0;

		FD_ZERO (&rd);
		FD_SET (des, &rd);
		s = select (des + 1, &rd, 0, 0, timer);
		if (s <= 0)
		{
			strcpy (str, empty_string);
			dgets_errno = (s == 0) ? -1 : errno;
			return 0;
		}
		else
		{
			c = read (des, io_rec[des]->buffer +
				  io_rec[des]->write_pos,
				  IO_BUFFER_SIZE - io_rec[des]->write_pos);

			if (c <= 0)
			{
				dgets_errno = (s == 0) ? -1 : errno;
				return 0;
			}
			io_rec[des]->write_pos += c;
		}
	}

	while (io_rec[des]->read_pos < io_rec[des]->write_pos)
	{
		str[cnt] = io_rec[des]->buffer[io_rec[des]->read_pos];
		io_rec[des]->read_pos++;
		if (str[cnt] == '\n')
			break;
		cnt++;
	}

	/* Add on newline if its not there. */
	if (str[cnt] != '\n')
		str[++cnt] = '\n';
	dgets_errno = 0;
	str[++cnt] = 0;
	return cnt - 1;
#endif
}

/*
 * new_select: works just like select(), execpt I trimmed out the excess
 * parameters I didn't need.  
 */
int 
new_select (fd_set * rd, fd_set * wd, struct timeval *timeout)
{
	int i, set = 0;
	fd_set new;
	struct timeval thetimeout, *newtimeout = &thetimeout;
	int max_fd = -1;
	static int num_fd = 0;

	if (!num_fd)
	{
		num_fd = IO_ARRAYLEN;	/* why do it a zillion times? */
		if (num_fd > FD_SETSIZE)
			num_fd = FD_SETSIZE;
	}

	if (timeout)
		memcpy (newtimeout, timeout, sizeof (struct timeval));
	else
		newtimeout = NULL;

	init_io ();
	FD_ZERO (&new);
	for (i = 0; i < num_fd; i++)
	{
		if (i > max_fd && ((rd && FD_ISSET (i, rd)) || (wd && FD_ISSET (i, wd))))
			max_fd = i;
		if (io_rec[i])
		{
			if (io_rec[i]->read_pos < io_rec[i]->write_pos)
			{
				FD_SET (i, &new);
				set = 1;
			}
		}
	}
	if (set)
	{
		set = 0;
		if (select (max_fd + 1, rd, wd, NULL, &right_away) <= 0)
			FD_ZERO (rd);
		for (i = 0; i < num_fd; i++)
		{
			if ((FD_ISSET (i, rd)) || (FD_ISSET (i, &new)))
			{
				set++;
				FD_SET (i, rd);
			}
			else
				FD_CLR (i, rd);
		}
		return (set);
	}
	return (select (max_fd + 1, rd, wd, NULL, newtimeout));
}

/* new_close: works just like close */
void 
new_close (int des)
{
	if (des < 0 || !io_rec)
		return;
	new_free ((char **) &(io_rec[des]));
	close (des);
}

/* set's socket options */
extern void 
set_socket_options (int s)
{
	int opt = 1;
	int optlen = sizeof (opt);
#ifndef NO_STRUCT_LINGER
	struct linger lin =
	{0};
	lin.l_onoff = lin.l_linger = 0;
	(void) setsockopt (s, SOL_SOCKET, SO_LINGER, (void *) &lin, optlen);
	opt = 1;
#endif /* NO_STRUCT_LINGER */
	(void) setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, optlen);
	opt = 1;
	(void) setsockopt (s, SOL_SOCKET, SO_KEEPALIVE, (void *) &opt, optlen);
}
