/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

#include <u.h>
#include <lib9.h>
#include <envvars.h>
#include <bio.h>
#include <thread.h>
#include <9P2000.h>
#include <9p.h>
#include <ctype.h>
#include "dat.h"

void	mainctl(void*);
void	startcmd(char *[], int*);
void	stdout2body(void*);

int	debug;
int	notepg;
int	eraseinput;
int	dirty = 0;

Window *win;		/* the main window */

void
usage(void)
{
	fprint(2, "usage: win [command]\n");
	threadexitsall("usage");
}

void
threadmain(int argc, char *argv[])
{
	int i, j;
	char *dir, *tag, *name;
	char buf[1024], **av;

	quotefmtinstall();
	rfork(RFNAMEG);
	ARGBEGIN{
	case 'd':
		debug = 1;
		chatty9p++;
		break;
	case 'e':
		eraseinput = 1;
		break;
	case 'D':
{extern int _threaddebuglevel;
		_threaddebuglevel = 1<<20;
}
	}ARGEND

	if(argc == 0){
		av = emalloc(3*sizeof(char*));
		av[0] = "rc";
		av[1] = "-i";
		name = getenv(ENV_SYSNAME);
	}else{
		av = argv;
		name = utfrrune(av[0], '/');
		if(name)
			name++;
		else
			name = av[0];
	}

	if(getwd(buf, sizeof buf) <= 0)
		dir = "/";
	else
		dir = buf;
	dir = estrdup(dir);
	tag = estrdup(dir);
	tag = eappend(estrdup(tag), "/-", name);
	win = newwindow();
	snprint(buf, sizeof buf, "%d", win->id);
	putenv("winid", buf);
	winname(win, tag);
	wintagwrite(win, "Send Noscroll", 5+8);
	threadcreate(mainctl, win, STACK);
	mountcons();
	threadcreate(fsloop, nil, STACK);
	startpipe();
	startcmd(av, &notepg);

	strcpy(buf, "win");
	j = 3;
	for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
		strcpy(buf+j, " ");
		strcpy(buf+j+1, argv[i]);
		j += 1+strlen(argv[i]);
	}

	ctlprint(win->ctl, "scroll");
	winsetdump(win, dir, buf);
}

int
EQUAL(char *s, char *t)
{
	while(tolower(*s) == tolower(*t++))
		if(*s++ == '\0')
			return 1;
	return 0;
}

int
command(Window *w, char *s)
{
	while(*s==' ' || *s=='\t' || *s=='\n')
		s++;
	if(strcmp(s, "Delete")==0 || strcmp(s, "Del")==0){
		write(notepg, "hangup", 6);
		windel(w, 1);
		threadexitsall(nil);
		return 1;
	}
	if(EQUAL(s, "scroll")){
		ctlprint(w->ctl, "scroll\nshow");
		return 1;
	}
	if(EQUAL(s, "noscroll")){
		ctlprint(w->ctl, "noscroll");
		return 1;
	}
	return 0;
}

static long
utfncpy(char *to, char *from, int n)
{
	char *end, *e;

	e = to+n;
	if(to >= e)
		return 0;
	end = memccpy(to, from, '\0', e - to);
	if(end == nil){
		end = e;
		if(end[-1]&0x80){
			if(end-2>=to && (end[-2]&0xE0)==0xC0)
				return end-to;
			if(end-3>=to && (end[-3]&0xF0)==0xE0)
				return end-to;
			while(end>to && (*--end&0xC0)==0x80)
				;
		}
	}else
		end--;
	return end - to;
}

/* sendinput and fsloop run in the same proc (can't interrupt each other). */
static Req *q;
static Req **eq;
static int
__sendinput(Window *w, uint32_t q0, uint32_t q1)
{
	char *s, *t;
	int n, nb, eofchar;
	static int partial;
	static char tmp[UTFmax];
	Req *r;
	Rune rune;

	if(!q)
		return 0;

	r = q;
	n = 0;
	if(partial){
	Partial:
		nb = partial;
		if(nb > r->ifcall.count)
			nb = r->ifcall.count;
		memmove(r->ofcall.data, tmp, nb);
		if(nb!=partial)
			memmove(tmp, tmp+nb, partial-nb);
		partial -= nb;
		q = r->aux;
		if(q == nil)
			eq = &q;
		r->aux = nil;
		r->ofcall.count = nb;
		if(debug)
			fprint(2, "satisfy read with partial\n");
		respond(r, nil);
		return n;
	}
	if(q0==q1)
		return 0;
	s = emalloc((q1-q0)*UTFmax+1);
	n = winread(w, q0, q1, s);
	s[n] = '\0';
	t = strpbrk(s, "\n\004");
	if(t == nil){
		free(s);
		return 0;
	}
	r = q;
	eofchar = 0;
	if(*t == '\004'){
		eofchar = 1;
		*t = '\0';
	}else
		*++t = '\0';
	nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
	if(nb==0 && s<t && r->ifcall.count > 0){
		partial = utfncpy(tmp, s, UTFmax);
		assert(partial > 0);
		chartorune(&rune, tmp);
		partial = runelen(rune);
		free(s);
		n = 1;
		goto Partial;
	}
	n = utfnlen(r->ofcall.data, nb);
	if(nb==strlen(s) && eofchar)
		n++;
	r->ofcall.count = nb;
	q = r->aux;
	if(q == nil)
		eq = &q;
	r->aux = nil;
	if(debug)
		fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
	respond(r, nil);
	return n;
}

static int
_sendinput(Window *w, uint32_t q0, uint32_t *q1)
{
	char buf[32];
	int n;

	n = __sendinput(w, q0, *q1);
	if(!n || !eraseinput)
		return n;
	/* erase q0 to q0+n */
	sprint(buf, "#%lud,#%lud", q0, q0+n);
	winsetaddr(w, buf, 0);
	write(w->data, buf, 0);
	*q1 -= n;
	return 0;
}

int
sendinput(Window *w, uint32_t q0, uint32_t *q1)
{
	uint32_t n;
	Req *oq;

	n = 0;
	do {
		oq = q;
		n += _sendinput(w, q0+n, q1);
	} while(q != oq);
	return n;
}

Event esendinput;
void
fsloop(void* _)
{
	Fsevent e;
	Req **l, *r;

	eq = &q;
	memset(&esendinput, 0, sizeof esendinput);
	esendinput.c1 = 'C';
	for(;;){
		while(recv(fschan, &e) == -1)
			;
		r = e.r;
		switch(e.type){
		case 'r':
			*eq = r;
			r->aux = nil;
			eq = (Req**)&r->aux;
			/* call sendinput with hostpt and endpt */
			sendp(win->cevent, &esendinput);
			break;
		case 'f':
			for(l=&q; *l; l=(Req**)&(*l)->aux){
				if(*l == r->oldreq){
					*l = (*l)->aux;
					if(*l == nil)
						eq = l;
					respond(r->oldreq, "interrupted");
					break;
				}
			}
			respond(r, nil);
			break;
		}
	}
}

void
sendit(char *s)
{
//	char tmp[32];

	write(win->body, s, strlen(s));
/*
 * RSC: The problem here is that other procs can call sendit,
 * so we lose our single-threadedness if we call sendinput.
 * In fact, we don't even have the right queue memory,
 * I think that we'll get a write event from the body write above,
 * and we can do the sendinput then, from our single thread.
 *
 * I still need to figure out how to test this assertion for
 * programs that use /srv/win*
 *
	winselect(win, "$", 0);
	seek(win->addr, 0UL, 0);
	if(read(win->addr, tmp, 2*12) == 2*12)
		hostpt += sendinput(win, hostpt, atol(tmp), );
 */
}

void
execevent(Window *w, Event *e, int (*command)(Window*, char*))
{
	Event *ea, *e2;
	int n, na, len, needfree;
	char *s, *t;

	ea = nil;
	e2 = nil;
	if(e->flag & 2)
		e2 = recvp(w->cevent);
	if(e->flag & 8){
		ea = recvp(w->cevent);
		na = ea->nb;
		recvp(w->cevent);
	}else
		na = 0;

	needfree = 0;
	s = e->b;
	if(e->nb==0 && (e->flag&2)){
		s = e2->b;
		e->q0 = e2->q0;
		e->q1 = e2->q1;
		e->nb = e2->nb;
	}
	if(e->nb==0 && e->q0<e->q1){
		/* fetch data from window */
		s = emalloc((e->q1-e->q0)*UTFmax+2);
		n = winread(w, e->q0, e->q1, s);
		s[n] = '\0';
		needfree = 1;
	}else
	if(na){
		t = emalloc(strlen(s)+1+na+2);
		sprint(t, "%s %s", s, ea->b);
		if(needfree)
			free(s);
		s = t;
		needfree = 1;
	}

	/* if it's a known command, do it */
	/* if it's a long message, it can't be for us anyway */
	if(!command(w, s) && s[0]!='\0'){	/* send it as typed text */
		/* if it's a built-in from the tag, send it back */
		if(e->flag & 1)
			fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
		else{	/* send text to main window */
			len = strlen(s);
			if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
				if(!needfree){
					/* if(needfree), we left room for a newline before */
					t = emalloc(len+2);
					strcpy(t, s);
					s = t;
					needfree = 1;
				}
				s[len++] = '\n';
				s[len] = '\0';
			}
			sendit(s);
		}
	}
	if(needfree)
		free(s);
}

int
hasboundary(Rune *r, int nr)
{
	int i;

	for(i=0; i<nr; i++)
		if(r[i]=='\n' || r[i]=='\004')
			return 1;
	return 0;
}

void
mainctl(void *v)
{
	Window *w;
	Event *e;
	int delta, pendingS, pendingK;
	uint32_t hostpt, endpt;
	char tmp[32];

	w = v;
	proccreate(wineventproc, w, STACK);

	hostpt = 0;
	endpt = 0;
	winsetaddr(w, "0", 0);
	pendingS = 0;
	pendingK = 0;
	for(;;){
		if(debug)
			fprint(2, "input range %lud-%lud\n", hostpt, endpt);
		e = recvp(w->cevent);
		if(debug)
			fprint(2, "msg: %C %C %d %d %d %d %q\n",
				e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
		switch(e->c1){
		default:
		Unknown:
			fprint(2, "unknown message %c%c\n", e->c1, e->c2);
			break;

		case 'C':	/* input needed for /dev/cons */
			if(pendingS)
				pendingK = 1;
			else
				hostpt += sendinput(w, hostpt, &endpt);
			break;

		case 'S':	/* output to stdout */
			sprint(tmp, "#%lud", hostpt);
			winsetaddr(w, tmp, 0);
			write(w->data, e->b, e->nb);
			pendingS += e->nr;
			break;

		case 'E':	/* write to tag or body; body happens due to sendit */
			delta = e->q1-e->q0;
			if(e->c2=='I'){
				endpt += delta;
				if(e->q0 < hostpt)
					hostpt += delta;
				else
					hostpt += sendinput(w, hostpt, &endpt);
				break;
			}
			if(!islower(e->c2))
				fprint(2, "win msg: %C %C %d %d %d %d %q\n",
					e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
			break;

		case 'F':	/* generated by our actions (specifically case 'S' above) */
			delta = e->q1-e->q0;
			if(e->c2=='D'){
				/* we know about the delete by _sendinput */
				break;
			}
			if(e->c2=='I'){
				pendingS -= e->q1 - e->q0;
				if(pendingS < 0)
					fprint(2, "win: pendingS = %d\n", pendingS);
				if(e->q0 != hostpt)
					fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
				endpt += delta;
				hostpt += delta;
				sendp(writechan, nil);
				if(pendingS == 0 && pendingK){
					pendingK = 0;
					hostpt += sendinput(w, hostpt, &endpt);
				}
				break;
			}
			if(!islower(e->c2))
				fprint(2, "win msg: %C %C %d %d %d %d %q\n",
					e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
			break;

		case 'K':
			delta = e->q1-e->q0;
			switch(e->c2){
			case 'D':
				endpt -= delta;
				if(e->q1 < hostpt)
					hostpt -= delta;
				else if(e->q0 < hostpt)
					hostpt = e->q0;
				break;
			case 'I':
				delta = e->q1 - e->q0;
				endpt += delta;
				if(endpt < e->q1)	/* just in case */
					endpt = e->q1;
				if(e->q0 < hostpt)
					hostpt += delta;
				if(e->nr>0 && e->r[e->nr-1]==0x7F){
					write(notepg, "interrupt", 9);
					hostpt = endpt;
					break;
				}
				if(e->q0 >= hostpt
				&& hasboundary(e->r, e->nr)){
					/*
					 * If we are between the S message (which
					 * we processed by inserting text in the
					 * window) and the F message notifying us
					 * that the text has been inserted, then our
					 * impression of the hostpt and acme's
					 * may be different.  This could be seen if you
					 * hit enter a bunch of times in a con
					 * session.  To work around the unreliability,
					 * only send input if we don't have an S pending.
					 * The same race occurs between when a character
					 * is typed and when we get notice of it, but
					 * since characters tend to be typed at the end
					 * of the buffer, we don't run into it.  There's
					 * no workaround possible for this typing race,
					 * since we can't tell when the user has typed
					 * something but we just haven't been notified.
					 */
					if(pendingS)
						pendingK = 1;
					else
						hostpt += sendinput(w, hostpt, &endpt);
				}
				break;
			}
			break;

		case 'M':	/* mouse */
			delta = e->q1-e->q0;
			switch(e->c2){
			case 'x':
			case 'X':
				execevent(w, e, command);
				break;

			case 'l':	/* reflect all searches back to acme */
			case 'L':
				if(e->flag & 2)
					recvp(w->cevent);
				winwriteevent(w, e);
				break;

			case 'I':
				endpt += delta;
				if(e->q0 < hostpt)
					hostpt += delta;
				else
					hostpt += sendinput(w, hostpt, &endpt);
				break;

			case 'D':
				endpt -= delta;
				if(e->q1 < hostpt)
					hostpt -= delta;
				else if(e->q0 < hostpt)
					hostpt = e->q0;
				break;
			case 'd':	/* modify away; we don't care */
			case 'i':
				break;

			default:
				goto Unknown;
			}
		}
	}
}

enum
{
	NARGS		= 100,
	NARGCHAR	= 8*1024,
	EXECSTACK 	= STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
};

struct Exec
{
	char		**argv;
	Channel	*cpid;
};

int
lookinbin(char *s)
{
	if(s[0] == '/')
		return 0;
	if(s[0]=='.' && s[1]=='/')
		return 0;
	if(s[0]=='.' && s[1]=='.' && s[2]=='/')
		return 0;
	return 1;
}

/* adapted from mail.  not entirely free of details from that environment */
void
execproc(void *v)
{
	struct Exec *e;
	char *cmd, **av;
	Channel *cpid;

	e = v;
	rfork(RFCFDG|RFNOTEG);
	av = e->argv;
	close(0);
	open("/dev/cons", OREAD);
	close(1);
	open("/dev/cons", OWRITE);
	dup(1, 2);
	cpid = e->cpid;
	free(e);
	procexec(cpid, av[0], av);
	if(lookinbin(av[0])){
		cmd = estrstrdup("/cmd/", av[0]);
		procexec(cpid, cmd, av);
	}
	error("can't exec %s: %r", av[0]);
}

void
startcmd(char *argv[], int *notepg)
{
	struct Exec *e;
	Channel *cpid;
	char buf[64];
	int pid;

	e = emalloc(sizeof(struct Exec));
	e->argv = argv;
	cpid = chancreate(sizeof(uint32_t), 0);
	e->cpid = cpid;
	sprint(buf, "/mnt/wsys/%d", win->id);
	bind(buf, "/dev/acme", MREPL);
	proccreate(execproc, e, EXECSTACK);
	do
		pid = recvul(cpid);
	while(pid == -1);
	sprint(buf, "/proc/%d/notepg", pid);
	*notepg = open(buf, OWRITE);
}
