#include "stdinc.h"

#include "9.h"

enum {
	Nl	= 256,			/* max. command line length */
	Nq	= 8*1024,		/* amount of I/O buffered */
};

typedef struct Q {
	QLock	lock;
	Rendez	full;
	Rendez	empty;

	char	q[Nq];
	int	n;
	int	r;
	int	w;
} Q;

typedef struct Cons {
	QLock	lock;
	int	ref;
	int	closed;
	int	fd;
	int	srvfd;
	int	ctlfd;
	Q*	iq;		/* points to console.iq */
	Q*	oq;		/* points to console.oq */
} Cons;

char *currfsysname;

static struct {
	Q*	iq;		/* input */
	Q*	oq;		/* output */
	char	l[Nl];		/* command line assembly */
	int	nl;		/* current line length */
	int	nopens;

	char*	prompt;
	int	np;
} console;

static void
consClose(Cons* cons)
{
	qlock(&cons->lock);
	cons->closed = 1;

	cons->ref--;
	if(cons->ref > 0){
		qlock(&cons->iq->lock);
		rwakeup(&cons->iq->full);
		qunlock(&cons->iq->lock);
		qlock(&cons->oq->lock);
		rwakeup(&cons->oq->empty);
		qunlock(&cons->oq->lock);
		qunlock(&cons->lock);
		return;
	}

	if(cons->ctlfd != -1){
		close(cons->ctlfd);
		cons->srvfd = -1;
	}
	if(cons->srvfd != -1){
		close(cons->srvfd);
		cons->srvfd = -1;
	}
	if(cons->fd != -1){
		close(cons->fd);
		cons->fd = -1;
	}
	qunlock(&cons->lock);
	vtfree(cons);
	console.nopens--;
}

static void
consIProc(void* v)
{
	Q *q;
	Cons *cons;
	int n, w;
	char buf[Nq/4];

	threadsetname("consI");

	cons = v;
	q = cons->iq;
	for(;;){
		/*
		 * Can't tell the difference between zero-length read
		 * and eof, so keep calling read until we get an error.
		 */
		if(cons->closed || (n = read(cons->fd, buf, Nq/4)) < 0)
			break;
		qlock(&q->lock);
		while(Nq - q->n < n && !cons->closed)
			rsleep(&q->full);
		w = Nq - q->w;
		if(w < n){
			memmove(&q->q[q->w], buf, w);
			memmove(&q->q[0], buf + w, n - w);
		}
		else
			memmove(&q->q[q->w], buf, n);
		q->w = (q->w + n) % Nq;
		q->n += n;
		rwakeup(&q->empty);
		qunlock(&q->lock);
	}
	consClose(cons);
}

static void
consOProc(void* v)
{
	Q *q;
	Cons *cons;
	char buf[Nq];
	int lastn, n, r;

	threadsetname("consO");

	cons = v;
	q = cons->oq;
	qlock(&q->lock);
	lastn = 0;
	for(;;){
		while(lastn == q->n && !cons->closed)
			rsleep(&q->empty);
		if((n = q->n - lastn) > Nq)
			n = Nq;
		if(n > q->w){
			r = n - q->w;
			memmove(buf, &q->q[Nq - r], r);
			memmove(buf+r, &q->q[0], n - r);
		}
		else
			memmove(buf, &q->q[q->w - n], n);
		lastn = q->n;
		qunlock(&q->lock);
		if(cons->closed || write(cons->fd, buf, n) < 0)
			break;
		qlock(&q->lock);
		rwakeup(&q->empty);
	}
	consClose(cons);
}

int
consOpen(int fd, int srvfd, int ctlfd)
{
	Cons *cons;

	cons = vtmallocz(sizeof(Cons));
	cons->fd = fd;
	cons->srvfd = srvfd;
	cons->ctlfd = ctlfd;
	cons->iq = console.iq;
	cons->oq = console.oq;
	console.nopens++;

	qlock(&cons->lock);
	cons->ref = 2;
	cons->closed = 0;
	if(proccreate(consOProc, cons, STACK) < 0){
		cons->ref--;
		qunlock(&cons->lock);
		consClose(cons);
		return 0;
	}
	qunlock(&cons->lock);

	if(ctlfd >= 0)
		consIProc(cons);
	else if(proccreate(consIProc, cons, STACK) < 0){
		consClose(cons);
		return 0;
	}

	return 1;
}

static int
qWrite(Q* q, char* p, int n)
{
	int w;

	qlock(&q->lock);
	if(n > Nq - q->w){
		w = Nq - q->w;
		memmove(&q->q[q->w], p, w);
		memmove(&q->q[0], p + w, n - w);
		q->w = n - w;
	}
	else{
		memmove(&q->q[q->w], p, n);
		q->w += n;
	}
	q->n += n;
	rwakeup(&q->empty);
	qunlock(&q->lock);

	return n;
}

static Q*
qAlloc(void)
{
	Q *q;

	q = vtmallocz(sizeof(Q));
	q->full.l = &q->lock;
	q->empty.l = &q->lock;
	q->n = q->r = q->w = 0;

	return q;
}

static void
consProc(void* v)
{
	USED(v);
	Q *q;
	int argc, i, n, r;
	char *argv[20], buf[Nq], *lp, *wbuf;
	char procname[64];

	snprint(procname, sizeof procname, "cons %s", currfsysname);
	threadsetname(procname);

	q = console.iq;
	qWrite(console.oq, console.prompt, console.np);
	qlock(&q->lock);
	for(;;){
		while((n = q->n) == 0)
			rsleep(&q->empty);
		r = Nq - q->r;
		if(r < n){
			memmove(buf, &q->q[q->r], r);
			memmove(buf + r, &q->q[0], n - r);
		}
		else
			memmove(buf, &q->q[q->r], n);
		q->r = (q->r + n) % Nq;
		q->n -= n;
		rwakeup(&q->full);
		qunlock(&q->lock);

		for(i = 0; i < n; i++){
			switch(buf[i]){
			case '\004':				/* ^D */
				if(console.nl == 0){
					qWrite(console.oq, "\n", 1);
					break;
				}
				/*FALLTHROUGH*/
			default:
				if(console.nl < Nl-1){
					qWrite(console.oq, &buf[i], 1);
					console.l[console.nl++] = buf[i];
				}
				continue;
			case '\b':
				if(console.nl != 0){
					qWrite(console.oq, &buf[i], 1);
					console.nl--;
				}
				continue;
			case '\n':
				qWrite(console.oq, &buf[i], 1);
				break;
			case '\025':				/* ^U */
				qWrite(console.oq, "^U\n", 3);
				console.nl = 0;
				break;
			case '\027':				/* ^W */
				console.l[console.nl] = '\0';
				wbuf = vtmalloc(console.nl+1);
				memmove(wbuf, console.l, console.nl+1);
				argc = tokenize(wbuf, argv, nelem(argv));
				if(argc > 0)
					argc--;
				console.nl = 0;
				lp = console.l;
				for(i = 0; i < argc; i++)
					lp += sprint(lp, "%q ", argv[i]);
				console.nl = lp - console.l;
				vtfree(wbuf);
				qWrite(console.oq, "^W\n", 3);
				if(console.nl == 0)
					break;
				qWrite(console.oq, console.l, console.nl);
				continue;
			case '\177':
				qWrite(console.oq, "\n", 1);
				console.nl = 0;
				break;
			}

			console.l[console.nl] = '\0';
			if(console.nl != 0)
				cliExec(console.l);

			console.nl = 0;
			qWrite(console.oq, console.prompt, console.np);
		}

		qlock(&q->lock);
	}
}

int
consWrite(char* buf, int len)
{
	if(console.oq == nil)
		return write(2, buf, len);
	if(console.nopens == 0)
		write(2, buf, len);
	return qWrite(console.oq, buf, len);
}

int
consPrompt(char* prompt)
{
	char buf[ERRMAX];

	if(prompt == nil)
		prompt = "prompt";

	vtfree(console.prompt);
	console.np = snprint(buf, sizeof(buf), "%s: ", prompt);
	console.prompt = vtstrdup(buf);

	return console.np;
}

int
consTTY(void)
{
	int ctl, fd;
	char *name, *p;

	name = "/dev/cons";
	if((fd = open(name, ORDWR)) < 0){
#ifdef PLAN9PORT
		name = "/dev/tty";
#else
		name = "#c/cons";
#endif
		if((fd = open(name, ORDWR)) < 0){
			werrstr("consTTY: open %s: %r", name);
			return 0;
		}
	}

#ifdef PLAN9PORT
	USED(p);
	ctl = 0;
#else
	p = smprint("%sctl", name);
	if((ctl = open(p, OWRITE)) < 0){
		close(fd);
		werrstr("consTTY: open %s: %r", p);
		free(p);
		return 0;
	}
	if(write(ctl, "rawon", 5) < 0){
		close(ctl);
		close(fd);
		werrstr("consTTY: write %s: %r", p);
		free(p);
		return 0;
	}
	free(p);
#endif

	if(consOpen(fd, fd, ctl) == 0){
		close(ctl);
		close(fd);
		return 0;
	}

	return 1;
}

int
consInit(void)
{
	console.iq = qAlloc();
	console.oq = qAlloc();
	console.nl = 0;

	consPrompt(nil);

	if(proccreate(consProc, nil, STACK) < 0){
		sysfatal("can't start console proc");
		return 0;
	}

	return 1;
}
