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

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

void usage()
{
	fprintf(stderr, "usage: pdfdebug [-drxs] [-u password] file.pdf [oid ...]\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)
{
	FILE *copy;
	fz_error *error;
	unsigned char buf[512];

	if (dosave)
		copy = fopen("/tmp/dump.stm", "wb");

	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);

		if (dosave)
			fwrite(buf, 1, n, copy);
	}

	if (dosave)
		fclose(copy);

	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, 0);

	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);
}

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

	while ((c = getopt(argc, argv, "drxsopu:")) != -1)
	{
		switch (c)
		{
		case 's':
			dodecode ++;
			dosave ++;
		case 'd':
			dodecode ++;
			break;
		case 'r':
			dorepair ++;
			break;
		case 'x':
			doprintxref ++;
			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 (optind == argc)
	{
		printf("trailer\n");
		fz_debugobj(xref->trailer);
		printf("\n");
	}

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

	pdf_closexref(xref);

	return 0;
}