#include "fitz.h"
#include "muxps.h"

#ifdef _MSC_VER
#include <winsock2.h>
#else
#include <sys/time.h>
#endif

char *output = NULL;
float resolution = 72;

int showxml = 0;
int showtext = 0;
int showtime = 0;
int showmd5 = 0;
int showoutline = 0;
int savealpha = 0;
int uselist = 1;

fz_colorspace *colorspace;
fz_glyph_cache *glyphcache;
char *filename;
fz_context *ctx;

struct {
	int count, total;
	int min, max;
	int minpage, maxpage;
} timing;

static void die(fz_error error)
{
	fz_error_handle(error, "aborting");
	exit(1);
}

static void usage(void)
{
	fprintf(stderr,
		"usage: xpsdraw [options] input.xps [pages]\n"
		"\t-o -\toutput filename (%%d for page number)\n"
		"\t\tsupported formats: pgm, ppm, pam, png\n"
		"\t-r -\tresolution in dpi (default: 72)\n"
		"\t-a\tsave alpha channel (only pam and png)\n"
		"\t-g\trender in grayscale\n"
		"\t-m\tshow timing information\n"
		"\t-t\tshow text (-tt for xml)\n"
		"\t-x\tshow display list\n"
		"\t-d\tdisable use of display list\n"
		"\t-5\tshow md5 checksums\n"
		"\t-l\tprint outline\n"
		"\tpages\tcomma separated list of ranges\n");
	exit(1);
}

static int gettime(void)
{
	static struct timeval first;
	static int once = 1;
	struct timeval now;
	if (once)
	{
		gettimeofday(&first, NULL);
		once = 0;
	}
	gettimeofday(&now, NULL);
	return (now.tv_sec - first.tv_sec) * 1000 + (now.tv_usec - first.tv_usec) / 1000;
}

static int isrange(char *s)
{
	while (*s)
	{
		if ((*s < '0' || *s > '9') && *s != '-' && *s != ',')
			return 0;
		s++;
	}
	return 1;
}

static void
xps_run_page(xps_document *doc, xps_page *page, fz_device *dev, fz_matrix ctm)
{
	doc->dev = dev;
	xps_parse_fixed_page(doc, ctm, page);
	doc->dev = NULL;
}

static void drawpage(xps_document *doc, int pagenum)
{
	xps_page *page;
	fz_display_list *list;
	fz_device *dev;
	int start;

	if (showtime)
	{
		start = gettime();
	}

	fz_try(doc->ctx)
	{
		page = xps_load_page(doc, pagenum - 1);
	}
	fz_catch(doc->ctx)
	{
		die(fz_error_note(1, "cannot load page %d in file '%s'", pagenum, filename));
	}

	list = NULL;

	if (uselist)
	{
		list = fz_new_display_list(doc->ctx);
		dev = fz_new_list_device(doc->ctx, list);
		xps_run_page(doc, page, dev, fz_identity);
		fz_free_device(dev);
	}

	if (showxml)
	{
		dev = fz_new_trace_device(doc->ctx);
		printf("<page number=\"%d\">\n", pagenum);
		if (list)
			fz_execute_display_list(list, dev, fz_identity, fz_infinite_bbox);
		else
			xps_run_page(doc, page, dev, fz_identity);
		printf("</page>\n");
		fz_free_device(dev);
	}

	if (showtext)
	{
		fz_text_span *text = fz_new_text_span(doc->ctx);
		dev = fz_new_text_device(doc->ctx, text);
		if (list)
			fz_execute_display_list(list, dev, fz_identity, fz_infinite_bbox);
		else
			xps_run_page(doc, page, dev, fz_identity);
		fz_free_device(dev);
		printf("[Page %d]\n", pagenum);
		if (showtext > 1)
			fz_debug_text_span_xml(text);
		else
			fz_debug_text_span(text);
		printf("\n");
		fz_free_text_span(doc->ctx, text);
	}

	if (showmd5 || showtime)
		printf("page %s %d", filename, pagenum);

	if (output || showmd5 || showtime)
	{
		float zoom;
		fz_matrix ctm;
		fz_rect rect;
		fz_bbox bbox;
		fz_pixmap *pix;

		rect.x0 = rect.y0 = 0;
		rect.x1 = page->width;
		rect.y1 = page->height;

		zoom = resolution / 96;
		ctm = fz_translate(0, -page->height);
		ctm = fz_concat(ctm, fz_scale(zoom, zoom));
		bbox = fz_round_rect(fz_transform_rect(ctm, rect));

		/* TODO: banded rendering and multi-page ppm */

		pix = fz_new_pixmap_with_rect(doc->ctx, colorspace, bbox);

		if (savealpha)
			fz_clear_pixmap(pix);
		else
			fz_clear_pixmap_with_color(pix, 255);

		dev = fz_new_draw_device(doc->ctx, glyphcache, pix);
		if (list)
			fz_execute_display_list(list, dev, ctm, bbox);
		else
			xps_run_page(doc, page, dev, ctm);
		fz_free_device(dev);

		if (output)
		{
			char buf[512];
			sprintf(buf, output, pagenum);
			if (strstr(output, ".pgm") || strstr(output, ".ppm") || strstr(output, ".pnm"))
				fz_write_pnm(doc->ctx, pix, buf);
			else if (strstr(output, ".pam"))
				fz_write_pam(doc->ctx, pix, buf, savealpha);
			else if (strstr(output, ".png"))
				fz_write_png(doc->ctx, pix, buf, savealpha);
		}

		if (showmd5)
		{
			fz_md5 md5;
			unsigned char digest[16];
			int i;

			fz_md5_init(&md5);
			fz_md5_update(&md5, pix->samples, pix->w * pix->h * pix->n);
			fz_md5_final(&md5, digest);

			printf(" ");
			for (i = 0; i < 16; i++)
				printf("%02x", digest[i]);
		}

		fz_drop_pixmap(doc->ctx, pix);
	}

	if (list)
		fz_free_display_list(doc->ctx, list);

	if (showtime)
	{
		int end = gettime();
		int diff = end - start;

		if (diff < timing.min)
		{
			timing.min = diff;
			timing.minpage = pagenum;
		}
		if (diff > timing.max)
		{
			timing.max = diff;
			timing.maxpage = pagenum;
		}
		timing.total += diff;
		timing.count ++;

		printf(" %dms", diff);
	}

	if (showmd5 || showtime)
		printf("\n");
}

static void drawrange(xps_document *doc, char *range)
{
	int page, spage, epage;
	char *spec, *dash;

	spec = fz_strsep(&range, ",");
	while (spec)
	{
		dash = strchr(spec, '-');

		if (dash == spec)
			spage = epage = xps_count_pages(doc);
		else
			spage = epage = atoi(spec);

		if (dash)
		{
			if (strlen(dash) > 1)
				epage = atoi(dash + 1);
			else
				epage = xps_count_pages(doc);
		}

		spage = CLAMP(spage, 1, xps_count_pages(doc));
		epage = CLAMP(epage, 1, xps_count_pages(doc));

		if (spage < epage)
			for (page = spage; page <= epage; page++)
				drawpage(doc, page);
		else
			for (page = spage; page >= epage; page--)
				drawpage(doc, page);

		spec = fz_strsep(&range, ",");
	}
}

static void drawoutline(xps_document *doc)
{
	fz_outline *outline = xps_load_outline(doc);
	if (showoutline > 1)
		fz_debug_outline_xml(outline, 0);
	else
		fz_debug_outline(outline, 0);
	fz_free_outline(outline);
}

int main(int argc, char **argv)
{
	int grayscale = 0;
	int accelerate = 1;
	xps_document *doc;
	int c;

	while ((c = fz_getopt(argc, argv, "o:p:r:Aadglmtx5")) != -1)
	{
		switch (c)
		{
		case 'o': output = fz_optarg; break;
		case 'r': resolution = atof(fz_optarg); break;
		case 'A': accelerate = 0; break;
		case 'a': savealpha = 1; break;
		case 'l': showoutline++; break;
		case 'm': showtime++; break;
		case 't': showtext++; break;
		case 'x': showxml++; break;
		case '5': showmd5++; break;
		case 'g': grayscale++; break;
		case 'd': uselist = 0; break;
		default: usage(); break;
		}
	}

	if (fz_optind == argc)
		usage();

	if (!showtext && !showxml && !showtime && !showmd5 && !showoutline && !output)
	{
		printf("nothing to do\n");
		exit(0);
	}

	if (accelerate)
		fz_accelerate();

	ctx = fz_new_context(&fz_alloc_default);
	if (ctx == NULL)
	{
		fprintf(stderr, "failed to initialise context");
		exit(1);
	}

	glyphcache = fz_new_glyph_cache(ctx);

	colorspace = fz_device_rgb;
	if (grayscale)
		colorspace = fz_device_gray;
	if (output && strstr(output, ".pgm"))
		colorspace = fz_device_gray;
	if (output && strstr(output, ".ppm"))
		colorspace = fz_device_rgb;

	timing.count = 0;
	timing.total = 0;
	timing.min = 1 << 30;
	timing.max = 0;
	timing.minpage = 0;
	timing.maxpage = 0;

	if (showxml)
		printf("<?xml version=\"1.0\"?>\n");

	while (fz_optind < argc)
	{
		filename = argv[fz_optind++];

		fz_try(ctx)
		{
			doc = xps_open_file(ctx, filename);
		}
		fz_catch(ctx)
		{
			die(fz_error_note(-1, "cannot open document: %s", filename));
		}

		if (showxml)
			printf("<document name=\"%s\">\n", filename);

		if (showoutline)
			drawoutline(doc);

		if (showtext || showxml || showtime || showmd5 || output)
		{
			if (fz_optind == argc || !isrange(argv[fz_optind]))
				drawrange(doc, "1-");
			if (fz_optind < argc && isrange(argv[fz_optind]))
				drawrange(doc, argv[fz_optind++]);
		}

		if (showxml)
			printf("</document>\n");

		xps_free_context(doc);
	}

	if (showtime)
	{
		printf("total %dms / %d pages for an average of %dms\n",
			timing.total, timing.count, timing.total / timing.count);
		printf("fastest page %d: %dms\n", timing.minpage, timing.min);
		printf("slowest page %d: %dms\n", timing.maxpage, timing.max);
	}

	fz_free_glyph_cache(ctx, glyphcache);
	fz_free_context(ctx);

	return 0;
}