#include <u.h>
#include <libc.h>
#include <bio.h>
#include "plot.h"
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>

void	define(char*);
void	call(char*);
void	include(char*);
int	process(Biobuf*);
int	server(void);

enum{
	ARC,
	BOX,
	CALL,
	CFILL,
	CIRC,
	CLOSEPL,
	COLOR,
	CSPLINE,
	DEFINE,
	DISK,
	DSPLINE,
	ERASE,
	FILL,
	FRAME,
	FSPLINE,
	GRADE,
	IDLE,
	INCLUDE,
	LINE,
	LSPLINE,
	MOVE,
	OPENPL,
	PARABOLA,
	PEN,
	PAUSE,
	POINT,
	POLY,
	RANGE,
	RESTORE,
	RMOVE,
	RVEC,
	SAVE,
	SBOX,
	SPLINE,
	TEXT,
	VEC,
	LAST
};

struct pcall {
	char	*cc;
	int	numc;
} plots[] = {
	[ARC] 		"a", 	1,
	[BOX] 		"bo", 	2,
	[CALL]		"ca",	2,
	[CFILL] 	"cf", 	2,
	[CIRC] 		"ci", 	2,
	[CLOSEPL] 	"cl", 	2,
	[COLOR] 	"co", 	2,
	[CSPLINE]	"cs",	2,
	[DEFINE]	"de",	2,
	[DISK]		"di",	2,
	[DSPLINE]	"ds",	2,
	[ERASE] 	"e", 	1,
	[FILL] 		"fi", 	2,
	[FRAME] 	"fr", 	2,
	[FSPLINE]	"fs",	2,
	[GRADE] 	"g", 	1,
	[IDLE] 		"id", 	2,
	[INCLUDE]	"in",	2,
	[LINE] 		"li", 	2,
	[LSPLINE]	"ls",	2,
	[MOVE] 		"m", 	1,
	[OPENPL] 	"o", 	1,
	[PARABOLA] 	"par", 	3,
	[PEN] 		"pe", 	2,
	[PAUSE] 	"pau", 	3,
	[POINT] 	"poi", 	3,
	[POLY] 		"pol", 	3,
	[RANGE] 	"ra", 	2,
	[RESTORE] 	"re", 	2,
	[RMOVE] 	"rm", 	2,
	[RVEC] 		"rv", 	2,
	[SAVE] 		"sa", 	2,
	[SBOX] 		"sb", 	2,
	[SPLINE] 	"sp", 	2,
	[TEXT] 		"t", 	1,
	[VEC] 		"v", 	1,
	[LAST]	 	0, 	0,
};

struct pcall *pplots;		/* last command read */

#define MAXL 16
struct fcall {
	char *name;
	char *stash;
} flibr[MAXL];			/* define strings */

struct fcall *fptr = flibr;

#define	NFSTACK	50
struct fstack{
	char name[128];
	int peekc;
	int lineno;
	char *corebuf;
	Biobuf *fd;
	double scale;
}fstack[NFSTACK];		/* stack of open input files & defines */
struct fstack *fsp=fstack;

#define	NARGSTR	8192
char argstr[NARGSTR+1];		/* string arguments */

#define	NX	8192
double x[NX];			/* numeric arguments */

#define	NPTS	256
int cnt[NPTS];			/* control-polygon vertex counts */
double *pts[NPTS];		/* control-polygon vertex pointers */

extern void m_swapbuf(void);	/* reaching into implementation.  ick. */
extern Image *offscreen;

void
resize(Point p)
{
	int fd;

	fd = open("/dev/wctl", OWRITE);
	if(fd >= 0){
		fprint(fd, "resize -dx %d -dy %d", p.x+4*2, p.y+4*2);
		close(fd);
	}
}

void
resizeto(Point p)
{
	Point s;

	s = (Point){Dx(screen->r), Dy(screen->r)};
	if(eqpt(p, s))
		return;
	resize(p);
}

void
eresized(int new)
{
	if(new && getwindow(display, Refnone) < 0)
		sysfatal("plot: can't reattach to window: %r");
//	resizeto((Point){Dx(offscreen->r)+4, Dy(offscreen->r)+4});
	m_swapbuf();
}

char *items[]={
	"exit",
	0
};
Menu menu={items};

void
mouseproc(void*)
{
	void *v;
	Rune r;
	Alt alts[4];
	Keyboardctl *k;
	Mousectl *m;
	Mouse mc;
	enum{Amouse, Akbd, Aresize, Aend};

	m = initmouse(nil, screen);
	k = initkeyboard(nil);

	memset(alts, 0, sizeof alts);
	alts[Amouse].c = m->c;
	alts[Amouse].v = &mc;
	alts[Amouse].op = CHANRCV;

	alts[Akbd].c = k->c;
	alts[Akbd].v = &r;
	alts[Akbd].op = CHANRCV;

	alts[Aresize].c = m->resizec;
	alts[Aresize].v = &v;
	alts[Aresize].op = CHANRCV;

	alts[Aend].op = CHANEND;

	for(;;)
		switch(alt(alts)){
		default:
			sysfatal("mouse!");
		case Amouse:
			if(mc.buttons & 4) {
				if(menuhit(3, m, &menu, nil) == 0)
					threadexitsall("");
			}
			break;
		case Akbd:
			switch(r){
			case 'q':
			case Kdel:
			case Keof:
				threadexitsall("");
			}
			break;
		case Aresize:
			eresized(1);
			;
		}
}

void
threadmain(int arc, char *arv[]){
	char *ap;
	Biobuf *bp;
	int fd;
	int i;
	int dflag;
	char *oflag;

	bp = 0;
	fd = dup(0, -1);		/* because openpl will close 0! */
	dflag=0;
	oflag="";
	argv0 = arv[0];
	for(i=1;i!=arc;i++) if(arv[i][0]=='-') switch(arv[i][1]){
	case 'd': dflag=1; break;
	case 'o': oflag=arv[i]+2; break;
	case 's': fd=server(); break;
	}
	openpl(oflag);
	proccreate(mouseproc, nil, 32*1024);
	if(dflag)
		doublebuffer();
	for (; arc > 1; arc--, arv++) {
		if (arv[1][0] == '-') {
			ap = arv[1];
			ap++;
			switch (*ap) {
			default:
				fprint(2, "%s not allowed as argument\n", ap);
				exits("usage");
			case 'T': break;
			case 'D': break;
			case 'd': break;
			case 'o': break;
			case 'W': break;
			case 's': break;
			case 'e': erase(); break;
			case 'C': closepl(); break;
			case 'w': ppause(); break;
			case 'c': color(ap+1); break;
			case 'f': cfill(ap+1); break;
			case 'p': pen(ap+1); break;
			case 'g': grade(atof(ap+1)); break;
			}
		}
		else if ((bp = Bopen(arv[1], OREAD)) == 0) {
			perror(arv[1]);
			fprint(2, "Cannot find file %s\n", arv[1]);
		}
		else if(process(bp)) Bterm(fsp->fd);
		else break;
	}
	if (bp == 0){
		bp = malloc(sizeof *bp);
		Binit(bp, fd, OREAD);
		process(bp);
	}
	closepl();
	flushimage(display, 1);
	for(;;)
		sleep(1000);
}
int isalpha(int c)
{
	return ('a'<=c && c<='z') || ('A'<=c && c<='Z');
}
int isupper(int c)
{
	return 'A'<=c && c<='Z';
}
int isdigit(int c)
{
	return '0'<=c && c<='9';
}
int ispunct(int c)
{
	return strchr("!\"#$%&'()*+,-./:;<=>?@[\]^_`{|}~", c)!=0;
}
int isspace(int c)
{
	return strchr(" \t\n\v\f\r", c)!=0;
}
int nextc(void){
	int c;
	Rune r;
	for(;;){
		if(fsp->peekc!=Beof){
			c=fsp->peekc;
			fsp->peekc=Beof;
			return c;
		}
		if(fsp->fd)
			c=Bgetrune(fsp->fd);
		else if(*fsp->corebuf){
			fsp->corebuf+=chartorune(&r, fsp->corebuf);
			c=r;
		}else
			c=Beof;
		if(c!=Beof || fsp==fstack) break;
		if(fsp->fd) Bterm(fsp->fd);
		--fsp;
	}
	if(c=='\n') fsp->lineno++;
	return c;
}
/*
 * Read a string into argstr -- ignores leading spaces
 * and an optional leading quote-mark
 */
void
strarg(void){
	int c;
	Rune r;
	int quote=0;
	char *s=argstr;
	do
		c=nextc();
	while(c==' ' || c=='\t');
	if(c=='\'' || c=='"'){
		quote=c;
		c=nextc();
	}
	r = 0;
	while(c!='\n' && c!=Beof){
		r=c;
		s+=runetochar(s, &r);
		c=nextc();
	}
	if(quote && s!=argstr && r==quote) --s;
	*s='\0';
}
/*
 * Read a floating point number into argstr
 */
numstring(void){
	int ndp=0;
	int ndig=0;
	char *s=argstr;
	int c=nextc();
	if(c=='+' || c=='-'){
		*s++=c;
		c=nextc();
	}
	while(isdigit(c) || c=='.'){
		if(s!=&argstr[NARGSTR]) *s++=c;
		if(c=='.') ndp++;
		else ndig++;
		c=nextc();
	}
	if(ndp>1 || ndig==0){
		fsp->peekc=c;
		return 0;
	}
	if(c=='e' || c=='E'){
		if(s!=&argstr[NARGSTR]) *s++=c;
		c=nextc();
		if(c=='+' || c=='-'){
			if(s!=&argstr[NARGSTR]) *s++=c;
			c=nextc();
		}
		if(!isdigit(c)){
			fsp->peekc=c;
			return 0;
		}
		while(isdigit(c)){
			if(s!=&argstr[NARGSTR]) *s++=c;
			c=nextc();
		}
	}
	fsp->peekc=c;
	*s='\0';
	return 1;
}
/*
 * Read n numeric arguments, storing them in
 * x[0], ..., x[n-1]
 */
void
numargs(int n){
	int i, c;
	for(i=0;i!=n;i++){
		do{
			c=nextc();
		}while(strchr(" \t\n", c) || c!='.' && c!='+' && c!='-' && ispunct(c));
		fsp->peekc=c;
		if(!numstring())
			sysfatal("%s:%d: number expected", fsp->name, fsp->lineno);
		x[i]=atof(argstr)*fsp->scale;
	}
}
/*
 * Read a list of lists of control vertices, storing points in x[.],
 * pointers in pts[.] and counts in cnt[.]
 */
void
polyarg(void){
	int nleft, l, r, c;
	double **ptsp=pts, *xp=x;
	int *cntp=cnt;
	do{
		c=nextc();
	}while(c==' ' || c=='\t');
	if(c=='{'){
		l='{';
		r='}';
	}
	else{
		l=r='\n';
		fsp->peekc=c;
	}
	nleft=1;
	*cntp=0;
	*ptsp=xp;
	for(;;){
		c=nextc();
		if(c==r){
			if(*cntp){
				if(*cntp&1)
					sysfatal("%s:%d: phase error", fsp->name, fsp->lineno);
				*cntp/=2;
				if(ptsp==&pts[NPTS])
					sysfatal("%s:%d: out of polygons", fsp->name, fsp->lineno);
				*++ptsp=xp;
				*++cntp=0;
			}
			if(--nleft==0) return;
		}
		else switch(c){
		case Beof:  return;
		case ' ':  break;
		case '\t': break;
		case '\n': break;
		case '.': case '+': case '-':
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			fsp->peekc=c;
			if(!numstring())
				sysfatal("%s:%d: expected number", fsp->name, fsp->lineno);
			if(xp==&x[NX])
				sysfatal("%s:%d: out of space", fsp->name, fsp->lineno);
			*xp++=atof(argstr);
			++*cntp;
			break;
		default:
			if(c==l) nleft++;
			else if(!ispunct(c)){
				fsp->peekc=c;
				return;
			}
		}
	}
}

process(Biobuf *fd){
	char *s;
	int c;
	fsp=fstack;
	fsp->fd=fd;
	fsp->corebuf=0;
	fsp->peekc=Beof;
	fsp->lineno=1;
	fsp->scale=1.;
	for(;;){
		do
			c=nextc();
		while(c==' ' || c=='\t');
		if(c==':'){
			do
				c=nextc();
			while(c!='\n' && c!=Beof);
			if(c==Beof) break;
			continue;
		}
		while(c=='.'){
			c=nextc();
			if(isdigit(c)){
				if(fsp->fd) Bungetc(fsp->fd);
				else --fsp->corebuf;
				c='.';
				break;
			}
		}
		if(c==Beof) break;
		if(c=='\n') continue;
		if(isalpha(c)){
			s=argstr;
			do{
				if(isupper(c)) c=tolower(c);
				if(s!=&argstr[NARGSTR]) *s++=c;
				c=nextc();
			}while(isalpha(c));
			fsp->peekc=c;
			*s='\0';
			for(pplots=plots;pplots->cc;pplots++)
				if(strncmp(argstr, pplots->cc, pplots->numc)==0)
					break;
			if(pplots->cc==0)
				sysfatal("%s:%d: %s unknown", fsp->name, fsp->lineno, argstr);
		}
		else{
			fsp->peekc=c;
		}
		if(!pplots)
			sysfatal("%s:%d: no command", fsp->name, fsp->lineno);
		switch(pplots-plots){
		case ARC:	numargs(7); rarc(x[0],x[1],x[2],x[3],x[4],x[5],x[6]); break;
		case BOX:	numargs(4); box(x[0], x[1], x[2], x[3]); break;
		case CALL:	strarg();   call(argstr); pplots=0; break;
		case CFILL:	strarg();   cfill(argstr); pplots=0; break;
		case CIRC:	numargs(3); circ(x[0], x[1], x[2]); break;
		case CLOSEPL:	strarg();   closepl(); pplots=0; break;
		case COLOR:	strarg();   color(argstr); pplots=0; break;
		case CSPLINE:	polyarg();  splin(4, cnt, pts); break;
		case DEFINE:	strarg();   define(argstr); pplots=0; break;
		case DISK:	numargs(3); plotdisc(x[0], x[1], x[2]); break;
		case DSPLINE:	polyarg();  splin(3, cnt, pts); break;
		case ERASE:	strarg();   erase(); pplots=0; break;
		case FILL:	polyarg();  fill(cnt, pts); break;
		case FRAME:	numargs(4); frame(x[0], x[1], x[2], x[3]); break;
		case FSPLINE:	polyarg();  splin(1, cnt, pts); break;
		case GRADE:	numargs(1); grade(x[0]); break;
		case IDLE:	strarg();   idle(); pplots=0; break;
		case INCLUDE:	strarg();   include(argstr); pplots=0; break;
		case LINE:	numargs(4); plotline(x[0], x[1], x[2], x[3]); break;
		case LSPLINE:	polyarg();  splin(2, cnt, pts); break;
		case MOVE:	numargs(2); move(x[0], x[1]); break;
		case OPENPL:	strarg();   openpl(argstr); pplots=0; break;
		case PARABOLA:	numargs(6); parabola(x[0],x[1],x[2],x[3],x[4],x[5]); break;
		case PAUSE:	strarg();   ppause(); pplots=0; break;
		case PEN:	strarg();   pen(argstr); pplots=0; break;
		case POINT:	numargs(2); dpoint(x[0], x[1]); break;
		case POLY:	polyarg();  plotpoly(cnt, pts); break;
		case RANGE:	numargs(4); range(x[0], x[1], x[2], x[3]); break;
		case RESTORE:	strarg();   restore(); pplots=0; break;
		case RMOVE:	numargs(2); rmove(x[0], x[1]); break;
		case RVEC:	numargs(2); rvec(x[0], x[1]); break;
		case SAVE:	strarg();   save(); pplots=0; break;
		case SBOX:	numargs(4); sbox(x[0], x[1], x[2], x[3]); break;
		case SPLINE:	polyarg();  splin(0, cnt, pts); break;
		case TEXT:	strarg();   text(argstr); pplots=0; break;
		case VEC:	numargs(2); vec(x[0], x[1]); break;
		default:
			sysfatal("%s:%d: plot: missing case %zd", fsp->name, fsp->lineno, pplots-plots);
		}
	}
	return 1;
}
char *names = 0;
char *enames = 0;
char *bstash = 0;
char *estash = 0;
unsigned size = 1024;
char *nstash = 0;
void define(char *a){
	char	*ap;
	short	i, j;
	int curly = 0;
	ap = a;
	while(isalpha(*ap))ap++;
	if(ap == a)
		sysfatal("plot: no name with define");
	i = ap - a;
	if(names+i+1 > enames){
		names = malloc((unsigned)512);
		enames = names + 512;
	}
	fptr->name = names;
	strncpy(names, a,i);
	names += i;
	*names++ = '\0';
	if(!bstash){
		bstash = nstash = malloc(size);
		estash = bstash + size;
	}
	fptr->stash = nstash;
	while(*ap != '{')
		if(*ap == '\n'){
			if((ap=Brdline(fsp->fd, '\n'))==0)
				sysfatal("plot: unexpected eof");
		}
		else ap++;
	while((j=Bgetc(fsp->fd))!= Beof){
		if(j == '{')curly++;
		else if(j == '}'){
			if(curly == 0)break;
			else curly--;
		}
		*nstash++ = j;
		if(nstash == estash){
			free(bstash);
			size += 1024;
			bstash = realloc(bstash,size);
			if(bstash == nil)
				sysfatal("plot: realloc: %r");
			estash = bstash+size;
		}
	}
	*nstash++ = '\0';
	if(fptr++ >= &flibr[MAXL])
		sysfatal("too many objects");
}
void call(char *a){
	char *ap;
	struct fcall *f;
	char sav;
	double SC;
	ap = a;
	while(isalpha(*ap))ap++;
	sav = *ap;
	*ap = '\0';
	for(f=flibr;f<fptr;f++){
		if (!(strcmp(a, f->name)))
			break;
	}
	if(f == fptr)
		sysfatal("plot: object %s not defined",a);
	*ap = sav;
	while (isspace(*ap) || *ap == ',') 
		ap++;
	if (*ap != '\0')
		SC = atof(ap);
	else SC = 1.;
	if(++fsp==&fstack[NFSTACK])
		sysfatal("plot: input stack overflow");
	snprint(fsp->name, sizeof fsp->name, "call %s", f->name);
	fsp->peekc=Beof;
	fsp->lineno=1;
	fsp->corebuf=f->stash;
	fsp->fd=0;
	fsp->scale=fsp[-1].scale*SC;
}
void include(char *a){
	Biobuf *fd;
	fd=Bopen(a, OREAD);
	if(fd==0)
		sysfatal("plot: cant include %s: %r", a);
	if(++fsp==&fstack[NFSTACK])
		sysfatal("plot: input stack overflow");
	snprint(fsp->name, sizeof fsp->name, "%s", a);
	fsp->peekc=Beof;
	fsp->lineno=1;
	fsp->corebuf=0;
	fsp->fd=fd;
}
/*
 * Doesn't work.  Why?
 */
int server(void){
	int fd, p[2];
	char buf[32];
	pipe(p);
	if((fd = create("/srv/plot", OWRITE, 0666)) < 0)
		sysfatal("open: %r");
	sprint(buf, "%d", p[1]);
	write(fd, buf, strlen(buf));
	close(fd);
	close(p[1]);
	return p[0];
}
