#include "sam.h"

Header	h;
uchar	indata[DATASIZE];
uchar	outdata[2*DATASIZE+3];	/* room for overflow message */
uchar	*inp;
uchar	*outp;
uchar	*outmsg = outdata;
Posn	cmdpt;
Posn	cmdptadv;
Buffer	snarfbuf;
int	waitack;
int	outbuffered;
int	tversion;

int	inshort(void);
long	inlong(void);
vlong	invlong(void);
int	inmesg(Tmesg);

void	outshort(int);
void	outlong(long);
void	outvlong(vlong);
void	outcopy(int, void*);
void	outsend(void);
void	outstart(Hmesg);

void	setgenstr(File*, Posn, Posn);

#ifdef DEBUG
char *hname[] = {
	[Hversion]	"Hversion",
	[Hbindname]	"Hbindname",
	[Hcurrent]	"Hcurrent",
	[Hnewname]	"Hnewname",
	[Hmovname]	"Hmovname",
	[Hgrow]		"Hgrow",
	[Hcheck0]	"Hcheck0",
	[Hcheck]	"Hcheck",
	[Hunlock]	"Hunlock",
	[Hdata]		"Hdata",
	[Horigin]	"Horigin",
	[Hunlockfile]	"Hunlockfile",
	[Hsetdot]	"Hsetdot",
	[Hgrowdata]	"Hgrowdata",
	[Hmoveto]	"Hmoveto",
	[Hclean]	"Hclean",
	[Hdirty]	"Hdirty",
	[Hcut]		"Hcut",
	[Hsetpat]	"Hsetpat",
	[Hdelname]	"Hdelname",
	[Hclose]	"Hclose",
	[Hsetsnarf]	"Hsetsnarf",
	[Hsnarflen]	"Hsnarflen",
	[Hack]		"Hack",
	[Hexit]		"Hexit",
	[Hplumb]		"Hplumb",
};

char *tname[] = {
	[Tversion]	"Tversion",
	[Tstartcmdfile]	"Tstartcmdfile",
	[Tcheck]	"Tcheck",
	[Trequest]	"Trequest",
	[Torigin]	"Torigin",
	[Tstartfile]	"Tstartfile",
	[Tworkfile]	"Tworkfile",
	[Ttype]		"Ttype",
	[Tcut]		"Tcut",
	[Tpaste]	"Tpaste",
	[Tsnarf]	"Tsnarf",
	[Tstartnewfile]	"Tstartnewfile",
	[Twrite]	"Twrite",
	[Tclose]	"Tclose",
	[Tlook]		"Tlook",
	[Tsearch]	"Tsearch",
	[Tsend]		"Tsend",
	[Tdclick]	"Tdclick",
	[Tstartsnarf]	"Tstartsnarf",
	[Tsetsnarf]	"Tsetsnarf",
	[Tack]		"Tack",
	[Texit]		"Texit",
	[Tplumb]		"Tplumb",
};

void
journal(int out, char *s)
{
	static int fd = 0;

	if(fd <= 0)
		fd = create("/tmp/sam.out", 1, 0666L);
	fprint(fd, "%s%s\n", out? "out: " : "in:  ", s);
}

void
journaln(int out, long n)
{
	char buf[32];

	snprint(buf, sizeof(buf), "%ld", n);
	journal(out, buf);
}

void
journalv(int out, vlong v)
{
	char buf[32];

	sprint(buf, sizeof(buf), "%lld", v);
	journal(out, buf);
}
#else
#define	journal(a, b)
#define journaln(a, b)
#define journalv(a, b)
#endif

int
rcvchar(void){
	static uchar buf[64];
	static i, nleft = 0;

	if(nleft <= 0){
		nleft = read(0, (char *)buf, sizeof buf);
		if(nleft <= 0)
			return -1;
		i = 0;
	}
	--nleft;
	return buf[i++];
}

int
rcv(void){
	int c;
	static state = 0;
	static count = 0;
	static i = 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)
				panic("count>DATASIZE");
			if(count == 0)
				goto zerocount;
			state++;
			break;

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

File *
whichfile(int tag)
{
	int i;

	for(i = 0; i<file.nused; i++)
		if(file.filepptr[i]->tag==tag)
			return file.filepptr[i];
	hiccough((char *)0);
	return 0;
}

int
inmesg(Tmesg type)
{
	Rune buf[1025];
	char cbuf[64];
	int i, m;
	short s;
	long l, l1;
	vlong v;
	File *f;
	Posn p0, p1, p;
	Range r;
	String *str;
	char *c, *wdir;
	Rune *rp;
	Plumbmsg *pm;

	if(type > TMAX)
		panic("inmesg");

	journal(0, tname[type]);

	inp = indata;
	switch(type){
	case -1:
		panic("rcv error");

	default:
		fprint(2, "unknown type %d\n", type);
		panic("rcv unknown");

	case Tversion:
		tversion = inshort();
		journaln(0, tversion);
		break;

	case Tstartcmdfile:
		v = invlong();		/* for 64-bit pointers */
		journalv(0, v);
		Strdupl(&genstr, samname);
		cmd = newfile();
		cmd->unread = 0;
		outTsv(Hbindname, cmd->tag, v);
		outTs(Hcurrent, cmd->tag);
		logsetname(cmd, &genstr);
		cmd->rasp = listalloc('P');
		cmd->mod = 0;
		if(cmdstr.n){
			loginsert(cmd, 0L, cmdstr.s, cmdstr.n);
			Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
		}
		fileupdate(cmd, FALSE, TRUE);
		outT0(Hunlock);
		break;

	case Tcheck:
		/* go through whichfile to check the tag */
		outTs(Hcheck, whichfile(inshort())->tag);
		break;

	case Trequest:
		f = whichfile(inshort());
		p0 = inlong();
		p1 = p0+inshort();
		journaln(0, p0);
		journaln(0, p1-p0);
		if(f->unread)
			panic("Trequest: unread");
		if(p1>f->nc)
			p1 = f->nc;
		if(p0>f->nc) /* can happen e.g. scrolling during command */
			p0 = f->nc;
		if(p0 == p1){
			i = 0;
			r.p1 = r.p2 = p0;
		}else{
			r = rdata(f->rasp, p0, p1-p0);
			i = r.p2-r.p1;
			bufread(f, r.p1, buf, i);
		}
		buf[i]=0;
		outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
		break;

	case Torigin:
		s = inshort();
		l = inlong();
		l1 = inlong();
		journaln(0, l1);
		lookorigin(whichfile(s), l, l1);
		break;

	case Tstartfile:
		termlocked++;
		f = whichfile(inshort());
		if(!f->rasp)	/* this might be a duplicate message */
			f->rasp = listalloc('P');
		current(f);
		outTsv(Hbindname, f->tag, invlong());	/* for 64-bit pointers */
		outTs(Hcurrent, f->tag);
		journaln(0, f->tag);
		if(f->unread)
			load(f);
		else{
			if(f->nc>0){
				rgrow(f->rasp, 0L, f->nc);
				outTsll(Hgrow, f->tag, 0L, f->nc);
			}
			outTs(Hcheck0, f->tag);
			moveto(f, f->dot.r);
		}
		break;

	case Tworkfile:
		i = inshort();
		f = whichfile(i);
		current(f);
		f->dot.r.p1 = inlong();
		f->dot.r.p2 = inlong();
		f->tdot = f->dot.r;
		journaln(0, i);
		journaln(0, f->dot.r.p1);
		journaln(0, f->dot.r.p2);
		break;

	case Ttype:
		f = whichfile(inshort());
		p0 = inlong();
		journaln(0, p0);
		journal(0, (char*)inp);
		str = tmpcstr((char*)inp);
		i = str->n;
		loginsert(f, p0, str->s, str->n);
		if(fileupdate(f, FALSE, FALSE))
			seq++;
		if(f==cmd && p0==f->nc-i && i>0 && str->s[i-1]=='\n'){
			freetmpstr(str);
			termlocked++;
			termcommand();
		}else
			freetmpstr(str);
		f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
		f->tdot = f->dot.r;
		break;

	case Tcut:
		f = whichfile(inshort());
		p0 = inlong();
		p1 = inlong();
		journaln(0, p0);
		journaln(0, p1);
		logdelete(f, p0, p1);
		if(fileupdate(f, FALSE, FALSE))
			seq++;
		f->dot.r.p1 = f->dot.r.p2 = p0;
		f->tdot = f->dot.r;   /* terminal knows the value of dot already */
		break;

	case Tpaste:
		f = whichfile(inshort());
		p0 = inlong();
		journaln(0, p0);
		for(l=0; l<snarfbuf.nc; l+=m){
			m = snarfbuf.nc-l;
			if(m>BLOCKSIZE)
				m = BLOCKSIZE;
			bufread(&snarfbuf, l, genbuf, m);
			loginsert(f, p0, tmprstr(genbuf, m)->s, m);
		}
		if(fileupdate(f, FALSE, TRUE))
			seq++;
		f->dot.r.p1 = p0;
		f->dot.r.p2 = p0+snarfbuf.nc;
		f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
		telldot(f);
		outTs(Hunlockfile, f->tag);
		break;

	case Tsnarf:
		i = inshort();
		p0 = inlong();
		p1 = inlong();
		snarf(whichfile(i), p0, p1, &snarfbuf, 0);
		break;

	case Tstartnewfile:
		v = invlong();
		Strdupl(&genstr, empty);
		f = newfile();
		f->rasp = listalloc('P');
		outTsv(Hbindname, f->tag, v);
		logsetname(f, &genstr);
		outTs(Hcurrent, f->tag);
		current(f);
		load(f);
		break;

	case Twrite:
		termlocked++;
		i = inshort();
		journaln(0, i);
		f = whichfile(i);
		addr.r.p1 = 0;
		addr.r.p2 = f->nc;
		if(f->name.s[0] == 0)
			error(Enoname);
		Strduplstr(&genstr, &f->name);
		writef(f);
		break;

	case Tclose:
		termlocked++;
		i = inshort();
		journaln(0, i);
		f = whichfile(i);
		current(f);
		trytoclose(f);
		/* if trytoclose fails, will error out */
		delete(f);
		break;

	case Tlook:
		f = whichfile(inshort());
		termlocked++;
		p0 = inlong();
		p1 = inlong();
		journaln(0, p0);
		journaln(0, p1);
		setgenstr(f, p0, p1);
		for(l = 0; l<genstr.n; l++){
			i = genstr.s[l];
			if(utfrune(".*+?(|)\\[]^$", i)){
				str = tmpcstr("\\");
				Strinsert(&genstr, str, l++);
				freetmpstr(str);
			}
		}
		Straddc(&genstr, '\0');
		nextmatch(f, &genstr, p1, 1);
		moveto(f, sel.p[0]);
		break;

	case Tsearch:
		termlocked++;
		if(curfile == 0)
			error(Enofile);
		if(lastpat.s[0] == 0)
			panic("Tsearch");
		nextmatch(curfile, &lastpat, curfile->dot.r.p2, 1);
		moveto(curfile, sel.p[0]);
		break;

	case Tsend:
		termlocked++;
		inshort();	/* ignored */
		p0 = inlong();
		p1 = inlong();
		setgenstr(cmd, p0, p1);
		bufreset(&snarfbuf);
		bufinsert(&snarfbuf, (Posn)0, genstr.s, genstr.n);
		outTl(Hsnarflen, genstr.n);
		if(genstr.s[genstr.n-1] != '\n')
			Straddc(&genstr, '\n');
		loginsert(cmd, cmd->nc, genstr.s, genstr.n);
		fileupdate(cmd, FALSE, TRUE);
		cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nc;
		telldot(cmd);
		termcommand();
		break;

	case Tdclick:
		f = whichfile(inshort());
		p1 = inlong();
		doubleclick(f, p1);
		f->tdot.p1 = f->tdot.p2 = p1;
		telldot(f);
		outTs(Hunlockfile, f->tag);
		break;

	case Tstartsnarf:
		if (snarfbuf.nc <= 0) {	/* nothing to export */
			outTs(Hsetsnarf, 0);
			break;
		}
		c = 0;
		i = 0;
		m = snarfbuf.nc;
		if(m > SNARFSIZE) {
			m = SNARFSIZE;
			dprint("?warning: snarf buffer truncated\n");
		}
		rp = malloc(m*sizeof(Rune));
		if(rp){
			bufread(&snarfbuf, 0, rp, m);
			c = Strtoc(tmprstr(rp, m));
			free(rp);
			i = strlen(c);
		}
		outTs(Hsetsnarf, i);
		if(c){
			Write(1, c, i);
			free(c);
		} else
			dprint("snarf buffer too long\n");
		break;

	case Tsetsnarf:
		m = inshort();
		if(m > SNARFSIZE)
			error(Etoolong);
		c = malloc(m+1);
		if(c){
			for(i=0; i<m; i++)
				c[i] = rcvchar();
			c[m] = 0;
			str = tmpcstr(c);
			free(c);
			bufreset(&snarfbuf);
			bufinsert(&snarfbuf, (Posn)0, str->s, str->n);
			freetmpstr(str);
			outT0(Hunlock);
		}
		break;

	case Tack:
		waitack = 0;
		break;

	case Tplumb:
		f = whichfile(inshort());
		p0 = inlong();
		p1 = inlong();
		pm = emalloc(sizeof(Plumbmsg));
		pm->src = strdup("sam");
		pm->dst = 0;
		/* construct current directory */
		c = Strtoc(&f->name);
		if(c[0] == '/')
			pm->wdir = c;
		else{
			wdir = emalloc(1024);
			getwd(wdir, 1024);
			pm->wdir = emalloc(1024);
			snprint(pm->wdir, 1024, "%s/%s", wdir, c);
			cleanname(pm->wdir);
			free(wdir);
			free(c);
		}
		c = strrchr(pm->wdir, '/');
		if(c)
			*c = '\0';
		pm->type = strdup("text");
		if(p1 > p0)
			pm->attr = nil;
		else{
			p = p0;
			while(p0>0 && (i=filereadc(f, p0 - 1))!=' ' && i!='\t' && i!='\n')
				p0--;
			while(p1<f->nc && (i=filereadc(f, p1))!=' ' && i!='\t' && i!='\n')
				p1++;
			sprint(cbuf, "click=%ld", p-p0);
			pm->attr = plumbunpackattr(cbuf);
		}
		if(p0==p1 || p1-p0>=BLOCKSIZE){
			plumbfree(pm);
			break;
		}
		setgenstr(f, p0, p1);
		pm->data = Strtoc(&genstr);
		pm->ndata = strlen(pm->data);
		c = plumbpack(pm, &i);
		if(c != 0){
			outTs(Hplumb, i);
			Write(1, c, i);
			free(c);
		}
		plumbfree(pm);
		break;

	case Texit:
		exits(0);
	}
	return TRUE;
}

void
snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
{
	Posn l;
	int i;

	if(!emptyok && p1==p2)
		return;
	bufreset(buf);
	/* Stage through genbuf to avoid compaction problems (vestigial) */
	if(p2 > f->nc){
		fprint(2, "bad snarf addr p1=%ld p2=%ld f->nc=%d\n", p1, p2, f->nc); /*ZZZ should never happen, can remove */
		p2 = f->nc;
	}
	for(l=p1; l<p2; l+=i){
		i = p2-l>BLOCKSIZE? BLOCKSIZE : p2-l;
		bufread(f, l, genbuf, i);
		bufinsert(buf, buf->nc, tmprstr(genbuf, i)->s, i);
	}
}

int
inshort(void)
{
	ushort n;

	n = inp[0] | (inp[1]<<8);
	inp += 2;
	return n;
}

long
inlong(void)
{
	ulong n;

	n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
	inp += 4;
	return n;
}

vlong
invlong(void)
{
	vlong v;
	
	v = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
	v = (v<<16) | (inp[3]<<8) | inp[2];
	v = (v<<16) | (inp[1]<<8) | inp[0];
	inp += 8;
	return v;
}

void
setgenstr(File *f, Posn p0, Posn p1)
{
	if(p0 != p1){
		if(p1-p0 >= TBLOCKSIZE)
			error(Etoolong);
		Strinsure(&genstr, p1-p0);
		bufread(f, p0, genbuf, p1-p0);
		memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
		genstr.n = p1-p0;
	}else{
		if(snarfbuf.nc == 0)
			error(Eempty);
		if(snarfbuf.nc > TBLOCKSIZE)
			error(Etoolong);
		bufread(&snarfbuf, (Posn)0, genbuf, snarfbuf.nc);
		Strinsure(&genstr, snarfbuf.nc);
		memmove(genstr.s, genbuf, RUNESIZE*snarfbuf.nc);
		genstr.n = snarfbuf.nc;
	}
}

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

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

void
outTs(Hmesg type, int s)
{
	outstart(type);
	journaln(1, s);
	outshort(s);
	outsend();
}

void
outS(String *s)
{
	char *c;
	int i;

	c = Strtoc(s);
	i = strlen(c);
	outcopy(i, c);
	if(i > 99)
		c[99] = 0;
	journaln(1, i);
	journal(1, c);
	free(c);
}

void
outTsS(Hmesg type, int s1, String *s)
{
	outstart(type);
	outshort(s1);
	outS(s);
	outsend();
}

void
outTslS(Hmesg type, int s1, Posn l1, String *s)
{
	outstart(type);
	outshort(s1);
	journaln(1, s1);
	outlong(l1);
	journaln(1, l1);
	outS(s);
	outsend();
}

void
outTS(Hmesg type, String *s)
{
	outstart(type);
	outS(s);
	outsend();
}

void
outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
{
	outstart(type);
	outshort(s1);
	outlong(l1);
	outlong(l2);
	journaln(1, l1);
	journaln(1, l2);
	outS(s);
	outsend();
}

void
outTsll(Hmesg type, int s, Posn l1, Posn l2)
{
	outstart(type);
	outshort(s);
	outlong(l1);
	outlong(l2);
	journaln(1, l1);
	journaln(1, l2);
	outsend();
}

void
outTsl(Hmesg type, int s, Posn l)
{
	outstart(type);
	outshort(s);
	outlong(l);
	journaln(1, l);
	outsend();
}

void
outTsv(Hmesg type, int s, vlong v)
{
	outstart(type);
	outshort(s);
	outvlong(v);
	journalv(1, v);
	outsend();
}

void
outstart(Hmesg type)
{
	journal(1, hname[type]);
	outmsg[0] = type;
	outp = outmsg+3;
}

void
outcopy(int count, void *data)
{
	memmove(outp, data, count);
	outp += count;
}

void
outshort(int s)
{
	*outp++ = s;
	*outp++ = s>>8; 
}

void
outlong(long l)
{
	*outp++ = l;
	*outp++ = l>>8;
	*outp++ = l>>16;
	*outp++ = l>>24;
}

void
outvlong(vlong v)
{
	int i;

	for(i = 0; i < 8; i++){
		*outp++ = v;
		v >>= 8;
	}
}

void
outsend(void)
{
	int outcount;

	if(outp >= outdata+nelem(outdata))
		panic("outsend");
	outcount = outp-outmsg;
	outcount -= 3;
	outmsg[1] = outcount;
	outmsg[2] = outcount>>8;
	outmsg = outp;
	if(!outbuffered){
		outcount = outmsg-outdata;
		if (write(1, (char*) outdata, outcount) != outcount)
			rescue();
		outmsg = outdata;
		return;
	}
}

int
needoutflush(void)
{
	return outmsg >= outdata+DATASIZE;
}

void
outflush(void)
{
	if(outmsg == outdata)
		return;
	outbuffered = 0;
	/* flow control */
	outT0(Hack);
	waitack = 1;
	do
		if(rcv() == 0){
			rescue();
			exits("eof");
		}
	while(waitack);
	outmsg = outdata;
	outbuffered = 1;
}
