#include	<u.h>
#include	<libc.h>
#include	<draw.h>
#include	<cursor.h>
#include	<event.h>
#include	<bio.h>
#include	"proof.h"

int	res;
int	hpos;
int	vpos;
int	DIV = 11;

Point offset;
Point xyoffset = { 0,0 };

Rectangle	view[MAXVIEW];
Rectangle	bound[MAXVIEW];		/* extreme points */
int	nview = 1;

int	lastp;	/* last page number we were on */

#define	NPAGENUMS	200
struct pagenum {
	int	num;
	long	adr;
} pagenums[NPAGENUMS];
int	npagenums;

int	curfont, cursize;

char	*getcmdstr(void);

static void	initpage(void);
static void	view_setup(int);
static Point	scale(Point);
static void	clearview(Rectangle);
static int	addpage(int);
static void	spline(Image *, int, Point *);
static int	skipto(int, int);
static void	wiggly(int);
static void	devcntrl(void);
static void	eatline(void);
static int	getn(void);
static int	botpage(int);
static void	getstr(char *);

#define Do screen->r.min
#define Dc screen->r.max

/* declarations and definitions of font stuff are in font.c and main.c */

static void
initpage(void)
{
	int i;

	view_setup(nview);
	for (i = 0; i < nview-1; i++)
		draw(screen, view[i], screen, nil, view[i+1].min);
	clearview(view[nview-1]);
	offset = view[nview-1].min;
	vpos = 0;
}

static void
view_setup(int n)
{
	int i, j, v, dx, dy, r, c;

	switch (n) {
	case 1: r = 1; c = 1; break;
	case 2: r = 1; c = 2; break;
	case 3: r = 1; c = 3; break;
	case 4: r = 2; c = 2; break;
	case 5: case 6: r = 2; c = 3; break;
	case 7: case 8: case 9: r = 3; c = 3; break;
	default: r = (n+2)/3; c = 3; break; /* finking out */
	}
	dx = (Dc.x - Do.x) / c;
	dy = (Dc.y - Do.y) / r;
	v = 0;
	for (i = 0; i < r && v < n; i++)
		for (j = 0; j < c && v < n; j++) {
			view[v] = screen->r;
			view[v].min.x = Do.x + j * dx;
			view[v].max.x = Do.x + (j+1) * dx;
			view[v].min.y = Do.y + i * dy;
			view[v].max.y = Do.y + (i+1) * dy;
			v++;
		}
}

static void
clearview(Rectangle r)
{
	draw(screen, r, display->white, nil, r.min);
}

int resized;
void eresized(int new)
{
	/* this is called if we are resized */
	if(new && getwindow(display, Refnone) < 0)
		drawerror(display, "can't reattach to window");
	initpage();
	resized = 1;
}

static Point
scale(Point p)
{
	p.x /= DIV;
	p.y /= DIV;
	return addpt(xyoffset, addpt(offset,p));
}

static int
addpage(int n)
{
	int i;

	for (i = 0; i < npagenums; i++)
		if (n == pagenums[i].num)
			return i;
	if (npagenums < NPAGENUMS-1) {
		pagenums[npagenums].num = n;
		pagenums[npagenums].adr = offsetc();
		npagenums++;
	}
	return npagenums;
}

void
readpage(void)
{
	int c, i, a, alpha, phi;
	static int first = 0;
	int m, n, gonow = 1;
	Rune r[32], t;
	Point p,q,qq;

	offset = screen->clipr.min;
	esetcursor(&deadmouse);
	while (gonow)
	{
		c = getc();
		switch (c)
		{
		case -1:
			esetcursor(0);
			if (botpage(lastp+1)) {
				initpage();
				break;
			}
			exits(0);
		case 'p':	/* new page */
			lastp = getn();
			addpage(lastp);
			if (first++ > 0) {
				esetcursor(0);
				botpage(lastp);
				esetcursor(&deadmouse);
			}
			initpage();
			break;
		case '\n':	/* when input is text */
		case ' ':
		case 0:		/* occasional noise creeps in */
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			/* two motion digits plus a character */
			hpos += (c-'0')*10 + getc()-'0';

		/* FALLS THROUGH */
		case 'c':	/* single ascii character */
			r[0] = getrune();
			r[1] = 0;
			dochar(r);
			break;

		case 'C':
			for(i=0; ; i++){
				t = getrune();
				if(isspace(t))
					break;
				r[i] = t;
			}
			r[i] = 0;
			dochar(r);
			break;

		case 'N':
			r[0] = getn();
			r[1] = 0;
			dochar(r);
			break;

		case 'D':	/* draw function */
			switch (getc())
			{
			case 'l':	/* draw a line */
				n = getn();
				m = getn();
				p = Pt(hpos,vpos);
				q = addpt(p, Pt(n,m));
				hpos += n;
				vpos += m;
				line(screen, scale(p), scale(q), 0, 0, 0, display->black, ZP);
				break;
			case 'c':	/* circle */
				/*nop*/
				m = getn()/2;
				p = Pt(hpos+m,vpos);
				hpos += 2*m;
				ellipse(screen, scale(p), m/DIV, m/DIV, 0, display->black, ZP);
				/* p=currentpt; p.x+=dmap(m/2);circle bp,p,a,ONES,Mode*/
				break;
			case 'e':	/* ellipse */
				/*nop*/
				m = getn()/2;
				n = getn()/2;
				p = Pt(hpos+m,vpos);
				hpos += 2*m;
				ellipse(screen, scale(p), m/DIV, n/DIV, 0, display->black, ZP);
				break;
			case 'a':	/* arc */
				p = scale(Pt(hpos,vpos));
				n = getn();
				m = getn();
				hpos += n;
				vpos += m;
				q = scale(Pt(hpos,vpos));
				n = getn();
				m = getn();
				hpos += n;
				vpos += m;
				qq = scale(Pt(hpos,vpos));
				/*
				  * tricky: convert from 3-point clockwise to
				  * center, angle1, delta-angle counterclockwise.
				 */
				a = hypot(qq.x-q.x, qq.y-q.y);
				phi = atan2(q.y-p.y, p.x-q.x)*180./PI;
				alpha = atan2(q.y-qq.y, qq.x-q.x)*180./PI - phi;
				if(alpha < 0)
					alpha += 360;
				arc(screen, q, a, a, 0, display->black, ZP, phi, alpha);
				break;
			case '~':	/* wiggly line */
				wiggly(0);
				break;
			default:
				break;
			}
			eatline();
			break;
		case 's':
			n = getn();	/* ignore fractional sizes */
			if (cursize == n)
				break;
			cursize = n;
			if (cursize >= NFONT)
				cursize = NFONT-1;
			break;
		case 'f':
			curfont = getn();
			break;
		case 'H':	/* absolute horizontal motion */
			hpos = getn();
			break;
		case 'h':	/* relative horizontal motion */
			hpos += getn();
			break;
		case 'w':	/* word space */
			break;
		case 'V':
			vpos = getn();
			break;
		case 'v':
			vpos += getn();
			break;
		case '#':	/* comment */
		case 'n':	/* end of line */
			eatline();
			break;
		case 'x':	/* device control */
			devcntrl();
			break;
		default:
			fprint(2, "unknown input character %o %c at offset %lud\n", c, c, offsetc());
			exits("bad char");
		}
	}
	esetcursor(0);
}

static void
spline(Image *b, int n, Point *pp)
{
	long w, t1, t2, t3, fac=1000; 
	int i, j, steps=10; 
	Point p, q;

	for (i = n; i > 0; i--)
		pp[i] = pp[i-1];
	pp[n+1] = pp[n];
	n += 2;
	p = pp[0];
	for(i = 0; i < n-2; i++)
	{
		for(j = 0; j < steps; j++)
		{
			w = fac * j / steps;
			t1 = w * w / (2 * fac);
			w = w - fac/2;
			t2 = 3*fac/4 - w * w / fac;
			w = w - fac/2;
			t3 = w * w / (2*fac);
			q.x = (t1*pp[i+2].x + t2*pp[i+1].x + 
				t3*pp[i].x + fac/2) / fac;
			q.y = (t1*pp[i+2].y + t2*pp[i+1].y + 
				t3*pp[i].y + fac/2) / fac;
			line(b, p, q, 0, 0, 0, display->black, ZP);
			p = q;
		}
	}
}

/* Have to parse skipped pages, to find out what fonts are loaded. */
static int
skipto(int gotop, int curp)
{
	char *p;
	int i;

	if (gotop == curp)
		return 1;
	for (i = 0; i < npagenums; i++)
		if (pagenums[i].num == gotop) {
			if (seekc(pagenums[i].adr) == Beof) {
				fprint(2, "can't rewind input\n");
				return 0;
			}
			return 1;
		}
	if (gotop <= curp) {
	    restart:
		if (seekc(0) == Beof) {
			fprint(2, "can't rewind input\n");
			return 0;
		}
	}
	for(;;){
		p = rdlinec();
		if (p == 0) {
			if(gotop>curp){
				gotop = curp;
				goto restart;
			}
			return 0;
		} else if (*p == 'p') {
			lastp = curp = atoi(p+1);
			addpage(lastp);	/* maybe 1 too high */
			if (curp>=gotop)
				return 1;
		}
	}
}

static void
wiggly(int skip)
{
	Point p[300];
	int c,i,n;
	for (n = 1; (c = getc()) != '\n' && c>=0; n++) {
		ungetc();
		p[n].x = getn();
		p[n].y = getn();
	}
	p[0] = Pt(hpos, vpos);
	for (i = 1; i < n; i++)
		p[i] = addpt(p[i],p[i-1]);
	hpos = p[n-1].x;
	vpos = p[n-1].y;
	for (i = 0; i < n; i++)
		p[i] = scale(p[i]);
	if (!skip)
		spline(screen,n,p);
}

static void
devcntrl(void)	/* interpret device control functions */
{
        char str[80];
	int n;

	getstr(str);
	switch (str[0]) {	/* crude for now */
	case 'i':	/* initialize */
		break;
	case 'T':	/* device name */
		getstr(devname);
		break;
	case 't':	/* trailer */
		break;
	case 'p':	/* pause -- can restart */
		break;
	case 's':	/* stop */
		break;
	case 'r':	/* resolution assumed when prepared */
		res=getn();
		DIV = floor(.5 + res/(100.0*mag));
		if (DIV < 1)
			DIV = 1;
		mag = res/(100.0*DIV); /* adjust mag according to DIV coarseness */
		break;
	case 'f':	/* font used */
		n = getn();
		getstr(str);
		loadfontname(n, str);
		break;
	/* these don't belong here... */
	case 'H':	/* char height */
		break;
	case 'S':	/* slant */
		break;
	case 'X':
		break;
	}
	eatline();
}

int
isspace(int c)
{
	return c==' ' || c=='\t' || c=='\n';
}

static void
getstr(char *is)
{
	uchar *s = (uchar *) is;

	for (*s = getc(); isspace(*s); *s = getc())
		;
	for (; !isspace(*s); *++s = getc())
		;
	ungetc();
	*s = 0;
}

static void
eatline(void)
{
	int c;

	while ((c=getc()) != '\n' && c >= 0)
		;
}

static int
getn(void)
{
	int n, c, sign;

	while (c = getc())
		if (!isspace(c))
			break;
	if(c == '-'){
		sign = -1;
		c = getc();
	}else
		sign = 1;
	for (n = 0; '0'<=c && c<='9'; c = getc())
		n = n*10 + c - '0';
	while (c == ' ')
		c = getc();
	ungetc();
	return(n*sign);
}

static int
botpage(int np)	/* called at bottom of page np-1 == top of page np */
{
	char *p;
	int n;

	while (p = getcmdstr()) {
		if (*p == '\0')
			return 0;
		if (*p == 'q')
			exits(p);
		if (*p == 'c')		/* nop */
			continue;
		if (*p == 'm') {
			mag = atof(p+1);
			if (mag <= .1 || mag >= 10)
				mag = DEFMAG;
			allfree();	/* zap fonts */
			DIV = floor(.5 + res/(100.0*mag));
			if (DIV < 1)
				DIV = 1;
			mag = res/(100.0*DIV);
			return skipto(np-1, np);	/* reprint the page */
		}
		if (*p == 'x') {
			xyoffset.x += atoi(p+1)*100;
			skipto(np-1, np);
			return 1;
		}
		if (*p == 'y') {
			xyoffset.y += atoi(p+1)*100;
			skipto(np-1, np);
			return 1;
		}
		if (*p == '/') {	/* divide into n pieces */
			nview = atoi(p+1);
			if (nview < 1)
				nview = 1;
			else if (nview > MAXVIEW)
				nview = MAXVIEW;
			return skipto(np-1, np);
		}
		if (*p == 'p') {
			if (p[1] == '\0'){	/* bare 'p' */
				if(skipto(np-1, np))
					return 1;
				continue;
			}
			p++;
		}
		if ('0'<=*p && *p<='9') {
			n = atoi(p);
			if(skipto(n, np))
				return 1;
			continue;
		}
		if (*p == '-' || *p == '+') {
			n = atoi(p);
			if (n == 0)
				n = *p == '-' ? -1 : 1;
			if(skipto(np - 1 + n, np))
				return 1;
			continue;
		}
		if (*p == 'd') {
			dbg = 1 - dbg;
			continue;
		}

		fprint(2, "illegal:  try q, 17, +2, -1, p, m.7, /2, x1, y-.5 or return\n");
	}
	return 0;
}
