/*
 * gs interface for page.
 * ps.c and pdf.c both use these routines.
 * a caveat: if you run more than one gs, only the last 
 * one gets killed by killgs 
 */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <bio.h>
#include "page.h"

static int gspid;	/* globals for atexit */
static int gsfd;
static void	killgs(void);

static void
killgs(void)
{
	char tmpfile[100];

	close(gsfd);
	postnote(PNGROUP, getpid(), "die");

	/*
	 * from ghostscript's use.txt:
	 * ``Ghostscript currently doesn't do a very good job of deleting temporary
	 * files when it exits; you may have to delete them manually from time to
	 * time.''
	 */
	sprint(tmpfile, "/tmp/gs_%.5da", (gspid+300000)%100000);
	if(chatty) fprint(2, "remove %s...\n", tmpfile);
	remove(tmpfile);
	sleep(100);
	postnote(PNPROC, gspid, "die yankee pig dog");
}

int
spawnwriter(GSInfo *g, Biobuf *b)
{
	char buf[4096];
	int n;
	int fd;

	switch(fork()){
	case -1:	return -1;
	case 0:	break;
	default:	return 0;
	}

	Bseek(b, 0, 0);
	fd = g->gsfd;
	while((n = Bread(b, buf, sizeof buf)) > 0)
		write(fd, buf, n);
	fprint(fd, "(/fd/3) (w) file dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring flushfile\n");
	_exits(0);
	return -1;
}

int
spawnreader(int fd)
{
	int n, pfd[2];
	char buf[1024];

	if(pipe(pfd)<0)
		return -1;
	switch(fork()){
	case -1:
		return -1;
	case 0:
		break;
	default:
		close(pfd[0]);
		return pfd[1];
	}

	close(pfd[1]);
	switch(fork()){
	case -1:
		wexits("fork failed");
	case 0:
		while((n=read(fd, buf, sizeof buf)) > 0) {
			write(1, buf, n);
			write(pfd[0], buf, n);
		}
		break;
	default:
		while((n=read(pfd[0], buf, sizeof buf)) > 0) {
			write(1, buf, n);
			write(fd, buf, n);
		}
		break;
	}
	postnote(PNGROUP, getpid(), "i'm die-ing");
	_exits(0);
	return -1;
}

void
spawnmonitor(int fd)
{
	char buf[4096];
	char *xbuf;
	int n;
	int out;
	int first;

	switch(rfork(RFFDG|RFNOTEG|RFPROC)){
	case -1:
	default:
		return;

	case 0:
		break;
	}

	out = open("/dev/cons", OWRITE);
	if(out < 0)
		out = 2;

	xbuf = buf;	/* for ease of acid */
	first = 1;
	while((n = read(fd, xbuf, sizeof buf)) > 0){
		if(first){
			first = 0;
			fprint(2, "Ghostscript Error:\n");
		}
		write(out, xbuf, n);
		alarm(500);
	}
	_exits(0);
}

int 
spawngs(GSInfo *g, char *safer)
{
	char *args[16];
	char tb[32], gb[32];
	int i, nargs;
	int devnull;
	int stdinout[2];
	int dataout[2];
	int errout[2];

	/*
	 * spawn gs
	 *
 	 * gs's standard input is fed from stdinout.
	 * gs output written to fd-2 (i.e. output we generate intentionally) is fed to stdinout.
	 * gs output written to fd 1 (i.e. ouptut gs generates on error) is fed to errout.
	 * gs data output is written to fd 3, which is dataout.
	 */
	if(pipe(stdinout) < 0 || pipe(dataout)<0 || pipe(errout)<0)
		return -1;

	nargs = 0;
	args[nargs++] = "gs";
	args[nargs++] = "-dNOPAUSE";
	args[nargs++] = safer;
	args[nargs++] = "-sDEVICE=plan9";
	args[nargs++] = "-sOutputFile=/fd/3";
	args[nargs++] = "-dQUIET";
	args[nargs++] = "-r100";
	sprint(tb, "-dTextAlphaBits=%d", textbits);
	sprint(gb, "-dGraphicsAlphaBits=%d", gfxbits);
	if(textbits)
		args[nargs++] = tb;
	if(gfxbits)
		args[nargs++] = gb;
	args[nargs++] = "-";
	args[nargs] = nil;

	gspid = fork();
	if(gspid == 0) {
		close(stdinout[1]);
		close(dataout[1]);
		close(errout[1]);

		/*
		 * Horrible problem: we want to dup fd's 0-4 below,
		 * but some of the source fd's might have those small numbers.
		 * So we need to reallocate those.  In order to not step on
		 * anything else, we'll dup the fd's to higher ones using
		 * dup(x, -1), but we need to use up the lower ones first.
		 */
		while((devnull = open("/dev/null", ORDWR)) < 5)
			;

		stdinout[0] = dup(stdinout[0], -1);
		errout[0] = dup(errout[0], -1);
		dataout[0] = dup(dataout[0], -1);

		dup(stdinout[0], 0);
		dup(errout[0], 1);
		dup(devnull, 2);	/* never anything useful */
		dup(dataout[0], 3);
		dup(stdinout[0], 4);
		for(i=5; i<20; i++)
			close(i);
		exec("/bin/gs", args);
		wexits("exec");
	}
	close(stdinout[0]);
	close(errout[0]);
	close(dataout[0]);
	atexit(killgs);

	if(teegs)
		stdinout[1] = spawnreader(stdinout[1]);

	gsfd = g->gsfd = stdinout[1];
	g->gsdfd = dataout[1];
	g->gspid = gspid;

	spawnmonitor(errout[1]);
	Binit(&g->gsrd, g->gsfd, OREAD);

	gscmd(g, "/PAGEOUT (/fd/4) (w) file def\n");
	gscmd(g, "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n");
	waitgs(g);

	return 0;
}

int
gscmd(GSInfo *gs, char *fmt, ...)
{
	char buf[1024];
	int n;

	va_list v;
	va_start(v, fmt);
	n = vseprint(buf, buf+sizeof buf, fmt, v) - buf;
	if(n <= 0)
		return n;

	if(chatty) {
		fprint(2, "cmd: ");
		write(2, buf, n);
	}

	if(write(gs->gsfd, buf, n) != 0)
		return -1;

	return n;
}

/*
 * set the dimensions of the bitmap we expect to get back from GS.
 */
void
setdim(GSInfo *gs, Rectangle bbox, int ppi, int landscape)
{
	Rectangle pbox;

	if(chatty)
		fprint(2, "setdim: bbox=%R\n", bbox);

	if(ppi)
		gs->ppi = ppi;

	gscmd(gs, "mark\n");
	if(ppi)
		gscmd(gs, "/HWResolution [%d %d]\n", ppi, ppi);

	if(!Dx(bbox))
		bbox = Rect(0, 0, 612, 792);	/* 8½×11 */

	switch(landscape){
	case 0:
		pbox = bbox;
		break;
	case 1:
		pbox = Rect(bbox.min.y, bbox.min.x, bbox.max.y, bbox.max.x);
		break;
	}
	gscmd(gs, "/PageSize [%d %d]\n", Dx(pbox), Dy(pbox));
	gscmd(gs, "/Margins [%d %d]\n", -pbox.min.x, -pbox.min.y);
	gscmd(gs, "currentdevice putdeviceprops pop\n");
	gscmd(gs, "/#copies 1 store\n");

	if(!eqpt(bbox.min, ZP))
		gscmd(gs, "%d %d translate\n", -bbox.min.x, -bbox.min.y);

	switch(landscape){
	case 0:
		break;
	case 1:
		gscmd(gs, "%d 0 translate\n", Dy(bbox));
		gscmd(gs, "90 rotate\n");
		break;
	}

	waitgs(gs);
}

void
waitgs(GSInfo *gs)
{
	/* we figure out that gs is done by telling it to
	 * print something and waiting until it does.
	 */
	char *p;
	Biobuf *b = &gs->gsrd;
	uchar buf[1024];
	int n;

//	gscmd(gs, "(\\n**bstack\\n) print flush\n");
//	gscmd(gs, "stack flush\n");
//	gscmd(gs, "(**estack\\n) print flush\n");
	gscmd(gs, "(\\n//GO.SYSIN DD\\n) PAGE==\n");

	alarm(300*1000);
	for(;;) {
		p = Brdline(b, '\n');
		if(p == nil) {
			n = Bbuffered(b);
			if(n <= 0)
				break;
			if(n > sizeof buf)
				n = sizeof buf;
			Bread(b, buf, n);
			continue;
		}
		p[Blinelen(b)-1] = 0;
		if(chatty) fprint(2, "p: ");
		if(chatty) write(2, p, Blinelen(b)-1);
		if(chatty) fprint(2, "\n");
		if(strstr(p, "Error:")) {
			alarm(0);
			fprint(2, "ghostscript error: %s\n", p);
			wexits("gs error");
		}

		if(strstr(p, "//GO.SYSIN DD")) {
			break;
		}
	}
	alarm(0);
}
