#ifndef lint
static char *RCSid = "$Header: /usr/brule/guest/empire/empire/emprcs/lib/gen/ioqueue.c,v 2.1 1995/07/27 22:11:50 empire Exp $";
#endif

/*
 * ioqueue.c
 *
 * Read and write onto io queues.  Note that
 * the io queues don't actually do any writing;
 * that is left for a higher level.
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include "misc.h"
#include "queue.h"
#include "ioqueue.h"

static int ioqtocbuf();
static int ioqtoiov();
static int ioqtobuf();
static int appendcc();
static int removecc();

extern	s_char *malloc();

struct ioqueue *
ioq_create(size)
	int	size;
{
	struct	ioqueue *ioq;

	ioq = (struct ioqueue *) malloc(sizeof(struct ioqueue));
	initque(&ioq->list.queue);
	ioq->list.nbytes = 0;
	ioq->list.offset = 0;
	ioq->list.size = 0;
	ioq->list.data = 0;
	ioq->bufsize = size;
	ioq->cc = 0;
	return ioq;
}

ioq_destroy(ioq)
	struct	ioqueue *ioq;
{
#if !defined(aix) && !defined(NeXT) 
/* ioq_drain doesn't work under aix or NeXT... dunno why --ts */
	ioq_drain(ioq); 
#endif /* aix */
	free((s_char *)ioq);
}

ioq_drain(ioq)
	struct ioqueue *ioq;
{
	struct qelem *qp;
	struct io *io;

	while ((qp = ioq->list.queue.q_forw) != &ioq->list.queue){
		io = (struct io *) qp;
		(void) remque(&io->queue);
		(void) free((s_char *)io->data);
		(void) free((s_char *)io);
	}
	
	ioq->cc = 0;
}

/*
 * copy batch of pointers into the passed
 * iovec, but don't actually dequeue the data.
 * return # of iovec initialized.
 */
int
ioq_makeiov(ioq, iov, cc)
	struct	ioqueue *ioq;
	struct	iovec *iov;
	int	cc;
{
	if (ioq->cc <= 0)
		return 0;
	return ioqtoiov(ioq, iov, cc);
}

/*
 * Copy the specified number of characters into the buffer
 * provided, without actually dequeueing the data.  Return
 * number of bytes actually found.
 */
int
ioq_peek(ioq, buf, cc)
	struct	ioqueue *ioq;
	s_char	*buf;
	int	cc;
{
	return ioqtobuf(ioq, buf, cc);
}

int
ioq_dequeue(ioq, cc)
	struct	ioqueue *ioq;
	int	cc;
{
	return removecc(ioq, cc);
}

ioq_append(ioq, buf, cc)
	struct	ioqueue *ioq;
	s_char	*buf;
	int	cc;
{
	appendcc(ioq, buf, cc);
}

int
ioq_qsize(ioq)
	struct	ioqueue *ioq;
{
	return ioq->cc;
}

/*
 * read a line of text up to (but not including)
 * the newline.  return -1 and read nothing if
 * no input is available
 */
int
ioq_gets(ioq, buf, cc)
	struct	ioqueue *ioq;
	s_char	*buf;
	int	cc;
{
	int	nbytes;
	int	actual;

	nbytes = ioqtocbuf(ioq, buf, cc - 1, '\n');
	if (nbytes >= 0) {
		actual = nbytes;
		if (actual > cc - 1)
			actual = cc - 1;
		buf[actual] = '\0';
		/* remove the newline too */
		removecc(ioq, nbytes + 1);
	}
	return nbytes;
}

int
ioq_puts(ioq, buf)
	struct	ioqueue *ioq;
	s_char	*buf;
{
	return appendcc(ioq, buf, strlen(buf));
}

/*
 * all the rest are local to this module
 */


/*
 * copy cc bytes from ioq to buf.
 * this routine doesn't free memory; this is
 * left for a higher level.
 */
static
int
ioqtobuf(ioq, buf, cc)
	struct ioqueue *ioq;
	s_char	*buf;
	int	cc;
{
	struct io *io;
	struct qelem *qp;
	struct qelem *head;
	register int nbytes;
	register int nleft;
	register s_char *offset;

	nleft = cc;
	offset = buf;
	head = &ioq->list.queue;
	for (qp = head->q_forw; qp != head && nleft > 0; qp = qp->q_forw) {
		io = (struct io *) qp;
		if ((nbytes = io->nbytes - io->offset) < 0) {
			/* XXX log something here */
			continue;
		}
		if (nbytes > 0) {
			if (nleft < nbytes)
				nbytes = nleft;
			bcopy(io->data + io->offset, offset, nbytes);
			offset += nbytes;
			nleft -= nbytes;
		}
	}
	return offset - buf;
}

/*
 * copy at most cc bytes from ioq to buf,
 * terminating on the stop character.
 */
static
int
ioqtocbuf(ioq, buf, cc, stopc)
	struct ioqueue *ioq;
	s_char	*buf;
	int	cc;
	register int stopc;
{
	register int nbytes;
	register s_char *p;
	register int n;
	struct	io *io;
	struct	qelem *qp;
	struct	qelem *head;
	int	total;
	int	found;

	head = &ioq->list.queue;
	found = 0;
	total = 0;
	for (qp = head->q_forw; qp != head; qp = qp->q_forw) {
		io = (struct io *) qp;
		if ((nbytes = io->nbytes - io->offset) <= 0)
			continue;
		p = io->data + io->offset;
		for (n=0; n < nbytes && p[n] != stopc; n++)
			;
		total += n;
		if (n < nbytes) {
			found++;
			break;
		}
	}
	if (found == 0)
		return -1;
	ioqtobuf(ioq, buf, cc < total ? cc : total);
	return total;
}

/*
 * initialize an iovec to point at max bytes worth
 * of data from the ioqueue.
 */
static
int
ioqtoiov(ioq, iov, max)
	struct ioqueue *ioq;
	struct iovec *iov;
	register int max;
{
	struct io *io;
	register int cc;
	register int niov;
	register int len;
	struct	qelem *qp;

	cc = max;
	niov = 0;
	qp = ioq->list.queue.q_forw;
	while (qp != &ioq->list.queue && cc > 0) {
		io = (struct io *) qp;
		len = io->nbytes - io->offset;
		if (len > cc)
			len = cc;
		iov->iov_base = io->data + io->offset;
		iov->iov_len = len;
		cc -= len;
		niov++;
		iov++;
		qp = qp->q_forw;
		if (niov >= 16)
			break;
	}
	return niov;
}

/*
 * append a buffer to the end of the ioq.
 */
static
int
appendcc(ioq, buf, cc)
	struct	ioqueue *ioq;
	s_char	*buf;
	int	cc;
{
	struct io *io;
	int	len;
	s_char	*ptr;
	int	avail;

	/* determine if any space is left */
	io = (struct io *)ioq->list.queue.q_back;
	avail = io->size - io->nbytes;
	if (avail > 0) {
		/* append to existing buffer */
		len = cc > avail ? avail : cc;
		bcopy(buf, io->data + io->nbytes, len);
		io->nbytes += len;
		ioq->cc += len;
		if (avail < cc)
			appendcc(ioq, buf + len, cc - len);
	} else {
		/* create a new buffer, minimum bufsize bytes */
		len = cc > ioq->bufsize ? cc : ioq->bufsize;
		ptr = malloc(len);
		bcopy(buf, ptr, cc);
		io = (struct io *) malloc(sizeof(struct io));
		io->nbytes = cc;
		io->size = len;
		io->offset = 0;
		io->data = ptr;
		insque(&io->queue, ioq->list.queue.q_back);
		ioq->cc += cc;
	}
	return cc;
}

/*
 * remove cc bytes from ioqueue ioq
 * free memory, dequeue io elements
 * which are no longer used.
 */
static
int
removecc(ioq, cc)
	struct ioqueue *ioq;
	register int cc;
{
	struct io *io;
	struct qelem *qp;
	register int nbytes;
	register int there;
	register int remain;

	nbytes = 0;
	remain = cc;
	while ((qp = ioq->list.queue.q_forw) != &ioq->list.queue) {
		io = (struct io *) qp;
		there = io->nbytes - io->offset;
		if (there < 0) {
			/* error */
			(void) remque(&io->queue);
			(void) free((s_char *)io);
			continue;
		}
		if (remain >= there) {
			/* not enough or exact; free entry */
			nbytes += there;
			remain -= there;
			(void) remque(&io->queue);
			(void) free(io->data);
			(void) free((s_char *)io);
		} else {
			/* too much; increment offset */
			io->offset += remain;
			nbytes += remain;
			remain = 0;
		}
		if (remain <= 0)
			break;
	}
	ioq->cc -= nbytes;
	return nbytes;
}
