#include <fitz.h>
#include <mupdf.h>

static char *password = "";
static int dodecode = 0;
static int dorepair = 0;
static int doprintxref = 0;
static int doprintpages = 0;

void usage()
{
	fprintf(stderr, "usage: mupdf [-drxp] [-u password] file.pdf\n");
	exit(1);
}

/*
 * Debug-print stream contents
 */

static int safecol = 0;

void printsafe(unsigned char *buf, int n)
{
	int i;
	for (i = 0; i < n; i++) {
		if (buf[i] == '\r' || buf[i] == '\n') {
			printf("\n");
			safecol = 0;
		}
		else if (buf[i] < 32 || buf[i] > 126) {
			printf(".");
			safecol ++;
		}
		else {
			printf("%c", buf[i]);
			safecol ++;
		}
		if (safecol == 79) {
			printf("\n");
			safecol = 0;
		}
	}
}

void decodestream(pdf_xref *xref, fz_obj *stream, int oid, int gid, int ofs)
{
	fz_error *error;
	unsigned char buf[512];

	safecol = 0;

	error = pdf_openstream0(xref, stream, oid, gid, ofs);
	if (error) fz_abort(error);

	while (1)
	{
		int n = fz_read(xref->file, buf, sizeof buf);
		if (n == 0)
			break;
		if (n < 0)
			fz_abort(fz_ferror(xref->file));
		printsafe(buf, n);
	}

	pdf_closestream(xref);
}

void copystream(pdf_xref *xref, fz_obj *stream, int ofs)
{
	fz_error *error;
	unsigned char buf[512];
	fz_filter *filter;
	fz_obj *obj;
	int len;

	safecol = 0;

	obj = fz_dictgets(stream, "Length");
	error = pdf_resolve(&obj, xref);
	if (error) fz_abort(error);
	len = fz_toint(obj);
	fz_dropobj(obj);

	error = fz_newnullfilter(&filter, len);
	if (error) fz_abort(error);

	fz_seek(xref->file, ofs);

	error = fz_pushfilter(xref->file, filter);
	if (error) fz_abort(error);

	while (1)
	{
		int n = fz_read(xref->file, buf, sizeof buf);
		if (n == 0)
			break;
		if (n < 0)
			fz_abort(fz_ferror(xref->file));
		printsafe(buf, n);
	}

	fz_popfilter(xref->file);
}

void printobject(pdf_xref *xref, int oid, int gid)
{
	fz_error *error;
	int stmofs;
	fz_obj *obj;

	error = pdf_loadobject0(&obj, xref, oid, gid, &stmofs);
	if (error) fz_abort(error);

	printf("%d %d obj\n", oid, gid);
	fz_debugobj(obj);
	printf("\n");
	if (stmofs != -1) {
		printf("stream\n");
		if (dodecode)
			decodestream(xref, obj, oid, gid, stmofs);
		else
			copystream(xref, obj, stmofs);
		printf("endstream\n");
	}
	printf("endobj\n");

	fz_dropobj(obj);
}

/*
 * Draw page
 */

void runcsi(pdf_xref *xref, pdf_csi *csi, pdf_resources *rdb, fz_obj *stmref)
{
	fz_error *error;

	error = pdf_openstream(xref, stmref);
	if (error) fz_abort(error);

	error = pdf_runcsi(csi, rdb, xref->file);
	if (error) fz_abort(error);

	pdf_closestream(xref);
}

void showpage(pdf_xref *xref, fz_obj *page)
{
	fz_error *error;
	pdf_csi *csi;
	pdf_resources *rdb = nil;
	fz_obj *resources;
	fz_obj *contents;
	int i;

	fz_debugobj(page);
	printf("\n");

	resources = fz_dictgets(page, "Resources");
	if (resources)
	{
		error = pdf_resolve(&resources, xref);
		if (error) fz_abort(error);

		error = pdf_loadresources(&rdb, xref, resources);
		if (error) fz_abort(error);

		// parse resources into native res dict
		fz_dropobj(resources);
	}
	else
		fz_abort(fz_throw("syntaxerror: missing resource dictionary"));

printf("resources:\n");
printf("  font:\n");
fz_debugobj(rdb->font);
printf("\n  extgstate:\n");
fz_debugobj(rdb->extgstate);
printf("\nfitz tree:\n");

	error = pdf_newcsi(&csi);
	if (error) fz_abort(error);

	contents = fz_dictgets(page, "Contents");
	if (contents)
	{
		if (fz_isarray(contents))
		{
			for (i = 0; i < fz_arraylen(contents); i++)
			{
				runcsi(xref, csi, rdb, fz_arrayget(contents, i));
			}
		}
		else
		{
			// XXX resolve and check if it is an array
			runcsi(xref, csi, rdb, contents);
		}
	}

	fz_debugtree(csi->tree);

	{
		fz_pixmap *pix;
		fz_renderer *gc;
		fz_matrix ctm;

#define W 612
#define H 792

#define xW 1106
#define xH 1548

		fz_newrenderer(&gc);
		fz_newpixmap(&pix, 0, 0, W, H, 1, 0);
		ctm = fz_concat(fz_translate(0, -H), fz_scale(1,-1));

		memset(pix->samples, 0x00, pix->stride * pix->h * 2);

printf("rendering!\n");
		fz_rendernode(gc, csi->tree->root, ctm, pix);
printf("done!\n");
		fz_debugpixmap(pix);

		fz_freepixmap(pix);
		fz_freerenderer(gc);
	}

	pdf_freecsi(csi);
}

int main(int argc, char **argv)
{
	fz_error *error;
	char *filename;
	pdf_xref *xref;
	pdf_pagetree *pages;
	int c;

	while ((c = getopt(argc, argv, "drxopu:")) != -1)
	{
		switch (c)
		{
		case 'd':
			dodecode ++;
			break;
		case 'r':
			dorepair ++;
			break;
		case 'x':
			doprintxref ++;
			break;
		case 'p':
			doprintpages ++;
			break;
		case 'u':
			password = optarg;
			break;
		default:
			usage();
		}
	}

	if (argc - optind == 0)
		usage();

	filename = argv[optind++];

	error = pdf_newxref(&xref);
	if (error)
		fz_abort(error);

	if (dorepair)
		error = pdf_repairxref(xref, filename);
	else
		error = pdf_openxref(xref, filename);
	if (error)
		fz_abort(error);

	error = pdf_decryptxref(xref);
	if (error)
		fz_abort(error);

	if (xref->crypt)
	{
		error = pdf_setpassword(xref->crypt, password);
		if (error) fz_abort(error);
	}

	if (doprintxref)
		pdf_debugxref(xref);

	if (doprintpages)
	{
		error = pdf_loadpagetree(&pages, xref);
		if (error) fz_abort(error);

		if (optind == argc)
		{
			printf("pagetree\n");
			pdf_debugpagetree(pages);
			printf("\n");
		}
		else
		{
			for ( ; optind < argc; optind++)
			{
				int page = atoi(argv[optind]);
				if (page < 1 || page > pages->count)
					fprintf(stderr, "page out of bounds: %d\n", page);
				printf("page %d\n", page);
				showpage(xref, pages->pobj[page - 1]);
			}
		}
	}

	else
	{
		if (optind == argc)
		{
			printf("trailer\n");
			fz_debugobj(xref->trailer);
			printf("\n");
		}

		for ( ; optind < argc; optind++)
		{
			printobject(xref, atoi(argv[optind]), 0);
			printf("\n");
		}
	}

printf("done.\n");

	pdf_closexref(xref);

	return 0;
}