#include <u.h>
#include <signal.h>
#include <libc.h>
#include <ctype.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <cursor.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include <complete.h>
#include "term.h"

enum
{
	STACK = 32768
};

int noecho = 0;

void servedevtext(void);
void listenthread(void*);
void textthread(void*);

typedef struct Text	Text;
typedef struct Readbuf	Readbuf;

enum
{
	HiWater	= 640000,	/* max size of history */
	LoWater	= 400000,	/* min size of history after max'ed */
	MinWater	= 20000,
};

/* various geometric paramters */
enum
{
	Scrollwid 	= 12,		/* width of scroll bar */
	Scrollgap 	= 4,		/* gap right of scroll bar */
	Maxtab		= 4,
};

enum
{
	Cut,
	Paste,
	Snarf,
	Send,
	Plumb,
	Scroll,
	Cooked,
};

#define	ESC		0x1B
#define	CUT		0x18	/* ctrl-x */		
#define	COPY		0x03	/* crtl-c */
#define	PASTE		0x16	/* crtl-v */

#define	READBUFSIZE 8192
#define TRUE 1
#define FALSE 0


struct Text
{
	Frame		*f;		/* frame ofr terminal */
	Mouse		m;
	uint		nr;		/* num of runes in term */
	uint		maxr;	/* max num of runes in r */
	Rune		*r;		/* runes for term */
	uint		nraw;		/* num of runes in raw buffer */
	Rune		*raw;		/* raw buffer */
	uint		org;		/* first rune on the screen */
	uint		q0;		/* start of selection region */
	uint		q1;		/* end of selection region */
	uint		qh;		/* unix point */
	int		npart;		/* partial runes read from console */
	char		part[UTFmax];	
	int		nsnarf;		/* snarf buffer */
	Rune		*snarf;
};

struct Readbuf
{
	short	n;				/* # bytes in buf */
	uchar	data[READBUFSIZE];		/* data bytes */
};

void	mouse(void);
void	domenu2(int);
void	loop(void);
void	geom(void);
void	fill(void);
void	tcheck(void);
void	updatesel(void);
void	doreshape(void);
void	runewrite(Rune*, int);
void	consread(void);
void	conswrite(char*, int);
int	bswidth(Rune c, uint start, int eatnl);
void	cut(void);
void	paste(Rune*, int, int);
void	snarfupdate(void);
void	snarf(void);
void	show(uint);
void	key(Rune);
void	setorigin(uint org, int exact);
uint	line2q(uint);
uint	backnl(uint, uint);
int	cansee(uint);
uint	backnl(uint, uint);
void	addraw(Rune*, int);
void	mselect(void);
void	doubleclick(uint *q0, uint *q1);
int	clickmatch(int cl, int cr, int dir, uint *q);
Rune	*strrune(Rune *s, Rune c);
int	consready(void);
Rectangle scrpos(Rectangle r, ulong p0, ulong p1, ulong tot);
void	scrdraw(void);
void	scroll(int);
void	hostproc(void *arg);
void	hoststart(void);
void	plumbstart(void);
void	plumb(uint, uint);
void	plumbclick(uint*, uint*);
uint	insert(Rune*, int, uint, int);
void scrolldown(int);
void scrollup(int);

#define	runemalloc(n)		malloc((n)*sizeof(Rune))
#define	runerealloc(a, n)	realloc(a, (n)*sizeof(Rune))
#define	runemove(a, b, n)	memmove(a, b, (n)*sizeof(Rune))
Rectangle	scrollr;	/* scroll bar rectangle */
Rectangle	lastsr;		/* used for scroll bar */
int		holdon;		/* hold mode */
int		rawon(void);		/* raw mode */
int		cooked;		/* force cooked */
int		scrolling;	/* window scrolls */
int		clickmsec;	/* time of last click */
uint		clickq0;	/* point of last click */
int		rcfd;
int		sfd;	/* slave fd, to get/set terminal mode */
int		rcpid;
int		maxtab;
int		use9wm;
Mousectl*	mc;
Keyboardctl*	kc;
Channel*	hostc;
Readbuf		rcbuf[2];
int		mainpid;
int		acmecolors;
int		plumbfd;
int		button2exec;
int		label(Rune*, int);
char		wdir[1024];
char		childwdir[1024];
void		hangupnote(void*, char*);
char		thesocket[100];

char *menu2str[] = {
	"cut",
	"paste",
	"snarf",
	"send",
	"plumb",
	"scroll",
	"cooked",
	0
};

Image* cols[NCOL];
Image* hcols[NCOL];
Image* palegrey;
Image* paleblue;
Image* blue;
Image *plumbcolor;
Image *execcolor;

Menu menu2 =
{
	menu2str
};

Text	t;

Cursor whitearrow = {
	{0, 0},
	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
	 0xFF, 0xF0, 0xFF, 0xF0, 0xFF, 0xF8, 0xFF, 0xFC, 
	 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFC, 
	 0xF3, 0xF8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, },
	{0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0xC0, 0x1C, 
	 0xC0, 0x30, 0xC0, 0x30, 0xC0, 0x38, 0xC0, 0x1C, 
	 0xC0, 0x0E, 0xC0, 0x07, 0xCE, 0x0E, 0xDF, 0x1C, 
	 0xD3, 0xB8, 0xF1, 0xF0, 0xE0, 0xE0, 0xC0, 0x40, }
};

void
usage(void)
{
	fprint(2, "usage: 9term [-ars] [-W winsize] [cmd ...]\n");
	threadexitsall("usage");
}

void
threadmain(int argc, char *argv[])
{
	char *p, *font;
	char buf[32];

	rfork(RFNOTEG);
	font = nil;
	_wantfocuschanges = 1;
	mainpid = getpid();
	ARGBEGIN{
	default:
		usage();
	case 'a':	/* acme mode */
		button2exec++;
		break;
	case 'f':
		font = EARGF(usage());
		break;
	case 's':
		scrolling++;
		break;
	case 'w':	/* started from "rio" window manager */
		use9wm = 1;
		break;
	case 'W':
		winsize = EARGF(usage());
		break;
	}ARGEND

	if(font)
		putenv("font", font);

	p = getenv("tabstop");
	if(p == 0)
		p = getenv("TABSTOP");
	if(p != 0 && maxtab <= 0)
		maxtab = strtoul(p, 0, 0);
	if(maxtab <= 0)
		maxtab = 4;	/* be like rio */

	snprint(buf, sizeof buf, "%d", maxtab);
	putenv("tabstop", buf);

	initdraw(0, nil, "9term");
	notify(hangupnote);
	notifyatsig(SIGCHLD, 1);
	servedevtext();

	mc = initmouse(nil, screen);
	kc = initkeyboard(nil);
	rcpid = rcstart(argc, argv, &rcfd, &sfd);
	hoststart();
	plumbstart();

	t.f = mallocz(sizeof(Frame), 1);

	if(acmecolors){
		cols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
		cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DDarkyellow);
		cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DYellowgreen);
	}else{
		cols[BACK] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DWhite);
		cols[HIGH] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xCCCCCCFF);
		cols[BORD] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x999999FF);
	}
	cols[TEXT] = display->black;
	cols[HTEXT] = display->black;
	palegrey = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0x666666FF);

	hcols[BACK] = cols[BACK];
	hcols[HIGH] = cols[HIGH];
	blue = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DMedblue);
	paleblue = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DGreyblue);

	hcols[BORD] = blue;
	hcols[TEXT] = hcols[BORD];
	hcols[HTEXT] = hcols[TEXT];

	plumbcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x006600FF);
	execcolor = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAA0000FF);

	if(!blue || !palegrey || !paleblue || !plumbcolor || !execcolor)
		sysfatal("alloc colors: %r");
	draw(screen, screen->r, cols[BACK], nil, ZP);
	geom();
	loop();
}

void
hangupnote(void *a, char *msg)
{
	if(getpid() != mainpid)
		noted(NDFLT);
	if(strcmp(msg, "hangup") == 0 && rcpid != 0){
		postnote(PNGROUP, rcpid, "hangup");
		noted(NDFLT);
	}
	if(strstr(msg, "child")){
		/* bug: do better */
		threadexitsall(0);
	}
	noted(NDFLT);
}

void
hostproc(void *arg)
{
	Channel *c;
	int i, n, which;

	c = arg;

	i = 0;
	for(;;){
		/* Let typing have a go -- maybe there's a rubout waiting. */
		yield();

		i = 1-i;	/* toggle */
		n = threadread(rcfd, rcbuf[i].data, sizeof rcbuf[i].data);
		if(n <= 0){
			if(n < 0)
				fprint(2, "9term: host read error: %r\n");
			threadexitsall("host");
		}
		rcbuf[i].n = n;
		which = i;
		send(c, &which);
	}
}

void
hoststart(void)
{
	hostc = chancreate(sizeof(int), 0);
	threadcreate(hostproc, hostc, 32*1024);
}

void
loop(void)
{
	Rune r;
	int i;
	Alt a[5];

	a[0].c = mc->c;
	a[0].v = &mc->m;
	a[0].op = CHANRCV;

	a[1].c = kc->c;
	a[1].v = &r;
	a[1].op = CHANRCV;

	a[2].c = hostc;
	a[2].v = &i;
	a[2].op = CHANRCV;

	a[3].c = mc->resizec;
	a[3].v = nil;
	a[3].op = CHANRCV;

	a[4].c = nil;
	a[4].v = nil;
	a[4].op = CHANEND;

	for(;;) {
		tcheck();

		scrdraw();
		flushimage(display, 1);
		a[2].op = CHANRCV;
		if(!scrolling && t.qh > t.org+t.f->nchars)
			a[2].op = CHANNOP;;
		switch(alt(a)) {
		default:
			sysfatal("impossible");
		case 0:
			t.m = mc->m;
			mouse();
			break;
		case 1:
			key(r);
			break;
		case 2:
			conswrite((char*)rcbuf[i].data, rcbuf[i].n);
			break;
		case 3:
			doreshape();
			break;
		}
	}
}

void
doreshape(void)
{
	if(getwindow(display, Refnone) < 0)
		sysfatal("can't reattach to window");
	draw(screen, screen->r, cols[BACK], nil, ZP);
	geom();
	scrdraw();
}

void
geom(void)
{
	Point p;
	Rectangle r;

	if(!acmecolors){
		if(_windowhasfocus){
			cols[TEXT] = cols[HTEXT] = display->black;
			hcols[TEXT] = hcols[HTEXT] = blue;
		}else{
			cols[TEXT] = cols[HTEXT] = palegrey;
			hcols[TEXT] = hcols[HTEXT] = paleblue;
		}
	}

	r = screen->r;
	r.min.y++;
	r.max.y--;

	scrollr = r;
	scrollr.max.x = r.min.x+Scrollwid;
	lastsr = Rect(0,0,0,0);

	r.min.x += Scrollwid+Scrollgap;

	frclear(t.f, 0);
	frinit(t.f, r, font, screen, holdon ? hcols : cols);
	t.f->maxtab = maxtab*stringwidth(font, "0");
	fill();
	updatesel();

	p = stringsize(font, "0");
	if(p.x == 0 || p.y == 0)
		return;

	updatewinsize(Dy(r)/p.y, Dx(r)/p.x, Dx(r), Dy(r));
}

void
drawhold(int holdon)
{
	if(holdon)
		setcursor(mc, &whitearrow);
	else
		setcursor(mc, nil);

	draw(screen, screen->r, cols[BACK], nil, ZP);
	geom();
	scrdraw();
}

void
wordclick(uint *q0, uint *q1)
{
	while(*q1<t.nr && !isspace(t.r[*q1]))
		(*q1)++;
	while(*q0>0 && !isspace(t.r[*q0-1]))
		(*q0)--;
}

int
aselect(uint *q0, uint *q1, Image *color)
{
	int cancel;
	uint oldq0, oldq1, newq0, newq1;

	/* save old selection */
	oldq0 = t.q0;
	oldq1 = t.q1;

	/* sweep out area and record it */
	t.f->cols[HIGH] = color;
	t.f->cols[HTEXT] = display->white;
	mselect();
	newq0 = t.q0;
	newq1 = t.q1;

	cancel = 0;
	if(t.m.buttons != 0){
		while(t.m.buttons){
			readmouse(mc);
			t.m = mc->m;
		}
		cancel = 1;
	}

	/* restore old selection */
	t.f->cols[HIGH] = cols[HIGH];
	t.f->cols[HTEXT] = cols[HTEXT];
	t.q0 = oldq0;
	t.q1 = oldq1;
	updatesel();

	if(cancel)
		return -1;

	/* selected a region */
	if(newq0 < newq1){
		*q0 = newq0;
		*q1 = newq1;
		return 0;
	}

	/* clicked inside previous selection */
	/* the "<=" in newq0 <= oldq1 allows us to click the right edge */
	if(oldq0 <= newq0 && newq0 <= oldq1){
		*q0 = oldq0;
		*q1 = oldq1;
		return 0;
	}

	/* just a click */
	*q0 = newq0;
	*q1 = newq1;
	return 0;
}

static Rune Lnl[1] = { '\n' };

void
mouse(void)
{
	int but;
	uint q0, q1;

	but = t.m.buttons;

	if(but != 1 && but != 2 && but != 4 && but != 8 && but != 16)
		return;

	if (ptinrect(t.m.xy, scrollr)) {
		scroll(but);
		if(t.qh<=t.org+t.f->nchars)
			consread();
		return;
	}
		
	switch(but) {
	case 1:
		mselect();
		break;
	case 2:
		if(button2exec){
			if(aselect(&q0, &q1, execcolor) >= 0){
				if(q0 == q1)
					wordclick(&q0, &q1);
				if(q0 == q1)
					break;
				t.q0 = t.q1 = t.nr;
				updatesel();
				paste(t.r+q0, q1-q0, 1);
				if(t.r[q1-1] != '\n')
					paste(Lnl, 1, 1);
			}
			break;
		}
		domenu2(2);
		break;
	case 4:
		bouncemouse(&t.m);
		break;
	/*
		if(aselect(&q0, &q1, plumbcolor) >= 0)
			plumb(q0, q1);
		break;
	*/
	case 8:
		scrollup(mousescrollsize(t.f->maxlines));
		break;
	case 16:
		scrolldown(mousescrollsize(t.f->maxlines));
		break;
	}
}

void
mselect(void)
{
	int b, x, y;
	uint q0;

	b = t.m.buttons;
	q0 = frcharofpt(t.f, t.m.xy) + t.org;
	if(t.m.msec-clickmsec<500 && clickq0==q0 && t.q0==t.q1 && b==1){
		doubleclick(&t.q0, &t.q1);
		updatesel();
/*		t.t.i->flush(); */
		x = t.m.xy.x;
		y = t.m.xy.y;
		/* stay here until something interesting happens */
		do {
			readmouse(mc);
			t.m = mc->m;
		} while(t.m.buttons==b && abs(t.m.xy.x-x)<4 && abs(t.m.xy.y-y)<4);
		t.m.xy.x = x;	/* in case we're calling frselect */
		t.m.xy.y = y;
		clickmsec = 0;
	}

	if(t.m.buttons == b) {
		frselect(t.f, mc);
		t.m = mc->m;
		t.q0 = t.f->p0 + t.org;
		t.q1 = t.f->p1 + t.org;
		clickmsec = t.m.msec;
		clickq0 = t.q0;
	}
	if((t.m.buttons != b) &&(b&1)){
		enum{Cancut = 1, Canpaste = 2} state = Cancut | Canpaste;
		while(t.m.buttons){
			if(t.m.buttons&2){
				if(state&Cancut){
					snarf();
					cut();
					state = Canpaste;
				}
			}else if(t.m.buttons&4){
				if(state&Canpaste){
					snarfupdate();
					if(t.nsnarf){
						paste(t.snarf, t.nsnarf, 0);
					}
					state = Cancut;
				}
			}
			readmouse(mc);
			t.m = mc->m;
		}
	}
}

Rune newline[] = { '\n', 0 };

void
domenu2(int but)
{
	if(scrolling)
		menu2str[Scroll] = "+ scroll";
	else
		menu2str[Scroll] = "- scroll";
	if(cooked)
		menu2str[Cooked] = "+ mustecho";
	else
		menu2str[Cooked] = "- mustecho";

	switch(menuhit(but, mc, &menu2, nil)){
	case -1:
		break;
	case Cut:
		snarf();
		cut();
		if(scrolling)
			show(t.q0);
		break;
	case Paste:
		snarfupdate();
		paste(t.snarf, t.nsnarf, 0);
		if(scrolling)
			show(t.q0);
		break;
	case Snarf:
		snarf();
		if(scrolling)
			show(t.q0);
		break;
	case Send:
		if(t.q0 != t.q1)
			snarf();
		else
			snarfupdate();
		t.q0 = t.q1 = t.nr;
		updatesel();
		paste(t.snarf, t.nsnarf, 1);
		if(t.nsnarf == 0 || t.snarf[t.nsnarf-1] != '\n')
			paste(newline, 1, 1);
		show(t.nr);
		consread();
		break;
	case Scroll:
		scrolling = !scrolling;
		if (scrolling) {
			show(t.nr);
			consread();
		}
		break;
	case Plumb:
		plumb(t.q0, t.q1);
		break;
	case Cooked:
		cooked = !cooked;
		break;
	default:
		sysfatal("bad menu item");
	}
}

int
windfilewidth(uint q0, int oneelement)
{
	uint q;
	Rune r;

	q = q0;
	while(q > 0){
		r = t.r[q-1];
		if(r<=' ')
			break;
		if(oneelement && r=='/')
			break;
		--q;
	}
	return q0-q;
}

void
showcandidates(Completion *c)
{
	int i;
	Fmt f;
	Rune *rp;
	uint nr, qline, q0;
	char *s;

	runefmtstrinit(&f);
	if (c->nmatch == 0)
		s = "[no matches in ";
	else
		s = "[";
	if(c->nfile > 32)
		fmtprint(&f, "%s%d files]\n", s, c->nfile);
	else{
		fmtprint(&f, "%s", s);
		for(i=0; i<c->nfile; i++){
			if(i > 0)
				fmtprint(&f, " ");
			fmtprint(&f, "%s", c->filename[i]);
		}
		fmtprint(&f, "]\n");
	}
	/* place text at beginning of line before host point */
	qline = t.qh;
	while(qline>0 && t.r[qline-1] != '\n')
		qline--;

	rp = runefmtstrflush(&f);
	nr = runestrlen(rp);

	q0 = t.q0;
	q0 += insert(rp, nr, qline, 0) - qline;
	free(rp);
	t.q0 = q0+nr;
	t.q1 = q0+nr;
	updatesel();
}

Rune*
namecomplete(void)
{
	int nstr, npath;
	Rune *rp, *path, *str;
	Completion *c;
	char *s, *dir, *root;

	/* control-f: filename completion; works back to white space or / */
	if(t.q0<t.nr && t.r[t.q0]>' ')	/* must be at end of word */
		return nil;
	nstr = windfilewidth(t.q0, TRUE);
	str = runemalloc(nstr);
	runemove(str, t.r+(t.q0-nstr), nstr);
	npath = windfilewidth(t.q0-nstr, FALSE);
	path = runemalloc(npath);
	runemove(path, t.r+(t.q0-nstr-npath), npath);
	rp = nil;

	/* is path rooted? if not, we need to make it relative to window path */
	if(npath>0 && path[0]=='/'){
		dir = malloc(UTFmax*npath+1);
		sprint(dir, "%.*S", npath, path);
	}else{
		if(strcmp(wdir, "") == 0)
			root = ".";
		else
			root = wdir;
		dir = malloc(strlen(root)+1+UTFmax*npath+1);
		sprint(dir, "%s/%.*S", root, npath, path);
	}
	dir = cleanname(dir);

	s = smprint("%.*S", nstr, str);
	c = complete(dir, s);
	free(s);
	if(c == nil)
		goto Return;

	if(!c->advance)
		showcandidates(c);

	if(c->advance)
		rp = runesmprint("%s", c->string);

  Return:
	freecompletion(c);
	free(dir);
	free(path);
	free(str);
	return rp;
}

void
scrollup(int n)
{
	setorigin(backnl(t.org, n), 1);
}

void
scrolldown(int n)
{
	setorigin(line2q(n), 1);
	if(t.qh<=t.org+t.f->nchars)
		consread();
}

void
key(Rune r)
{
	Rune *rp;
	int nr;

	if(r == 0)
		return;
	switch(r){
	case Kpgup:
		scrollup(t.f->maxlines*2/3);
		return;
	case Kpgdown:
		scrolldown(t.f->maxlines*2/3);
		return;
	case Kup:
		scrollup(t.f->maxlines/3);
		return;
	case Kdown:
		scrolldown(t.f->maxlines/3);
		return;
	case Kleft:
		if(t.q0 > 0){
			t.q0--;
			t.q1 = t.q0;
			updatesel();
			show(t.q0);
		}
		return;
	case Kright:
		if(t.q1 < t.nr){
			t.q1++;
			t.q0 = t.q1;
			updatesel();
			show(t.q1);
		}
		return;
	case Khome:
		show(0);
		return;
	case Kend:
	case 0x05:
		show(t.nr);
		return;

	/*
	 * Non-standard extensions.
	 */
	case CUT:
		snarf();
		cut();
		if(scrolling)
			show(t.q0);
		return;
	case COPY:
		snarf();
		if(scrolling)
			show(t.q0);
		return;
	case PASTE:
		snarfupdate();
		paste(t.snarf, t.nsnarf, 0);
		if(scrolling)
			show(t.q0);
		return;
	}

	switch(r) {
	/* case 0x03:	can't do this because ^C is COPY */
	case 0x7F:	/* DEL: send interrupt */
		paste(&r, 1, 1);
		t.qh = t.q0 = t.q1 = t.nr;
		show(t.q0);
		/* must write the interrupt character in case app is in raw mode (e.g., ssh) */
		write(rcfd, "\x7F", 1);
	//	postnote(PNGROUP, rcpid, "interrupt");
		return;
	}

	if(rawon() && t.q0==t.nr){
		addraw(&r, 1);
		consread();
		return;
	}

	if(r==ESC || (holdon && r==0x7F)){	/* toggle hold */
		holdon = !holdon;
		drawhold(holdon);
	//	replaceintegerproperty("_9WM_HOLD_MODE", 1, 32, holdon);
		if(!holdon)
			consread();
		if(r==ESC)
			return;
	}

	snarf();

	switch(r) {
	case 0x06:	/* ^F: file name completion */
	case Kins:		/* Insert: file name completion */
		rp = namecomplete();
		if(rp == nil)
			return;
		nr = runestrlen(rp);
		paste(rp, nr, 1);
		free(rp);
		return;
	case 0x08:	/* ^H: erase character */
	case 0x15:	/* ^U: erase line */
	case 0x17:	/* ^W: erase word */
		if (t.q0 != 0 && t.q0 != t.qh)
			t.q0 -= bswidth(r, t.q0, 1);
		cut();
		break;
	default:
		paste(&r, 1, 1);
		break;
	}
	if(scrolling)
		show(t.q0);
}

int
bswidth(Rune c, uint start, int eatnl)
{
	uint q, eq, stop;
	Rune r;
	int skipping;

	/* there is known to be at least one character to erase */
	if(c == 0x08)	/* ^H: erase character */
		return 1;
	q = start;
	stop = 0;
	if(q > t.qh)
		stop = t.qh;
	skipping = 1;
	while(q > stop){
		r = t.r[q-1];
		if(r == '\n'){		/* eat at most one more character */
			if(q == start && eatnl)	/* eat the newline */
				--q;
			break; 
		}
		if(c == 0x17){
			eq = isalnum(r);
			if(eq && skipping)	/* found one; stop skipping */
				skipping = 0;
			else if(!eq && !skipping)
				break;
		}
		--q;
	}
	return start-q;
}

int
consready(void)
{
	int i, c;

	if(holdon)
		return 0;

	if(rawon()) 
		return t.nraw != 0;

	/* look to see if there is a complete line */
	for(i=t.qh; i<t.nr; i++){
		c = t.r[i];
		if(c=='\n' || c=='\004' || c=='\x7F')
			return 1;
	}
	return 0;
}


void
consread(void)
{
	char buf[8000], *p;
	int c, width, n;
	int s, raw;

	raw = rawon();
	for(;;) {
		if(!consready())
			return;
		n = sizeof(buf);
		p = buf;
		c = 0;
		while(n >= UTFmax && (t.qh<t.nr || t.nraw > 0)) {
			if(t.qh == t.nr){
				width = runetochar(p, &t.raw[0]);
				t.nraw--;
				runemove(t.raw, t.raw+1, t.nraw);
			}else
				width = runetochar(p, &t.r[t.qh++]);
			c = *p;
			p += width;
			n -= width;
			if(!raw && (c == '\n' || c == '\004' || c == '\x7F'))
				break;
		}
		n = p-buf;

		/*
		 * If we've been echoing, make sure the terminal isn't
		 * while we do the write.  This screws up if someone 
		 * else tries to turn off echo at the same time we do
		 * (we'll turn it on again after the write), but that's not
		 * too likely.
		 */
		s = setecho(sfd, 0);
		if(write(rcfd, buf, n) < 0)
			exits(0);
		if(s)
			setecho(sfd, s);
	}
}

void
conswrite(char *p, int n)
{
	int n2, i;
	Rune buf2[1000], *q;

	/* convert to runes */
	i = t.npart;
	if(i > 0){
		/* handle partial runes */
		while(i < UTFmax && n>0) {
			t.part[i] = *p;
			i++;
			p++;
			n--;
			if(fullrune(t.part, i)) {
				t.npart = 0;
				chartorune(buf2, t.part);
				runewrite(buf2, 1);
				break;
			}
		}
		/* there is a little extra room in a message buf */
	}

	while(n >= UTFmax || fullrune(p, n)) {
		n2 = nelem(buf2);
		q = buf2;

		while(n2) {
			if(n < UTFmax && !fullrune(p, n))
				break;
			i = chartorune(q, p);
			p += i;
			n -= i;
			n2--;
			q++;
		}
		runewrite(buf2, q-buf2);
	}

	if(n != 0) {
		assert(n+t.npart < UTFmax);
		memcpy(t.part+t.npart, p, n);
		t.npart += n;
	}

	if(scrolling)
		show(t.qh);
}

void
runewrite(Rune *r, int n)
{
	int i;
	uint initial;
	uint q0, q1;
	uint p0, p1;
	Rune *p, *q;

	n = label(r, n);
	if(n == 0)
		return;

	/* get rid of backspaces */
	initial = 0;
	p = q = r;
	for(i=0; i<n; i++) {
		if(*p == '\b') {
			if(q == r)
				initial++;
			else
				--q;
		} else if(*p == '\r') {	/* treat like ^U */
			/* convert CR without NL into erased line */
			/* i feel really sleazy about this but it helps */
			while(i<n-1 && *(p+1) == '\r'){
				i++;
				p++;
			}
			if(i<n-1 && *(p+1) != '\n'){
				while(q > r && *(q-1) != '\n')
					q--;
				if(q==r)
					initial = bswidth(0x15, t.qh, 0);
			}
		} else if(*p)
			*q++ = *p;
		p++;
	}
	n = q-r;

	if(initial){
		/* write turned into a delete */

		if(initial > t.qh)
			initial = t.qh;
		q0 = t.qh-initial;
		q1 = t.qh;

		runemove(t.r+q0, t.r+q1, t.nr-q1);
		t.nr -= initial;
		t.qh -= initial;
		if(t.q0 > q1)
			t.q0 -= initial;
		else if(t.q0 > q0)
			t.q0 = q0;
		if(t.q1 > q1)
			t.q1 -= initial;
		else if(t.q1 > q0)
			t.q1 = q0;
		if(t.org > q1)
			t.org -= initial;
		else if(q0 < t.org+t.f->nchars){
			if(t.org < q0)
				p0 = q0 - t.org;
			else {
				t.org = q0;
				p0 = 0;
			}
			p1 = q1 - t.org;
			if(p1 > t.f->nchars)
				p1 = t.f->nchars;
			frdelete(t.f, p0, p1);
			fill();
		}
		updatesel();
	}

	insert(r, n, t.qh, 1);
}


void
cut(void)
{
	uint n, p0, p1;
	uint q0, q1;

	q0 = t.q0;
	q1 = t.q1;

	if (q0 < t.org && q1 >= t.org)
		show(q0);

	n = q1-q0;
	if(n == 0)
		return;
	runemove(t.r+q0, t.r+q1, t.nr-q1);
	t.nr -= n;
	t.q0 = t.q1 = q0;
	if(q1 < t.qh)
		t.qh -= n;
	else if(q0 < t.qh)
		t.qh = q0;
	if(q1 < t.org)
		t.org -= n;
	else if(q0 < t.org+t.f->nchars){
		assert(q0 >= t.org);
		p0 = q0 - t.org;
		p1 = q1 - t.org;
		if(p1 > t.f->nchars)
			p1 = t.f->nchars;
		frdelete(t.f, p0, p1);
		fill();
	}
	updatesel();
}

void
snarfupdate(void)
{
	char *pp;
	int n, i;
	Rune *p;

	pp = getsnarf();
	if(pp == nil)
		return;
	n = strlen(pp);
	if(n <= 0) {
		 /*t.nsnarf = 0;*/
		return;
	}
	t.snarf = runerealloc(t.snarf, n);
	for(i=0,p=t.snarf; i<n; p++)
		i += chartorune(p, pp+i);
	t.nsnarf = p-t.snarf;

}

char sbuf[SnarfSize];
void
snarf(void)
{
	char *p;
	int i, n;
	Rune *rp;

	if(t.q1 == t.q0)
		return;
	n = t.q1-t.q0;
	t.snarf = runerealloc(t.snarf, n);
	for(i=0,p=sbuf,rp=t.snarf; i<n && p < sbuf+SnarfSize-UTFmax; i++){
		*rp++ = *(t.r+t.q0+i);
		p += runetochar(p, t.r+t.q0+i);
	}
	t.nsnarf = rp-t.snarf;
	*p = '\0';
	putsnarf(sbuf);
}

uint
min(uint x, uint y)
{
	if(x < y)
		return x;
	return y;
}

uint
max(uint x, uint y)
{
	if(x > y)
		return x;
	return y;
}

uint
insert(Rune *r, int n, uint q0, int hostwrite)
{
	uint m;

	if(n == 0)
		return q0;
	if(t.nr+n>HiWater && q0>=t.org && q0>=t.qh){
		m = min(HiWater-LoWater, min(t.org, t.qh));
		t.org -= m;
		t.qh -= m;
		if(t.q0 > m)
			t.q0 -= m;
		else
			t.q0 = 0;
		if(t.q1 > m)
			t.q1 -= m;
		else
			t.q1 = 0;
		t.nr -= m;
		runemove(t.r, t.r+m, t.nr);
		q0 -= m;
	}
	if(t.nr+n > t.maxr){
		/*
		 * Minimize realloc breakage:
		 *	Allocate at least MinWater
		 * 	Double allocation size each time
		 *	But don't go much above HiWater
		 */
		m = max(min(2*(t.nr+n), HiWater), t.nr+n)+MinWater;
		if(m > HiWater)
			m = max(HiWater+MinWater, t.nr+n);
		if(m > t.maxr){
			t.r = runerealloc(t.r, m);
			t.maxr = m;
		}
	}
	runemove(t.r+q0+n, t.r+q0, t.nr-q0);
	runemove(t.r+q0, r, n);
	t.nr += n;
	/* if output touches, advance selection, not qh; works best for keyboard and output */
	if(q0 <= t.q1)
		t.q1 += n;
	if(q0 <= t.q0)
		t.q0 += n;
	if(q0 < t.qh || (q0==t.qh && hostwrite))
		t.qh += n;
	else
		consread();
	if(q0 < t.org)
		t.org += n;
	else if(q0 <= t.org+t.f->nchars)
		frinsert(t.f, r, r+n, q0-t.org);
	return q0;
}

void
paste(Rune *r, int n, int advance)
{
	Rune *rbuf;

	if(rawon() && t.q0==t.nr){
		addraw(r, n);
		return;
	}

	cut();
	if(n == 0)
		return;

	/*
	 * if this is a button2 execute then we might have been passed
	 * runes inside the buffer.  must save them before realloc.
	 */
	rbuf = nil;
	if(t.r <= r && r < t.r+n){
		rbuf = runemalloc(n);
		runemove(rbuf, r, n);
		r = rbuf;
	}

	insert(r, n, t.q0, 0);
	updatesel();
	free(rbuf);
}

void
fill(void)
{
	if (t.f->nlines >= t.f->maxlines)
		return;
	frinsert(t.f, t.r + t.org + t.f->nchars, t.r + t.nr, t.f->nchars);
}

void
updatesel(void)
{
	Frame *f;
	uint n;

	f = t.f;
	if(t.org+f->p0 == t.q0 && t.org+f->p1 == t.q1)
		return;

	n = t.f->nchars;

	frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 0);
	if (t.q0 >= t.org)
		f->p0 = t.q0-t.org;
	else
		f->p0 = 0;
	if(f->p0 > n)
		f->p0 = n;
	if (t.q1 >= t.org)
		f->p1 = t.q1-t.org;
	else
		f->p1 = 0;
	if(f->p1 > n)
		f->p1 = n;
	frdrawsel(f, frptofchar(f, f->p0), f->p0, f->p1, 1);

/*
	if(t.qh<=t.org+t.f.nchars && t.cwqueue != 0)
		t.cwqueue->wakeup <-= 0;
*/

	tcheck();
}

void
show(uint q0)
{
	int nl;
	uint q, oq;

	if(cansee(q0))
		return;
	
	if (q0<t.org)
		nl = t.f->maxlines/5;
	else
		nl = 4*t.f->maxlines/5;
	q = backnl(q0, nl);
	/* avoid going in the wrong direction */
	if (q0>t.org && q<t.org)
		q = t.org;
	setorigin(q, 0);
	/* keep trying until q0 is on the screen */
	while(!cansee(q0)) {
		assert(q0 >= t.org);
		oq = q;
		q = line2q(t.f->maxlines-nl);
		assert(q > oq);
		setorigin(q, 1);
	}
}

int
cansee(uint q0)
{
	uint qe;

	qe = t.org+t.f->nchars;

	if(q0>=t.org && q0 < qe)
		return 1;
	if (q0 != qe)
		return 0;
	if (t.f->nlines < t.f->maxlines)
		return 1;
	if (q0 > 0 && t.r[t.nr-1] == '\n')
		return 0;
	return 1;
}


void
setorigin(uint org, int exact)
{
	int i, a;
	uint n;
	
	if(org>0 && !exact){
		/* try and start after a newline */
		/* don't try harder than 256 chars */
		for(i=0; i<256 && org<t.nr; i++){
			if(t.r[org-1] == '\n')
				break;
			org++;
		}
	}
	a = org-t.org;

	if(a>=0 && a<t.f->nchars)
		frdelete(t.f, 0, a);
	else if(a<0 && -a<100*t.f->maxlines){
		n = t.org - org;
		frinsert(t.f, t.r+org, t.r+org+n, 0);
	}else
		frdelete(t.f, 0, t.f->nchars);
	t.org = org;
	fill();
	updatesel();
}


uint
line2q(uint n)
{
	Frame *f;

	f = t.f;
	return frcharofpt(f, Pt(f->r.min.x, f->r.min.y + n*font->height))+t.org;
}

uint
backnl(uint p, uint n)
{
	int i, j;

	for (i = n;; i--) {
		/* at 256 chars, call it a line anyway */
		for(j=256; --j>0 && p>0; p--)
			if(t.r[p-1]=='\n')
				break;
		if (p == 0 || i == 0)
			return p;
		p--;
	}
	return 0; /* alef bug */
}

void
addraw(Rune *r, int nr)
{
	t.raw = runerealloc(t.raw, t.nraw+nr);
	runemove(t.raw+t.nraw, r, nr);
	t.nraw += nr;
/*
	if(t.crqueue != nil)
		t.crqueue->wakeup <-= 0;
*/	
}


Rune left1[] =  { '{', '[', '(', '<', 0xab, 0 };
Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
Rune left2[] =  { '\n', 0 };
Rune left3[] =  { '\'', '"', '`', 0 };

Rune *left[] = {
	left1,
	left2,
	left3,
	0
};

Rune *right[] = {
	right1,
	left2,
	left3,
	0
};

void
doubleclick(uint *q0, uint *q1)
{
	int c, i;
	Rune *r, *l, *p;
	uint q;

	for(i=0; left[i]!=0; i++){
		q = *q0;
		l = left[i];
		r = right[i];
		/* try matching character to left, looking right */
		if(q == 0)
			c = '\n';
		else
			c = t.r[q-1];
		p = strrune(l, c);
		if(p != 0){
			if(clickmatch(c, r[p-l], 1, &q))
				*q1 = q-(c!='\n');
			return;
		}
		/* try matching character to right, looking left */
		if(q == t.nr)
			c = '\n';
		else
			c = t.r[q];
		p = strrune(r, c);
		if(p != 0){
			if(clickmatch(c, l[p-r], -1, &q)){
				*q1 = *q0+(*q0<t.nr && c=='\n');
				*q0 = q;
				if(c!='\n' || q!=0 || t.r[0]=='\n')
					(*q0)++;
			}
			return;
		}
	}
	/* try filling out word to right */
	while(*q1<t.nr && isalnum(t.r[*q1]))
		(*q1)++;
	/* try filling out word to left */
	while(*q0>0 && isalnum(t.r[*q0-1]))
		(*q0)--;
}

int
clickmatch(int cl, int cr, int dir, uint *q)
{
	Rune c;
	int nest;

	nest = 1;
	for(;;){
		if(dir > 0){
			if(*q == t.nr)
				break;
			c = t.r[*q];
			(*q)++;
		}else{
			if(*q == 0)
				break;
			(*q)--;
			c = t.r[*q];
		}
		if(c == cr){
			if(--nest==0)
				return 1;
		}else if(c == cl)
			nest++;
	}
	return cl=='\n' && nest==1;
}

void
tcheck(void)
{
	Frame *f;
		
	f = t.f;

	assert(t.q0 <= t.q1 && t.q1 <= t.nr);
	assert(t.org <= t.nr && t.qh <= t.nr);
	assert(f->p0 <= f->p1 && f->p1 <= f->nchars);
	assert(t.org + f->nchars <= t.nr);
	assert(t.org+f->nchars==t.nr || (f->nlines >= f->maxlines));
}

Rune*
strrune(Rune *s, Rune c)
{
	Rune c1;

	if(c == 0) {
		while(*s++)
			;
		return s-1;
	}

	while(c1 = *s++)
		if(c1 == c)
			return s-1;
	return 0;
}

void
scrdraw(void)
{
	Rectangle r, r1, r2;
	static Image *scrx;

	r = scrollr;
	r.min.x += 1;	/* border between margin and bar */
	r1 = r;
	if(scrx==0 || scrx->r.max.y < r.max.y){
		if(scrx)
			freeimage(scrx);
		scrx = allocimage(display, Rect(0, 0, 32, r.max.y), screen->chan, 1, DPaleyellow);
		if(scrx == 0)
			sysfatal("scroll balloc");
	}
	r1.min.x = 0;
	r1.max.x = Dx(r);
	r2 = scrpos(r1, t.org, t.org+t.f->nchars, t.nr);
	if(!eqrect(r2, lastsr)){
		lastsr = r2;
		draw(scrx, r1, cols[BORD], nil, ZP);
		draw(scrx, r2, cols[BACK], nil, r2.min);
//		r2 = r1;
//		r2.min.x = r2.max.x-1;
//		draw(scrx, r2, cols[BORD], nil, ZP);
		draw(screen, r, scrx, nil, r1.min);
	}
}

Rectangle
scrpos(Rectangle r, ulong p0, ulong p1, ulong tot)
{
	long h;
	Rectangle q;

	q = insetrect(r, 1);
	h = q.max.y-q.min.y;
	if(tot == 0)
		return q;
	if(tot > 1024L*1024L)
		tot >>= 10, p0 >>= 10, p1 >>= 10;
	if(p0 > 0)
		q.min.y += h*p0/tot;
	if(p1 < tot)
		q.max.y -= h*(tot-p1)/tot;
	if(q.max.y < q.min.y+2){
		if(q.min.y+2 <= r.max.y)
			q.max.y = q.min.y+2;
		else
			q.min.y = q.max.y-2;
	}
	return q;
}

void
scroll(int but)
{
	uint p0, oldp0;
	Rectangle s;
	int x, y, my, h, first, exact;

	s = insetrect(scrollr, 1);
	h = s.max.y-s.min.y;
	x = (s.min.x+s.max.x)/2;
	oldp0 = ~0;
	first = 1;
	do{
		if(t.m.xy.x<s.min.x || s.max.x<=t.m.xy.x){
			readmouse(mc);
			t.m = mc->m;
		}else{
			my = t.m.xy.y;
			if(my < s.min.y)
				my = s.min.y;
			if(my >= s.max.y)
				my = s.max.y;
//			if(!eqpt(t.m.xy, Pt(x, my)))
//				cursorset(Pt(x, my));
			exact = 1;
			if(but == 2){
				y = my;
				if(y > s.max.y-2)
					y = s.max.y-2;
				if(t.nr > 1024*1024)
					p0 = ((t.nr>>10)*(y-s.min.y)/h)<<10;
				else
					p0 = t.nr*(y-s.min.y)/h;
				exact = 0;
			} else if(but == 1)
				p0 = backnl(t.org, (my-s.min.y)/font->height);
			else 
				p0 = t.org+frcharofpt(t.f, Pt(s.max.x, my));

			if(oldp0 != p0)
				setorigin(p0, exact);
			oldp0 = p0;
			scrdraw();
			readmouse(mc);
			t.m = mc->m;
		}
	}while(t.m.buttons & (1<<(but-1)));
}

void
plumbstart(void)
{
	if((plumbfd = plumbopen("send", OWRITE)) < 0)
		fprint(2, "9term: plumbopen: %r\n");
}

void
plumb(uint q0, uint q1)
{
	Plumbmsg *pm;
	char *p;
	int i, p0, n;
	char cbuf[100];

	pm = malloc(sizeof(Plumbmsg));
	pm->src = strdup("9term");
	pm->dst = 0;
	pm->wdir = strdup(wdir);
	pm->type = strdup("text");
	pm->data = nil;
	if(q1 > q0)
		pm->attr = nil;
	else{
		p0 = q0;
		wordclick(&q0, &q1);
		sprint(cbuf, "click=%d", p0-q0);
		pm->attr = plumbunpackattr(cbuf);
	}
	if(q0==q1){
		plumbfree(pm);
		return;
	}
	pm->data = malloc(SnarfSize);
	n = q1 - q0;
	for(i=0,p=pm->data; i<n && p < pm->data + SnarfSize-UTFmax; i++)
		p += runetochar(p, t.r+q0+i);
	*p = '\0';
	pm->ndata = strlen(pm->data);
	plumbsend(plumbfd, pm);
	plumbfree(pm);
}

/*
 * Process in-band messages about window title changes.
 * The messages are of the form:
 *
 *	\033];xxx\007
 *
 * where xxx is the new directory.  This format was chosen
 * because it changes the label on xterm windows.
 */
int
label(Rune *sr, int n)
{
	Rune *sl, *el, *er, *r;

	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)
		sr = el - sizeof wdir;
	for(sl=el-3; sl>=sr; sl--)
		if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
			break;
	if(sl < sr)
		return n;

	snprint(wdir, sizeof wdir, "%.*S", (el-1)-(sl+3), sl+3);
	drawsetlabel(display, wdir);

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

int
rawon(void)
{
	return !cooked && !isecho(sfd);
}

/*
 * Clumsy hack to make " and "" work.
 * Then again, what's not a clumsy hack here in Unix land?
 */

char adir[100];
int afd;

void
removethesocket(void)
{
	if(thesocket[0])
		if(remove(thesocket) < 0)
			fprint(2, "remove %s: %r\n", thesocket);
}

void
servedevtext(void)
{
	char buf[100];

	snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());

	if((afd = announce(buf, adir)) < 0){
		putenv("text9term", "");
		return;
	}

	putenv("text9term", buf);
	threadcreate(listenthread, nil, STACK);
	strcpy(thesocket, buf+5);
	atexit(removethesocket);
}

void
listenthread(void *arg)
{
	int fd;
	char dir[100];

	USED(arg);
	for(;;){
		fd = threadlisten(adir, dir);
		if(fd < 0){
			close(afd);
			return;
		}
		threadcreate(textthread, (void*)fd, STACK);
	}
}

void
textthread(void *arg)
{
	int fd, i, x, n, end;
	Rune r;
	char buf[4096], *p, *ep;

	fd = (int)arg;
	p = buf;
	ep = buf+sizeof buf;
	end = t.org+t.nr;	/* avoid possible output loop */
	for(i=t.org;; i++){
		if(i >= end || ep-p < UTFmax){
			for(x=0; x<p-buf; x+=n)
				if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
					goto break2;
			
			if(i >= end)
				break;
			p = buf;
		}
		if(i < t.org)
			i = t.org;
		r = t.r[i-t.org];
		if(r < Runeself)
			*p++ = r;
		else
			p += runetochar(p, &r);
	}
break2:
	close(fd);
}
