#ifndef lint
static char *RCSid = "$Header: /usr/brule/guest/empire/empire/emprcs/lib/gen/io.c,v 2.3 1995/10/11 02:55:05 empire Exp $";
#endif

/*
 * io.c
 *
 * Arrange for input and output on a file descriptor
 * to be queued.  Provide main loop -- a mechanism for
 * blocking across all registered file descriptors, and
 * reading or writing when appropriate.
 *
 */

#ifdef aix
#include <fcntl.h>
#endif /* aix */
#include <errno.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <time.h>

#include "misc.h"
#include "bit.h"
#include "queue.h"
#include "ioqueue.h"
#include "io_mask.h"
#include "io.h"

#include "lwp.h"

extern struct player *player;	/* XXX */

static	struct iop **io_list;
static	struct io_mask *iom;
static	bit_fdmask newoutput;

struct iop {
	int fd;
	struct ioqueue *input;
	struct ioqueue *output;
	int flags;
	s_char *assoc;
	int bufsize;
	int (*notify)();
};

extern	int errno;

io_init()
{
	iom = iom_create(IO_READ|IO_WRITE);
	io_list = (struct iop **) calloc(getfdtablesize(), sizeof(*io_list));
	newoutput = bit_newfdmask();
}

struct iop *
io_open(fd, flags, bufsize, notify, assoc)
	int	fd;
	int	flags;
	int	bufsize;
	int	(*notify)();
	s_char	*assoc;
{
	struct	iop *iop;

	if (io_list[fd] != 0) {
		/* already exists */
		return 0;
	}
	flags = flags & (IO_READ|IO_WRITE|IO_NBLOCK|IO_NEWSOCK);
	if ((flags & (IO_READ|IO_WRITE)) == 0)
		return 0;
	iop = (struct iop *) malloc(sizeof(struct iop));
	iop->fd = fd;
	iop->input = 0;
	iop->output = 0;
	iop->flags = 0;
	iop->bufsize = bufsize;
	if ((flags & IO_READ) && (flags & IO_NEWSOCK) == 0)
		iop->input = ioq_create(bufsize);
	if ((flags & IO_WRITE) && (flags & IO_NEWSOCK) == 0)
		iop->output = ioq_create(bufsize);
	if (flags & IO_NBLOCK)
		io_noblocking(iop, 1);
	iop->flags = flags;
	iop->assoc = assoc;
	iop->notify = notify;
	io_list[fd] = iop;
	iom_set(iom, flags, fd);
	return iop;
}

io_close(iop)
	struct	iop *iop;
{
	
	if (iop->input != 0) 
		ioq_destroy(iop->input);
	if (iop->output != 0) 
		ioq_destroy(iop->output);
	iom_clear(iom, iop->flags, iop->fd);
	BIT_CLRB(iop->fd, newoutput);
	io_list[iop->fd] = 0;
	(void) close(iop->fd);
	free((s_char *)iop);
}

int
io_input(iop, waitforinput)
	struct	iop *iop;
	int	waitforinput;
{
	s_char	buf[IO_BUFSIZE];
	int	cc;

	if ((iop->flags & IO_READ) == 0)
		return -1;
	if (iop->flags & IO_ERROR)
		return -1;
	if (waitforinput)
		lwpSleepFd(iop->fd, LWP_FD_READ);
	cc = read(iop->fd, buf, sizeof(buf));
	if (cc < 0) {
		if (errno == EWOULDBLOCK)
			return 0;
		iop->flags |= IO_ERROR;
		iom_clear(iom, IO_READ, iop->fd);
		return -1;
	}
	if (cc == 0) {
		iop->flags |= IO_EOF;
		return 0;
	}
	ioq_append(iop->input, buf, cc);
	return cc;
}

int
io_inputwaiting(iop)
	struct	iop *iop;
{
	return ioq_qsize(iop->input);
}

int
io_outputwaiting(iop)
	struct	iop *iop;
{
	return ioq_qsize(iop->output);
}

int
io_output(iop, waitforoutput)
	struct	iop *iop;
	int	waitforoutput;
{
	struct	iovec iov[16];
	int	cc;
	int	n;
	int	remain;

	if (!io_outputwaiting(iop))
		return 0;
 	BIT_CLRB(iop->fd, newoutput);
	if ((iop->flags & IO_WRITE) == 0)
		return -1;
	if (iop->flags & IO_ERROR)
		return -1;
	if (ioq_qsize(iop->output) == 0)
		return 0;
	n = ioq_makeiov(iop->output, iov, IO_BUFSIZE);
	if (n <= 0) {
		iom_clear(iom, IO_WRITE, iop->fd);
		return 0;
	}

	if (waitforoutput)
	   lwpSleepFd(iop->fd, LWP_FD_WRITE); 


	cc = writev(iop->fd, iov, n);

	if (cc < 0) {
		if (errno == EWOULDBLOCK) {
			remain = ioq_qsize(iop->output);
			if (remain > 0)
				iom_set(iom, IO_WRITE, iop->fd);
			return remain;
		}
		iop->flags |= IO_ERROR;
		iom_clear(iom, IO_WRITE, iop->fd);
		return -1;
	}
#ifndef	hpux
	if (cc == 0) {
		iop->flags |= IO_EOF;
		return 0;
	}
#else
	if (cc == 0) {
		remain = ioq_qsize(iop->output);
		if (remain > 0)
			iom_set(iom, IO_WRITE, iop->fd);
		return remain;
	}
#endif	/* hpux */
	ioq_dequeue(iop->output, cc);
	remain = ioq_qsize(iop->output);
	if (remain == 0) {
		iom_clear(iom, IO_WRITE, iop->fd);
 	} else {
 		iom_set(iom, IO_WRITE, iop->fd);
	}
	return cc;
}

int
io_select(tv)
	struct	timeval *tv;
{
	bit_fdmask readmask;
	bit_fdmask writemask;
	int	n;
	int	nfds;
	int	fd;
	struct iop *iop;

	iom_getmask(iom, &nfds, &readmask, &writemask);
	n = select(nfds + 1, readmask, writemask, 0, tv);
	if (n <= 0) {
		if (errno == EINTR)
			return 0;
		return -1;
	}
	while ((fd = bit_fd(readmask)) >= 0) {
		iop = io_list[fd];
		if ((iop->flags & IO_NEWSOCK) == 0)
			(void) io_input(iop, IO_NOWAIT);
		if (iop->notify != 0)
			iop->notify(iop, IO_READ, iop->assoc);
		BIT_CLRB(fd, readmask);
	}
	while ((fd = bit_fd(writemask)) >= 0) {
		iop = io_list[fd];
		if (io_output(iop, IO_NOWAIT) < 0 && iop->notify != 0)
			iop->notify(iop, IO_WRITE, iop->assoc);
		BIT_CLRB(fd, writemask);
	}
	return n;
}

io_flush(doWait)
	int	doWait;
{
	int	fd;
	struct	iop *iop;

	while ((fd = bit_fd(newoutput)) >= 0) {
		iop = io_list[fd];
		if (io_output(iop, doWait) < 0 && iop->notify != 0)
			iop->notify(iop, IO_WRITE, iop->assoc);
	}
}

int
io_peek(iop, buf, nbytes)
	struct	iop *iop;
	s_char	*buf;
	int	nbytes;
{
	if ((iop->flags & IO_READ) == 0)
		return -1;
	return ioq_peek(iop->input, buf, nbytes);
}

int
io_read(iop, buf, nbytes)
	struct	iop *iop;
	s_char	*buf;
	int	nbytes;
{
	int	cc;

	if ((iop->flags & IO_READ) == 0)
		return -1;
	cc = ioq_peek(iop->input, buf, nbytes);
	if (cc > 0)
		ioq_dequeue(iop->input, cc);
	return cc;
}

int
io_write(iop, buf, nbytes, doWait)
	struct	iop *iop;
	s_char	*buf;
	int	nbytes;
	int	doWait;
{
	int	len;

	if ((iop->flags & IO_WRITE) == 0)
		return -1;
	ioq_append(iop->output, buf, nbytes);
	BIT_SETB(iop->fd, newoutput);
	len = ioq_qsize(iop->output);
	if (len > iop->bufsize) {
		if (doWait) {
			io_output_all(iop);
		} else {
			/* only try a write every BUFSIZE characters */
			if (((len-nbytes) % iop->bufsize) <
			    (len % iop->bufsize))
				io_output(iop, 0);
		}
	}
	return nbytes;
}

int
io_output_all(iop)
	struct	iop *iop;
{
	int	n;

	while ((n = io_output(iop, IO_NOWAIT)) > 0)
		lwpSleepFd(iop->fd, LWP_FD_WRITE);
	return n;
}

int
io_gets(iop, buf, nbytes)
	struct	iop *iop;
	s_char	*buf;
	int	nbytes;
{
	if ((iop->flags & IO_READ) == 0)
		return -1;
	return ioq_gets(iop->input, buf, nbytes);
}

int
io_puts(iop, buf)
	struct	iop *iop;
	s_char	*buf;
{
	if ((iop->flags & IO_WRITE) == 0)
		return -1;
	BIT_SETB(iop->fd, newoutput);
	return ioq_puts(iop->output, buf);
}

int
io_shutdown(iop, flags)
	struct	iop *iop;
	int	flags;
{
	flags &= (IO_READ|IO_WRITE);
	if ((iop->flags & flags) != flags)
		return -1;
	if (flags & IO_READ) {
		shutdown(iop->fd, 0);
		ioq_drain(iop->input);
	}
	if (flags & IO_WRITE) {
		shutdown(iop->fd, 1);
		ioq_drain(iop->output);
	}
	return 0;
}

int
io_noblocking(iop, value)
	struct	iop *iop;
	int	value;
{
	int	flags;

	flags = fcntl(iop->fd, F_GETFL, 0);
	if (flags < 0)
		return -1;
	if (value == 0)
		flags &= ~FNDELAY;
	else
		flags |= FNDELAY;
	if (fcntl(iop->fd, F_SETFL, flags) < 0)
		return -1;
	if (value == 0)
		iop->flags &= ~IO_NBLOCK;
	else
		iop->flags |= IO_NBLOCK;
	return 0;
}

io_conn(iop)
	struct	iop *iop;
{
	return (iop->flags & IO_CONN);
}

io_error(iop)
	struct	iop *iop;
{
	return (iop->flags & IO_ERROR);
}

io_eof(iop)
	struct	iop *iop;
{
	return (iop->flags & IO_EOF);
}

io_fileno(iop)
	struct	iop *iop;
{
	return iop->fd;
}

struct iop *
io_iopfromfd(fd)
	int	fd;
{
	return io_list[fd];
}






