/*
 * ps.c
 * 
 * provide postscript file reading support for page
 */

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <bio.h>
#include <ctype.h>
#include "page.h"

typedef struct PSInfo	PSInfo;
typedef struct Page	Page;
	
struct Page {
	char *name;
	int offset;			/* offset of page beginning within file */
};

struct PSInfo {
	GSInfo;
	Rectangle bbox;	/* default bounding box */
	Page *page;
	int npage;
	int clueless;	/* don't know where page boundaries are */
	long psoff;	/* location of %! in file */
	char ctm[256];
};

static int	pswritepage(Document *d, int fd, int page);
static Image*	psdrawpage(Document *d, int page);
static char*	pspagename(Document*, int);

#define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
Rectangle
rdbbox(char *p)
{
	Rectangle r;
	int a;
	char *f[4];
	while(*p == ':' || *p == ' ' || *p == '\t')
		p++;
	if(tokenize(p, f, 4) != 4)
		return Rect(0,0,0,0);
	r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
	r = canonrect(r);
	if(Dx(r) <= 0 || Dy(r) <= 0)
		return Rect(0,0,0,0);

	if(truetoboundingbox)
		return r;

	/* initdraw not called yet, can't use %R */
	if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
	/*
	 * attempt to sniff out A4, 8½×11, others
	 * A4 is 596×842
	 * 8½×11 is 612×792
	 */

	a = Dx(r)*Dy(r);
	if(a < 300*300){	/* really small, probably supposed to be */
		/* empty */
	} else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842)	/* A4 */
		r = Rect(0, 0, 596, 842);
	else {	/* cast up to 8½×11 */
		if(Dx(r) <= 612 && r.max.x <= 612){
			r.min.x = 0;
			r.max.x = 612;
		}
		if(Dy(r) <= 792 && r.max.y <= 792){
			r.min.y = 0;
			r.max.y = 792;
		}
	}
	if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
	return r;
}

#define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y

int
prefix(char *x, char *y)
{
	return strncmp(x, y, strlen(y)) == 0;
}

/*
 * document ps is really being printed as n-up pages.
 * we need to treat every n pages as 1.
 */
void
repaginate(PSInfo *ps, int n)
{
	int i, np, onp;
	Page *page;

	page = ps->page;
	onp = ps->npage;
	np = (ps->npage+n-1)/n;

	if(chatty) {
		for(i=0; i<=onp+1; i++)
			print("page %d: %d\n", i, page[i].offset);
	}

	for(i=0; i<np; i++)
		page[i] = page[n*i];

	/* trailer */
	page[np] = page[onp];

	/* EOF */
	page[np+1] = page[onp+1];

	ps->npage = np;

	if(chatty) {
		for(i=0; i<=np+1; i++)
			print("page %d: %d\n", i, page[i].offset);
	}

}

Document*
initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
{
	Document *d;
	PSInfo *ps;
	char *p;
	char *q, *r;
	char eol;
	char *nargv[1];
	char fdbuf[20];
	char tmp[32];
	int fd;
	int i;
	int incomments;
	int cantranslate;
	int trailer=0;
	int nesting=0;
	int dumb=0;
	int landscape=0;
	long psoff;
	long npage, mpage;
	Page *page;
	Rectangle bbox = Rect(0,0,0,0);

	if(argc > 1) {
		fprint(2, "can only view one ps file at a time\n");
		return nil;
	}

	fprint(2, "reading through postscript...\n");
	if(b == nil){	/* standard input; spool to disk (ouch) */
		fd = spooltodisk(buf, nbuf, nil);
		sprint(fdbuf, "/fd/%d", fd);
		b = Bopen(fdbuf, OREAD);
		if(b == nil){
			fprint(2, "cannot open disk spool file\n");
			wexits("Bopen temp");
		}
		nargv[0] = fdbuf;
		argv = nargv;
	}

	/* find %!, perhaps after PCL nonsense */
	Bseek(b, 0, 0);
	psoff = 0;
	eol = 0;
	for(i=0; i<16; i++){
		psoff = Boffset(b);
		if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
			fprint(2, "cannot find end of first line\n");
			wexits("initps");
		}
		if(p[0]=='\x1B')
			p++, psoff++;
		if(p[0] == '%' && p[1] == '!')
			break;
	}
	if(i == 16){
		werrstr("not ps");
		return nil;
	}

	/* page counting */
	npage = 0;
	mpage = 16;
	page = emalloc(mpage*sizeof(*page));
	memset(page, 0, mpage*sizeof(*page));

	cantranslate = goodps;
	incomments = 1;
Keepreading:
	while(p = Brdline(b, eol)) {
		if(p[0] == '%')
			if(chatty > 1) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
		if(npage == mpage) {
			mpage *= 2;
			page = erealloc(page, mpage*sizeof(*page));
			memset(&page[npage], 0, npage*sizeof(*page));
		}

		if(p[0] != '%' || p[1] != '%')
			continue;

		if(prefix(p, "%%BeginDocument")) {
			nesting++;
			continue;
		}
		if(nesting > 0 && prefix(p, "%%EndDocument")) {
			nesting--;
			continue;
		}
		if(nesting)
			continue;

		if(prefix(p, "%%EndComment")) {
			incomments = 0;
			continue;
		}
		if(reverse == -1 && prefix(p, "%%PageOrder")) {
			/* glean whether we should reverse the viewing order */
			p[Blinelen(b)-1] = 0;
			if(strstr(p, "Ascend"))
				reverse = 0;
			else if(strstr(p, "Descend"))
				reverse = 1;
			else if(strstr(p, "Special"))
				dumb = 1;
			p[Blinelen(b)-1] = '\n';
			continue;
		} else if(prefix(p, "%%Trailer")) {
			incomments = 1;
			page[npage].offset = Boffset(b)-Blinelen(b);
			trailer = 1;
			continue;
		} else if(incomments && prefix(p, "%%Orientation")) {
			if(strstr(p, "Landscape"))
				landscape = 1;
		} else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
			bbox = rdbbox(p+strlen(q)+1);
			if(chatty)
				/* can't use %R because haven't initdraw() */
				fprint(2, "document bbox [%d %d %d %d]\n",
					RECT(bbox));
			continue;
		}

		/*
		 * If they use the initgraphics command, we can't play our translation tricks.
		 */
		p[Blinelen(b)-1] = 0;
		if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
			cantranslate = 0;
		p[Blinelen(b)-1] = eol;

		if(!prefix(p, "%%Page:"))
			continue;

		/* 
		 * figure out of the %%Page: line contains a page number
		 * or some other page description to use in the menu bar.
		 * 
		 * lines look like %%Page: x y or %%Page: x
		 * we prefer just x, and will generate our
		 * own if necessary.
		 */
		p[Blinelen(b)-1] = 0;
		if(chatty) fprint(2, "page %s\n", p);
		r = p+7;
		while(*r == ' ' || *r == '\t')
			r++;
		q = r;
		while(*q && *q != ' ' && *q != '\t')
			q++;
		free(page[npage].name);
		if(*r) {
			if(*r == '"' && *q == '"')
				r++, q--;
			if(*q)
				*q = 0;
			page[npage].name = estrdup(r);
			*q = 'x';
		} else {
			snprint(tmp, sizeof tmp, "p %ld", npage+1);
			page[npage].name = estrdup(tmp);
		}

		/*
		 * store the offset info for later viewing
		 */
		trailer = 0;
		p[Blinelen(b)-1] = eol;
		page[npage++].offset = Boffset(b)-Blinelen(b);
	}
	if(Blinelen(b) > 0){
		fprint(2, "page: linelen %d\n", Blinelen(b));
		Bseek(b, Blinelen(b), 1);
		goto Keepreading;
	}

	if(Dx(bbox) == 0 || Dy(bbox) == 0)
		bbox = Rect(0,0,612,792);	/* 8½×11 */
	/*
	 * if we didn't find any pages, assume the document
	 * is one big page
	 */
	if(npage == 0) {
		dumb = 1;
		if(chatty) fprint(2, "don't know where pages are\n");
		reverse = 0;
		goodps = 0;
		trailer = 0;
		page[npage].name = "p 1";
		page[npage++].offset = 0;
	}

	if(npage+2 > mpage) {
		mpage += 2;
		page = erealloc(page, mpage*sizeof(*page));
		memset(&page[mpage-2], 0, 2*sizeof(*page));
	}

	if(!trailer)
		page[npage].offset = Boffset(b);

	Bseek(b, 0, 2); /* EOF */
	page[npage+1].offset = Boffset(b);

	d = emalloc(sizeof(*d));
	ps = emalloc(sizeof(*ps));
	ps->page = page;
	ps->npage = npage;
	ps->bbox = bbox;
	ps->psoff = psoff;

	d->extra = ps;
	d->npage = ps->npage;
	d->b = b;
	d->drawpage = psdrawpage;
	d->pagename = pspagename;

	d->fwdonly = ps->clueless = dumb;
	d->docname = argv[0];

	if(spawngs(ps, "-dSAFER") < 0)
		return nil;

	if(!cantranslate)
		bbox.min = ZP;
	setdim(ps, bbox, ppi, landscape);

	if(goodps){
		/*
		 * We want to only send the page (i.e. not header and trailer) information
	 	 * for each page, so initialize the device by sending the header now.
		 */
		pswritepage(d, ps->gsfd, -1);
		waitgs(ps);
	}

	if(dumb) {
		fprint(ps->gsfd, "(%s) run\n", argv[0]);
		fprint(ps->gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
	}

	ps->bbox = bbox;

	return d;
}

static int
pswritepage(Document *d, int fd, int page)
{
	Biobuf *b = d->b;
	PSInfo *ps = d->extra;
	int t, n, i;
	long begin, end;
	char buf[8192];

	if(page == -1)
		begin = ps->psoff;
	else
		begin = ps->page[page].offset;

	end = ps->page[page+1].offset;

	if(chatty) {
		fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
			page, begin, end);
	}
	Bseek(b, begin, 0);

	t = end-begin;
	n = sizeof(buf);
	if(n > t) n = t;
	while(t > 0 && (i=Bread(b, buf, n)) > 0) {
		if(write(fd, buf, i) != i)
			return -1;
		t -= i;
		if(n > t)
			n = t;
	}
	return end-begin;
}

static Image*
psdrawpage(Document *d, int page)
{
	PSInfo *ps = d->extra;
	Image *im;

	if(ps->clueless)
		return readimage(display, ps->gsdfd, 0);

	waitgs(ps);

	if(goodps)
		pswritepage(d, ps->gsfd, page);
	else {
		pswritepage(d, ps->gsfd, -1);
		pswritepage(d, ps->gsfd, page);
		pswritepage(d, ps->gsfd, d->npage);
	}
	/*
	 * If last line terminator is \r, gs will read ahead to check for \n
	 * so send one to avoid deadlock.
	 */
	write(ps->gsfd, "\n", 1);
	im = readimage(display, ps->gsdfd, 0);
	if(im == nil) {
		fprint(2, "fatal: readimage error %r\n");
		wexits("readimage");
	}
	waitgs(ps);

	return im;
}

static char*
pspagename(Document *d, int page)
{
	PSInfo *ps = (PSInfo *) d->extra;
	return ps->page[page].name;
}
