#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9pclient.h>
#include "term.h"

const char *termprog = "win";

#define	EVENTSIZE	256
#define	STACK	32768

typedef struct Event Event;
typedef struct Q Q;

struct Event
{
	int	c1;
	int	c2;
	int	q0;
	int	q1;
	int	flag;
	int	nb;
	int	nr;
	char	b[EVENTSIZE*UTFmax+1];
	Rune	r[EVENTSIZE+1];
};

Event blank = {
	'M',
	'X',
	0, 0, 0, 1, 1,
	{ ' ', 0 },
	{ ' ', 0 }
};

struct Q
{
	QLock	lk;
	int		p;
	int		k;
};

Q	q;

CFid *eventfd;
CFid *addrfd;
CFid *datafd;
CFid *ctlfd;
/* int bodyfd; */

char	*typing;
int	ntypeb;
int	ntyper;
int	ntypebreak;
int	debug;
int	rcfd;
int	cook = 1;
int	password;
int	israw(int);

char *name;

char **prog;
Channel *cwait;
int pid = -1;

int	label(char*, int);
void	error(char*, ...);
void	stdinproc(void*);
void	stdoutproc(void*);
void	type(Event*, int, CFid*, CFid*);
void	sende(Event*, int, CFid*, CFid*, CFid*, int);
char	*onestring(int, char**);
int	delete(Event*);
void	deltype(uint, uint);
void	sendbs(int, int);
void	runproc(void*);

int
fsfidprint(CFid *fid, char *fmt, ...)
{
	char buf[256];
	va_list arg;
	int n;

	va_start(arg, fmt);
	n = vsnprint(buf, sizeof buf, fmt, arg);
	va_end(arg);
	return fswrite(fid, buf, n);
}

void
usage(void)
{
	fprint(2, "usage: win cmd args...\n");
	threadexitsall("usage");
}

void
waitthread(void *v)
{
	recvp(cwait);
	threadexitsall(nil);
}

void
hangupnote(void *a, char *msg)
{
	if(strcmp(msg, "hangup") == 0 && pid != 0){
		postnote(PNGROUP, pid, "hangup");
		noted(NDFLT);
	}
	if(strstr(msg, "child")){
		char buf[128];
		int n;

		n = awaitnohang(buf, sizeof buf-1);
		if(n > 0){
			buf[n] = 0;
			if(atoi(buf) == pid)
				threadexitsall(0);
		}
		noted(NCONT);
	}
	noted(NDFLT);
}

void
threadmain(int argc, char **argv)
{
	int fd, id;
	char buf[256];
	char buf1[128];
	CFsys *fs;
	char *dump;

	dump = onestring(argc, argv);

	ARGBEGIN{
	case 'd':
		debug = 1;
		break;
	case 'n':
		name = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND

	prog = argv;

	if(name == nil){
		if(argc > 0)
			name = argv[0];
		else{
			name = sysname();
			if(name == nil)
				name = "gnot";
		}
	}

	/*
	 * notedisable("sys: write on closed pipe");
	 * not okay to disable the note, because that
	 * gets inherited by the subshell, so that something
	 * as simple as "yes | sed 10q" never exits.
	 * call notifyoff instead.  (is notedisable ever safe?)
	 */
	notifyoff("sys: write on closed pipe");

	noteenable("sys: child");
	notify(hangupnote);

	if((fs = nsmount("acme", "")) == 0)
		sysfatal("nsmount acme: %r");
	ctlfd = fsopen(fs, "new/ctl", ORDWR|OCEXEC);
	if(ctlfd == 0 || fsread(ctlfd, buf, 12) != 12)
		sysfatal("ctl: %r");
	id = atoi(buf);
	snprint(buf, sizeof buf, "%d", id);
	putenv("winid", buf);
	sprint(buf, "%d/tag", id);
	fd = fsopenfd(fs, buf, OWRITE|OCEXEC);
	write(fd, "Send ", 4+1);
	close(fd);
	sprint(buf, "%d/event", id);
	eventfd = fsopen(fs, buf, ORDWR|OCEXEC);
	sprint(buf, "%d/addr", id);
	addrfd = fsopen(fs, buf, ORDWR|OCEXEC);
	sprint(buf, "%d/data", id);
	datafd = fsopen(fs, buf, ORDWR|OCEXEC);
	sprint(buf, "%d/body", id);
/*	bodyfd = fsopenfd(fs, buf, ORDWR|OCEXEC); */
	if(eventfd==nil || addrfd==nil || datafd==nil)
		sysfatal("data files: %r");
/*
	if(eventfd<0 || addrfd<0 || datafd<0 || bodyfd<0)
		sysfatal("data files: %r");
*/
	fsunmount(fs);

	cwait = threadwaitchan();
	threadcreate(waitthread, nil, STACK);
	pid = rcstart(argc, argv, &rcfd, nil);
	if(pid == -1)
		sysfatal("exec failed");

	getwd(buf1, sizeof buf1);
	sprint(buf, "name %s/-%s\n0\n", buf1, name);
	fswrite(ctlfd, buf, strlen(buf));
	sprint(buf, "dumpdir %s/\n", buf1);
	fswrite(ctlfd, buf, strlen(buf));
	sprint(buf, "dump %s\n", dump);
	fswrite(ctlfd, buf, strlen(buf));
	sprint(buf, "scroll");
	fswrite(ctlfd, buf, strlen(buf));

	updatewinsize(25, 80, 0, 0);
	proccreate(stdoutproc, nil, STACK);
	stdinproc(nil);
}

void
error(char *s, ...)
{
	va_list arg;

	if(s){
		va_start(arg, s);
		s = vsmprint(s, arg);
		va_end(arg);
		fprint(2, "win: %s: %r\n", s);
	}
	if(pid != -1)
		postnote(PNGROUP, pid, "hangup");
	threadexitsall(s);
}

char*
onestring(int argc, char **argv)
{
	char *p;
	int i, n;
	static char buf[1024];

	if(argc == 0)
		return "";
	p = buf;
	for(i=0; i<argc; i++){
		n = strlen(argv[i]);
		if(p+n+1 >= buf+sizeof buf)
			break;
		memmove(p, argv[i], n);
		p += n;
		*p++ = ' ';
	}
	p[-1] = 0;
	return buf;
}

int
getec(CFid *efd)
{
	static char buf[8192];
	static char *bufp;
	static int nbuf;

	if(nbuf == 0){
		nbuf = fsread(efd, buf, sizeof buf);
		if(nbuf <= 0)
			error(nil);
		bufp = buf;
	}
	--nbuf;
	return *bufp++;
}

int
geten(CFid *efd)
{
	int n, c;

	n = 0;
	while('0'<=(c=getec(efd)) && c<='9')
		n = n*10+(c-'0');
	if(c != ' ')
		error("event number syntax");
	return n;
}

int
geter(CFid *efd, char *buf, int *nb)
{
	Rune r;
	int n;

	r = getec(efd);
	buf[0] = r;
	n = 1;
	if(r < Runeself)
		goto Return;
	while(!fullrune(buf, n))
		buf[n++] = getec(efd);
	chartorune(&r, buf);
    Return:
	*nb = n;
	return r;
}

void
gete(CFid *efd, Event *e)
{
	int i, nb;

	e->c1 = getec(efd);
	e->c2 = getec(efd);
	e->q0 = geten(efd);
	e->q1 = geten(efd);
	e->flag = geten(efd);
	e->nr = geten(efd);
	if(e->nr > EVENTSIZE)
		error("event string too long");
	e->nb = 0;
	for(i=0; i<e->nr; i++){
		e->r[i] = geter(efd, e->b+e->nb, &nb);
		e->nb += nb;
	}
	e->r[e->nr] = 0;
	e->b[e->nb] = 0;
	if(getec(efd) != '\n')
		error("event syntax 2");
}

int
nrunes(char *s, int nb)
{
	int i, n;
	Rune r;

	n = 0;
	for(i=0; i<nb; n++)
		i += chartorune(&r, s+i);
	return n;
}

void
stdinproc(void *v)
{
	CFid *cfd = ctlfd;
	CFid *efd = eventfd;
	CFid *dfd = datafd;
	CFid *afd = addrfd;
	int fd0 = rcfd;
	Event e, e2, e3, e4;
	int n;

	USED(v);

	for(;;){
		if(debug)
			fprint(2, "typing[%d,%d)\n", q.p, q.p+ntyper);
		gete(efd, &e);
		if(debug)
			fprint(2, "msg %c%c q[%d,%d)... ", e.c1, e.c2, e.q0, e.q1);
		qlock(&q.lk);
		switch(e.c1){
		default:
		Unknown:
			print("unknown message %c%c\n", e.c1, e.c2);
			break;

		case 'E':	/* write to body or tag; can't affect us */
			switch(e.c2){
			case 'I':
			case 'D':		/* body */
				if(debug)
					fprint(2, "shift typing %d... ", e.q1-e.q0);
				q.p += e.q1-e.q0;
				break;

			case 'i':
			case 'd':		/* tag */
				break;

			default:
				goto Unknown;
			}
			break;

		case 'F':	/* generated by our actions; ignore */
			break;

		case 'K':
		case 'M':
			switch(e.c2){
			case 'I':
				if(e.nr == 1 && e.r[0] == 0x7F) {
					char buf[1];
					fsprint(addrfd, "#%ud,#%ud", e.q0, e.q1);
					fswrite(datafd, "", 0);
					buf[0] = 0x7F;
					write(fd0, buf, 1);
					break;
				}
				if(e.q0 < q.p){
					if(debug)
						fprint(2, "shift typing %d... ", e.q1-e.q0);
					q.p += e.q1-e.q0;
				}
				else if(e.q0 <= q.p+ntyper){
					if(debug)
						fprint(2, "type... ");
					type(&e, fd0, afd, dfd);
				}
				break;

			case 'D':
				n = delete(&e);
				q.p -= n;
				if(israw(fd0) && e.q1 >= q.p+n)
					sendbs(fd0, n);
				break;

			case 'x':
			case 'X':
				if(e.flag & 2)
					gete(efd, &e2);
				if(e.flag & 8){
					gete(efd, &e3);
					gete(efd, &e4);
				}
				if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){
					/* send it straight back */
					fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
					break;
				}
				if(e.q0==e.q1 && (e.flag&2)){
					e2.flag = e.flag;
					e = e2;
				}
				char buf[100];
				snprint(buf, sizeof buf, "%.*S", e.nr, e.r);
				if(cistrcmp(buf, "cook") == 0) {
					cook = 1;
					break;
				}
				if(cistrcmp(buf, "nocook") == 0) {
					cook = 0;
					break;
				}
				if(e.flag & 8){
					if(e.q1 != e.q0){
						sende(&e, fd0, cfd, afd, dfd, 0);
						sende(&blank, fd0, cfd, afd, dfd, 0);
					}
					sende(&e3, fd0, cfd, afd, dfd, 1);
				}else	 if(e.q1 != e.q0)
					sende(&e, fd0, cfd, afd, dfd, 1);
				break;

			case 'l':
			case 'L':
				/* just send it back */
				if(e.flag & 2)
					gete(efd, &e2);
				fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
				break;

			case 'd':
			case 'i':
				break;

			default:
				goto Unknown;
			}
		}
		qunlock(&q.lk);
	}
}

int
dropcr(char *p, int n)
{
	int i;
	char *w, *r, *q;

	r = p;
	w = p;
	for(i=0; i<n; i++) {
		switch(*r) {
		case '\b':
			if(w > p)
				w--;
			break;
		case '\r':
			while(i<n-1 && *(r+1) == '\r') {
				r++;
				i++;
			}
			if(i<n && *(r+1) != '\n') {
				q = r;
				while(q>p && *(q-1) != '\n')
					q--;
				if(q > p) {
					w = q;
					break;
				}
			}
			*w++ = '\n';
			break;
		default:
			*w++ = *r;
			break;
		}
		r++;
	}
	return w-p;
}

void
stdoutproc(void *v)
{
	int fd1 = rcfd;
	CFid *afd = addrfd;
	CFid *dfd = datafd;
	int n, m, w, npart;
	char *buf, *s, *t;
	Rune r;
	char x[16], hold[UTFmax];

	USED(v);
	buf = malloc(8192+UTFmax+1);
	npart = 0;
	for(;;){
		/* Let typing have a go -- maybe there's a rubout waiting. */
		yield();
		n = read(fd1, buf+npart, 8192);
		if(n <= 0)
			error(nil);

		n = echocancel(buf+npart, n);
		if(n == 0)
			continue;

		n = dropcrnl(buf+npart, n);
		if(n == 0)
			continue;

		n = dropcr(buf+npart, n);
		if(n == 0)
			continue;

		/* squash NULs */
		s = memchr(buf+npart, 0, n);
		if(s){
			for(t=s; s<buf+npart+n; s++)
				if(*t = *s)	/* assign = */
					t++;
			n = t-(buf+npart);
		}

		n += npart;

		/* hold on to final partial rune */
		npart = 0;
		while(n>0 && (buf[n-1]&0xC0)){
			--n;
			npart++;
			if((buf[n]&0xC0)!=0x80){
				if(fullrune(buf+n, npart)){
					w = chartorune(&r, buf+n);
					n += w;
					npart -= w;
				}
				break;
			}
		}
		if(n > 0){
			memmove(hold, buf+n, npart);
			buf[n] = 0;
			n = label(buf, n);
			buf[n] = 0;

			// clumsy but effective: notice password
			// prompts so we can disable echo.
			password = 0;
			if(cistrstr(buf, "password") || cistrstr(buf, "passphrase")) {
				int i;

				i = n;
				while(i > 0 && buf[i-1] == ' ')
					i--;
				password = i > 0 && buf[i-1] == ':';
			}

			qlock(&q.lk);
			m = sprint(x, "#%d", q.p);
			if(fswrite(afd, x, m) != m){
				fprint(2, "stdout writing address %s: %r; resetting\n", x);
				if(fswrite(afd, "$", 1) < 0)
					fprint(2, "reset: %r\n");
				fsseek(afd, 0, 0);
				m = fsread(afd, x, sizeof x-1);
				if(m >= 0){
					x[m] = 0;
					q.p = atoi(x);
				}
			}
			if(fswrite(dfd, buf, n) != n)
				error("stdout writing body");
			/* Make sure acme scrolls to the end of the above write. */
			if(fswrite(dfd, nil, 0) != 0)
				error("stdout flushing body");
			q.p += nrunes(buf, n);
			qunlock(&q.lk);
			memmove(buf, hold, npart);
		}
	}
}

char wdir[512];
int
label(char *sr, int n)
{
	char *sl, *el, *er, *r, *p;

	er = sr+n;
	for(r=er-1; r>=sr; r--)
		if(*r == '\007')
			break;
	if(r < sr)
		return n;

	el = r+1;
	if(el-sr > sizeof wdir - strlen(name) - 20)
		sr = el - (sizeof wdir - strlen(name) - 20);
	for(sl=el-3; sl>=sr; sl--)
		if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
			break;
	if(sl < sr)
		return n;

	*r = 0;
	if(strcmp(sl+3, "*9term-hold+") != 0) {
		/*
		 * add /-sysname if not present
		 */
		snprint(wdir, sizeof wdir, "name %s", sl+3);
		p = strrchr(wdir, '/');
		if(p==nil || *(p+1) != '-'){
			p = wdir+strlen(wdir);
			if(*(p-1) != '/')
				*p++ = '/';
			*p++ = '-';
			strcpy(p, name);
		}
		strcat(wdir, "\n0\n");
		fswrite(ctlfd, wdir, strlen(wdir));
	}

	memmove(sl, el, er-el);
	n -= (el-sl);
	return n;
}

int
delete(Event *e)
{
	uint q0, q1;
	int deltap;

	q0 = e->q0;
	q1 = e->q1;
	if(q1 <= q.p)
		return e->q1-e->q0;
	if(q0 >= q.p+ntyper)
		return 0;
	deltap = 0;
	if(q0 < q.p){
		deltap = q.p-q0;
		q0 = 0;
	}else
		q0 -= q.p;
	if(q1 > q.p+ntyper)
		q1 = ntyper;
	else
		q1 -= q.p;
	deltype(q0, q1);
	return deltap;
}

void
addtype(int c, uint p0, char *b, int nb, int nr)
{
	int i, w;
	Rune r;
	uint p;
	char *b0;

	for(i=0; i<nb; i+=w){
		w = chartorune(&r, b+i);
		if((r==0x7F||r==3) && c=='K'){
			write(rcfd, "\x7F", 1);
			/* toss all typing */
			q.p += ntyper+nr;
			ntypebreak = 0;
			ntypeb = 0;
			ntyper = 0;
			/* buglet:  more than one delete ignored */
			return;
		}
		if(r=='\n' || r==0x04)
			ntypebreak++;
	}
	typing = realloc(typing, ntypeb+nb);
	if(typing == nil)
		error("realloc");
	if(p0 == ntyper)
		memmove(typing+ntypeb, b, nb);
	else{
		b0 = typing;
		for(p=0; p<p0 && b0<typing+ntypeb; p++){
			w = chartorune(&r, b0+i);
			b0 += w;
		}
		if(p != p0)
			error("typing: findrune");
		memmove(b0+nb, b0, (typing+ntypeb)-b0);
		memmove(b0, b, nb);
	}
	ntypeb += nb;
	ntyper += nr;
}

int
israw(int fd0)
{
	return (!cook || password) && !isecho(fd0);
}

void
sendtype(int fd0)
{
	int i, n, nr, raw;

	raw = israw(fd0);
	while(ntypebreak || (raw && ntypeb > 0)){
		for(i=0; i<ntypeb; i++)
			if(typing[i]=='\n' || typing[i]==0x04 || (i==ntypeb-1 && raw)){
				if((typing[i] == '\n' || typing[i] == 0x04) && ntypebreak > 0)
					ntypebreak--;
				n = i+1;
				i++;
				if(!raw)
					echoed(typing, n);
				if(write(fd0, typing, n) != n)
					error("sending to program");
				nr = nrunes(typing, i);
				q.p += nr;
				ntyper -= nr;
				ntypeb -= i;
				memmove(typing, typing+i, ntypeb);
				goto cont2;
			}
		print("no breakchar\n");
		ntypebreak = 0;
cont2:;
	}
}

void
sendbs(int fd0, int n)
{
	char buf[128];
	int m;

	memset(buf, 0x08, sizeof buf);
	while(n > 0) {
		m = sizeof buf;
		if(m > n)
			m = n;
		n -= m;
		write(fd0, buf, m);
	}
}

void
deltype(uint p0, uint p1)
{
	int w;
	uint p, b0, b1;
	Rune r;

	/* advance to p0 */
	b0 = 0;
	for(p=0; p<p0 && b0<ntypeb; p++){
		w = chartorune(&r, typing+b0);
		b0 += w;
	}
	if(p != p0)
		error("deltype 1");
	/* advance to p1 */
	b1 = b0;
	for(; p<p1 && b1<ntypeb; p++){
		w = chartorune(&r, typing+b1);
		b1 += w;
		if(r=='\n' || r==0x04)
			ntypebreak--;
	}
	if(p != p1)
		error("deltype 2");
	memmove(typing+b0, typing+b1, ntypeb-b1);
	ntypeb -= b1-b0;
	ntyper -= p1-p0;
}

void
type(Event *e, int fd0, CFid *afd, CFid *dfd)
{
	int m, n, nr;
	char buf[128];

	if(e->nr > 0)
		addtype(e->c1, e->q0-q.p, e->b, e->nb, e->nr);
	else{
		m = e->q0;
		while(m < e->q1){
			n = sprint(buf, "#%d", m);
			fswrite(afd, buf, n);
			n = fsread(dfd, buf, sizeof buf);
			nr = nrunes(buf, n);
			while(m+nr > e->q1){
				do; while(n>0 && (buf[--n]&0xC0)==0x80);
				--nr;
			}
			if(n == 0)
				break;
			addtype(e->c1, m-q.p, buf, n, nr);
			m += nr;
		}
	}
	if(israw(fd0)) {
		n = sprint(buf, "#%d,#%d", e->q0, e->q1);
		fswrite(afd, buf, n);
		fswrite(dfd, "", 0);
		q.p -= e->q1 - e->q0;
	}
	sendtype(fd0);
	if(e->nb > 0 && e->b[e->nb-1] == '\n')
		cook = 1;
}

void
sende(Event *e, int fd0, CFid *cfd, CFid *afd, CFid *dfd, int donl)
{
	int l, m, n, nr, lastc, end;
	char abuf[16], buf[128];

	end = q.p+ntyper;
	l = sprint(abuf, "#%d", end);
	fswrite(afd, abuf, l);
	if(e->nr > 0){
		fswrite(dfd, e->b, e->nb);
		addtype(e->c1, ntyper, e->b, e->nb, e->nr);
		lastc = e->r[e->nr-1];
	}else{
		m = e->q0;
		lastc = 0;
		while(m < e->q1){
			n = sprint(buf, "#%d", m);
			fswrite(afd, buf, n);
			n = fsread(dfd, buf, sizeof buf);
			nr = nrunes(buf, n);
			while(m+nr > e->q1){
				do; while(n>0 && (buf[--n]&0xC0)==0x80);
				--nr;
			}
			if(n == 0)
				break;
			l = sprint(abuf, "#%d", end);
			fswrite(afd, abuf, l);
			fswrite(dfd, buf, n);
			addtype(e->c1, ntyper, buf, n, nr);
			lastc = buf[n-1];
			m += nr;
			end += nr;
		}
	}
	if(donl && lastc!='\n'){
		fswrite(dfd, "\n", 1);
		addtype(e->c1, ntyper, "\n", 1, 1);
	}
	fswrite(cfd, "dot=addr", 8);
	sendtype(fd0);
}
