#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include "flayer.h"
#include "samterm.h"

#define	HSIZE	3	/* Type + short count */
Header	h;
uchar	indata[DATASIZE+1];	/* room for NUL */
uchar	outdata[DATASIZE];
short	outcount;
int	hversion;
int	exiting;

void	inmesg(Hmesg, int);
int	inshort(int);
long	inlong(int);
vlong	invlong(int);
void	hsetdot(int, long, long);
void	hmoveto(int, long);
void	hsetsnarf(int);
void	hplumb(int);
void	clrlock(void);
int	snarfswap(char*, int, char**);

void
rcv(void)
{
	int c;
	static state = 0;
	static count = 0;
	static i = 0;
	static int errs = 0;

	while((c=rcvchar()) != -1)
		switch(state){
		case 0:
			h.type = c;
			state++;
			break;

		case 1:
			h.count0 = c;
			state++;
			break;

		case 2:
			h.count1 = c;
			count = h.count0|(h.count1<<8);
			i = 0;
			if(count > DATASIZE){
				if(++errs < 5){
					dumperrmsg(count, h.type, h.count0, c);
					state = 0;
					continue;
				}
				fprint(2, "type %d count %d\n", h.type, count);
				panic("count>DATASIZE");
			}
			if(count == 0)
				goto zerocount;
			state++;
			break;

		case 3:
			indata[i++] = c;
			if(i == count){
		zerocount:
				indata[i] = 0;
				inmesg(h.type, count);
				state = count = 0;
				continue;
			}
			break;
		}
}

Text *
whichtext(int tg)
{
	int i;

	for(i=0; i<nname; i++)
		if(tag[i] == tg)
			return text[i];
	panic("whichtext");
	return 0;
}

void
inmesg(Hmesg type, int count)
{
	Text *t;
	int i, m;
	long l;
	Flayer *lp;

	m = inshort(0);
	l = inlong(2);
	switch(type){
	case -1:
		panic("rcv error");
	default:
		fprint(2, "type %d\n", type);
		panic("rcv unknown");

	case Hversion:
		hversion = m;
		break;

	case Hbindname:
		l = invlong(2);		/* for 64-bit pointers */
		if((i=whichmenu(m)) < 0)
			break;
		/* in case of a race, a bindname may already have occurred */
		if((t=whichtext(m)) == 0)
			t=(Text *)l;
		else	/* let the old one win; clean up the new one */
			while(((Text *)l)->nwin>0)
				closeup(&((Text *)l)->l[((Text *)l)->front]);
		text[i] = t;
		text[i]->tag = m;
		break;

	case Hcurrent:
		if(whichmenu(m)<0)
			break;
		t = whichtext(m);
		i = which && ((Text *)which->user1)==&cmd && m!=cmd.tag;
		if(t==0 && (t = sweeptext(0, m))==0)
			break;
		if(t->l[t->front].textfn==0)
			panic("Hcurrent");
		lp = &t->l[t->front];
		if(i){
			flupfront(lp);
			flborder(lp, 0);
			work = lp;
		}else
			current(lp);
		break;

	case Hmovname:
		if((m=whichmenu(m)) < 0)
			break;
		t = text[m];
		l = tag[m];
		i = name[m][0];
		text[m] = 0;	/* suppress panic in menudel */
		menudel(m);
		if(t == &cmd)
			m = 0;
		else{
			if (nname>0 && text[0]==&cmd)
				m = 1;
			else m = 0;
			for(; m<nname; m++)
				if(strcmp((char*)indata+2, (char*)name[m]+1)<0)
					break;
		}
		menuins(m, indata+2, t, i, (int)l);
		break;

	case Hgrow:
		if(whichmenu(m) >= 0)
			hgrow(m, l, inlong(6), 1);
		break;

	case Hnewname:
		menuins(0, (uchar *)"", (Text *)0, ' ', m);
		break;

	case Hcheck0:
		i = whichmenu(m);
		if(i>=0) {
			t = text[i];
			if(t)
				t->lock++;
			outTs(Tcheck, m);
		}
		break;

	case Hcheck:
		i = whichmenu(m);
		if(i>=0) {
			t = text[i];
			if(t && t->lock)
				t->lock--;
			hcheck(m);
		}
		break;

	case Hunlock:
		clrlock();
		break;

	case Hdata:
		if(whichmenu(m) >= 0)
			l += hdata(m, l, indata+6, count-6);
	Checkscroll:
		if(m == cmd.tag){
			for(i=0; i<NL; i++){
				lp = &cmd.l[i];
				if(lp->textfn)
					center(lp, l>=0? l : lp->p1);
			}
		}
		break;

	case Horigin:
		if(whichmenu(m) >= 0)
			horigin(m, l);
		break;

	case Hunlockfile:
		if(whichmenu(m)>=0 && (t = whichtext(m))->lock){
			--t->lock;
			l = -1;
			goto Checkscroll;
		}
		break;

	case Hsetdot:
		if(whichmenu(m) >= 0)
			hsetdot(m, l, inlong(6));
		break;

	case Hgrowdata:
		if(whichmenu(m)<0)
			break;
		hgrow(m, l, inlong(6), 0);
		whichtext(m)->lock++;	/* fake the request */
		l += hdata(m, l, indata+10, count-10);
		goto Checkscroll;

	case Hmoveto:
		if(whichmenu(m)>=0)
			hmoveto(m, l);
		break;

	case Hclean:
		if((m = whichmenu(m)) >= 0)
			name[m][0] = ' ';
		break;

	case Hdirty:
		if((m = whichmenu(m))>=0)
			name[m][0] = '\'';
		break;

	case Hdelname:
		if((m=whichmenu(m)) >= 0)
			menudel(m);
		break;

	case Hcut:
		if(whichmenu(m) >= 0)
			hcut(m, l, inlong(6));
		break;

	case Hclose:
		if(whichmenu(m)<0 || (t = whichtext(m))==0)
			break;
		l = t->nwin;
		for(i = 0,lp = t->l; l>0 && i<NL; i++,lp++)
			if(lp->textfn){
				closeup(lp);
				--l;
			}
		break;

	case Hsetpat:
		setpat((char *)indata);
		break;

	case Hsetsnarf:
		hsetsnarf(m);
		break;

	case Hsnarflen:
		snarflen = inlong(0);
		break;

	case Hack:
		outT0(Tack);
		break;

	case Hexit:
		exiting = 1;
		outT0(Texit);
		threadexitsall(nil);
		break;

	case Hplumb:
		hplumb(m);
		break;

	case Hmenucmd:
		menucmd((char *)indata);
		break;
	}
}

void
setlock(void)
{
	hostlock++;
	setcursor(mousectl, cursor = &lockarrow);
}

void
clrlock(void)
{
	hasunlocked = 1;
	if(hostlock > 0)
		hostlock--;
	if(hostlock == 0)
		setcursor(mousectl, cursor=(Cursor *)0);
}

void
startfile(Text *t)
{
	outTsv(Tstartfile, t->tag, (vlong)t);	/* for 64-bit pointers */
	setlock();
}

void
startnewfile(int type, Text *t)
{
	t->tag = Untagged;
	outTv(type, (vlong)t);			/* for 64-bit pointers */
}

int
inshort(int n)
{
	return indata[n]|(indata[n+1]<<8);
}

long
inlong(int n)
{
	return indata[n]|(indata[n+1]<<8)|
		((long)indata[n+2]<<16)|((long)indata[n+3]<<24);
}

vlong
invlong(int n)
{
	vlong v;

	v = (indata[n+7]<<24) | (indata[n+6]<<16) | (indata[n+5]<<8) | indata[n+4];
	v = (v<<16) | (indata[n+3]<<8) | indata[n+2];
	v = (v<<16) | (indata[n+1]<<8) | indata[n];
	return v;
}

void
outT0(Tmesg type)
{
	outstart(type);
	outsend();
}

void
outTl(Tmesg type, long l)
{
	outstart(type);
	outlong(l);
	outsend();
}

void
outTs(Tmesg type, int s)
{
	outstart(type);
	outshort(s);
	outsend();
}

void
outTss(Tmesg type, int s1, int s2)
{
	outstart(type);
	outshort(s1);
	outshort(s2);
	outsend();
}

void
outTsll(Tmesg type, int s1, long l1, long l2)
{
	outstart(type);
	outshort(s1);
	outlong(l1);
	outlong(l2);
	outsend();
}

void
outTsl(Tmesg type, int s1, long l1)
{
	outstart(type);
	outshort(s1);
	outlong(l1);
	outsend();
}

void
outTsv(Tmesg type, int s1, vlong v1)
{
	outstart(type);
	outshort(s1);
	outvlong(v1);
	outsend();
}

void
outTv(Tmesg type, vlong v1)
{
	outstart(type);
	outvlong(v1);
	outsend();
}

void
outTslS(Tmesg type, int s1, long l1, Rune *s)
{
	char buf[DATASIZE*UTFmax+1];
	char *c;

	outstart(type);
	outshort(s1);
	outlong(l1);
	c = buf;
	while(*s)
		c += runetochar(c, s++);
	*c++ = 0;
	outcopy(c-buf, (uchar *)buf);
	outsend();
}

void
outTsls(Tmesg type, int s1, long l1, int s2)
{
	outstart(type);
	outshort(s1);
	outlong(l1);
	outshort(s2);
	outsend();
}

void
outstart(Tmesg type)
{
	outdata[0] = type;
	outcount = 0;
}

void
outcopy(int count, uchar *data)
{
	while(count--)
		outdata[HSIZE+outcount++] = *data++;	
}

void
outshort(int s)
{
	uchar buf[2];

	buf[0]=s;
	buf[1]=s>>8;
	outcopy(2, buf);
}

void
outlong(long l)
{
	uchar buf[4];

	buf[0]=l;
	buf[1]=l>>8;
	buf[2]=l>>16;
	buf[3]=l>>24;
	outcopy(4, buf);
}

void
outvlong(vlong v)
{
	int i;
	uchar buf[8];

	for(i = 0; i < sizeof(buf); i++){
		buf[i] = v;
		v >>= 8;
	}

	outcopy(8, buf);
}

void
outsend(void)
{
	if(outcount>DATASIZE-HSIZE)
		panic("outcount>sizeof outdata");
	outdata[1]=outcount;
	outdata[2]=outcount>>8;
	if(write(1, (char *)outdata, outcount+HSIZE)!=outcount+HSIZE)
		panic("write error");
}


void
hsetdot(int m, long p0, long p1)
{
	Text *t = whichtext(m);
	Flayer *l = &t->l[t->front];

	flushtyping(1);
	flsetselect(l, p0, p1);
}

void
horigin(int m, long p0)
{
	Text *t = whichtext(m);
	Flayer *l = &t->l[t->front];
	long a;
	ulong n;
	Rune *r;

	if(!flprepare(l)){
		l->origin = p0;
		return;
	}
	a = p0-l->origin;
	if(a>=0 && a<l->f.nchars)
		frdelete(&l->f, 0, a);
	else if(a<0 && -a<l->f.nchars){
		r = rload(&t->rasp, p0, l->origin, &n);
		frinsert(&l->f, r, r+n, 0);
	}else
		frdelete(&l->f, 0, l->f.nchars);
	l->origin = p0;
	scrdraw(l, t->rasp.nrunes);
	if(l->visible==Some)
		flrefresh(l, l->entire, 0);
	hcheck(m);
}

void
hmoveto(int m, long p0)
{
	Text *t = whichtext(m);
	Flayer *l = &t->l[t->front];

	if(p0<l->origin || p0-l->origin>l->f.nchars*9/10)
		outTsll(Torigin, m, p0, 2L);
}

void
hcheck(int m)
{
	Flayer *l;
	Text *t;
	int reqd = 0, i;
	long n, nl, a;
	Rune *r;

	if(m == Untagged)
		return;
	t = whichtext(m);
	if(t == 0)		/* possible in a half-built window */
		return;
	for(l = &t->l[0], i = 0; i<NL; i++, l++){
		if(l->textfn==0 || !flprepare(l))	/* BUG: don't
							   need this if BUG below
							   is fixed */
			continue;
		a = t->l[i].origin;
		n = rcontig(&t->rasp, a, a+l->f.nchars, 1);
		if(n<l->f.nchars)	/* text missing in middle of screen */
			a+=n;
		else{			/* text missing at end of screen? */
        Again:
		 	if(l->f.lastlinefull)
				goto Checksel;	/* all's well */
			a = t->l[i].origin+l->f.nchars;
			n = t->rasp.nrunes-a;
			if(n==0)
				goto Checksel;
			if(n>TBLOCKSIZE)
				n = TBLOCKSIZE;
			n = rcontig(&t->rasp, a, a+n, 1);
			if(n>0){
				rload(&t->rasp, a, a+n, 0);
				nl = l->f.nchars;
				r = scratch;
				flinsert(l, r, r+n, l->origin+nl);
				if(nl == l->f.nchars)	/* made no progress */
					goto Checksel;
				goto Again;
			}
		}
		if(!reqd){
			n = rcontig(&t->rasp, a, a+TBLOCKSIZE, 0);
			if(n <= 0)
				panic("hcheck request==0");
			outTsls(Trequest, m, a, (int)n);
			outTs(Tcheck, m);
			t->lock++;	/* for the Trequest */
			t->lock++;	/* for the Tcheck */
			reqd++;
			continue;
		}
	    Checksel:
		flsetselect(l, l->p0, l->p1);
	}
}

void
flnewlyvisible(Flayer *l)
{
	hcheck(((Text *)l->user1)->tag);
}

void
hsetsnarf(int nc)
{
	char *s2;
	char *s1;
	int i;
	int n;

	setcursor(mousectl, &deadmouse);
	s2 = alloc(nc+1);
	for(i=0; i<nc; i++)
		s2[i] = getch();
	s2[nc] = 0;
	n = snarfswap(s2, nc, &s1);
	if(n >= 0){
		if(!s1)
			n = 0;
		s1 = realloc(s1, n+1);
		if (!s1)
			panic("realloc");
		s1[n] = 0;
		snarflen = n;
		outTs(Tsetsnarf, n);
		if(n>0 && write(1, s1, n)!=n)
			panic("snarf write error");
		free(s1);
	}else
		outTs(Tsetsnarf, 0);
	free(s2);
	setcursor(mousectl, cursor);
}

void
hplumb(int nc)
{
	int i;
	char *s;
	Plumbmsg *m;

	s = alloc(nc);
	for(i=0; i<nc; i++)
		s[i] = getch();
	if(plumbfd >= 0){
		m = plumbunpack(s, nc);
		if(m != 0)
			plumbsend(plumbfd, m);
		plumbfree(m);
	}
	free(s);
}

void
hgrow(int m, long a, long new, int req)
{
	int i;
	Flayer *l;
	Text *t = whichtext(m);
	long o, b;

	if(new <= 0)
		panic("hgrow");
	rresize(&t->rasp, a, 0L, new);
	for(l = &t->l[0], i = 0; i<NL; i++, l++){
		if(l->textfn == 0)
			continue;
		o = l->origin;
		b = a-o-rmissing(&t->rasp, o, a);
		if(a < o)
			l->origin+=new;
		if(a < l->p0)
			l->p0+=new;
		if(a < l->p1)
			l->p1+=new;
		/* must prevent b temporarily becoming unsigned */
		if(!req || a<o || (b>0 && b>l->f.nchars) ||
		    (l->f.nchars==0 && a-o>0))
			continue;
		if(new>TBLOCKSIZE)
			new = TBLOCKSIZE;
		outTsls(Trequest, m, a, (int)new);
		t->lock++;
		req = 0;
	}
}

int
hdata1(Text *t, long a, Rune *r, int len)
{
	int i;
	Flayer *l;
	long o, b;

	for(l = &t->l[0], i=0; i<NL; i++, l++){
		if(l->textfn==0)
			continue;
		o = l->origin;
		b = a-o-rmissing(&t->rasp, o, a);
		/* must prevent b temporarily becoming unsigned */
		if(a<o || (b>0 && b>l->f.nchars))
			continue;
		flinsert(l, r, r+len, o+b);
	}
	rdata(&t->rasp, a, a+len, r);
	rclean(&t->rasp);
	return len;
}

int
hdata(int m, long a, uchar *s, int len)
{
	int i, w;
	Text *t = whichtext(m);
	Rune buf[DATASIZE], *r;

	if(t->lock)
		--t->lock;
	if(len == 0)
		return 0;
	r = buf;
	for(i=0; i<len; i+=w,s+=w)
		w = chartorune(r++, (char*)s);
	return hdata1(t, a, buf, r-buf);
}

int
hdatarune(int m, long a, Rune *r, int len)
{
	Text *t = whichtext(m);

	if(t->lock)
		--t->lock;
	if(len == 0)
		return 0;
	return hdata1(t, a, r, len);
}

void
hcut(int m, long a, long old)
{
	Flayer *l;
	Text *t = whichtext(m);
	int i;
	long o, b;

	if(t->lock)
		--t->lock;
	for(l = &t->l[0], i = 0; i<NL; i++, l++){
		if(l->textfn == 0)
			continue;
		o = l->origin;
		b = a-o-rmissing(&t->rasp, o, a);
		/* must prevent b temporarily becoming unsigned */
		if((b<0 || b<l->f.nchars) && a+old>=o){
			fldelete(l, b<0? o : o+b,
			    a+old-rmissing(&t->rasp, o, a+old));
		}
		if(a+old<o)
			l->origin-=old;
		else if(a<=o)
			l->origin = a;
		if(a+old<l->p0)
			l->p0-=old;
		else if(a<=l->p0)
			l->p0 = a;
		if(a+old<l->p1)
			l->p1-=old;
		else if(a<=l->p1)
			l->p1 = a;
	}
	rresize(&t->rasp, a, old, 0L);
	rclean(&t->rasp);
}
