#include "ghostxps.h"

#include <expat.h>

#define REL_START_PART "http://schemas.microsoft.com/xps/2005/06/fixedrepresentation"
#define REL_REQUIRED_RESOURCE "http://schemas.microsoft.com/xps/2005/06/required-resource"
#define REL_REQUIRED_RESOURCE_RECURSIVE "http://schemas.microsoft.com/xps/2005/06/required-resource#recursive"

#define CT_FIXDOC "application/vnd.ms-package.xps-fixeddocument+xml"
#define CT_FIXDOCSEQ "application/vnd.ms-package.xps-fixeddocumentsequence+xml"
#define CT_FIXPAGE "application/vnd.ms-package.xps-fixedpage+xml"

int xps_doc_trace = 0;

/*
 * Content types are stored in two separate binary trees.
 * One lists Override entries, which map a part name to a type.
 * The other lists Default entries, which map a file extension to a type.
 */

void xps_debug_type_map(xps_context_t *ctx, char *label, xps_type_map_t *node)
{
    if (node)
    {
	if (node->left)
	    xps_debug_type_map(ctx, label, node->left);
	dprintf3("%s name=%s type=%s\n", label, node->name, node->type);
	if (node->right)
	    xps_debug_type_map(ctx, label, node->right);
    }
}

static xps_type_map_t *
xps_new_type_map(xps_context_t *ctx, char *name, char *type)
{
    xps_type_map_t *node;

    node = xps_alloc(ctx, sizeof(xps_type_map_t));
    if (!node)
	goto cleanup;

    node->name = xps_strdup(ctx, name);
    node->type = xps_strdup(ctx, type);
    node->left = NULL;
    node->right = NULL;

    if (!node->name)
	goto cleanup;
    if (!node->type)
	goto cleanup;

    return node;

cleanup:
    if (node)
    {
	if (node->name) xps_free(ctx, node->name);
	if (node->type) xps_free(ctx, node->type);
	xps_free(ctx, node);
    }
    return NULL;
}

static void
xps_add_type_map(xps_context_t *ctx, xps_type_map_t *node, char *name, char *type)
{
    int cmp = strcmp(node->name, name);
    if (cmp < 0)
    {
	if (node->left)
	    xps_add_type_map(ctx, node->left, name, type);
	else
	    node->left = xps_new_type_map(ctx, name, type);
    }
    else if (cmp > 0)
    {
	if (node->right)
	    xps_add_type_map(ctx, node->right, name, type);
	else
	    node->right = xps_new_type_map(ctx, name, type);
    }
    else
    {
	/* it's a duplicate so we don't do anything */
    }
}

void
xps_free_type_map(xps_context_t *ctx, xps_type_map_t *node)
{
    if (node)
    {
	xps_free_type_map(ctx, node->left);
	xps_free_type_map(ctx, node->right);
	xps_free(ctx, node->name);
	xps_free(ctx, node->type);
	xps_free(ctx, node);
    }
}

static void
xps_add_override(xps_context_t *ctx, char *part_name, char *content_type)
{
    if (ctx->overrides == NULL)
	ctx->overrides = xps_new_type_map(ctx, part_name, content_type);
    else
	xps_add_type_map(ctx, ctx->overrides, part_name, content_type);
}

static void
xps_add_default(xps_context_t *ctx, char *extension, char *content_type)
{
    if (ctx->defaults == NULL)
	ctx->defaults = xps_new_type_map(ctx, extension, content_type);
    else
	xps_add_type_map(ctx, ctx->defaults, extension, content_type);
}

static char *
xps_find_override(xps_type_map_t *node, char *partname)
{
    if (node)
    {
	int cmp = strcmp(node->name, partname);
	if (cmp < 0)
	    return xps_find_override(node->left, partname);
	if (cmp == 0)
	    return node->type;
	if (cmp > 0)
	    return xps_find_override(node->right, partname);
    }
    return NULL;
}

static char *
xps_find_default(xps_type_map_t *node, char *extension)
{
    if (node)
    {
	int cmp = strcmp(node->name, extension);
	if (cmp < 0)
	    return xps_find_default(node->left, extension);
	if (cmp == 0)
	    return node->type;
	if (cmp > 0)
	    return xps_find_default(node->right, extension);
    }
    return NULL;
}

char *
xps_get_content_type(xps_context_t *ctx, char *partname)
{
    char *extension;
    char *type;

    type = xps_find_override(ctx->overrides, partname);
    if (type)
    {
	return type;
    }

    extension = strrchr(partname, '.');
    if (extension)
	extension ++;

    type = xps_find_default(ctx->defaults, extension);
    if (type)
    {
	return type;
    }

    return NULL;
}

/*
 * Relationships are stored in a linked list hooked into the part which
 * is the source of the relation.
 */

void xps_debug_parts(xps_context_t *ctx)
{
    xps_part_t *part = ctx->first_part;
    xps_relation_t *rel;
    while (part)
    {
	dprintf2("part '%s' size=%d\n", part->name, part->size);
	for (rel = part->relations; rel; rel = rel->next)
	    dprintf2("     target=%s type=%s\n", rel->target, rel->type);
	part = part->next;
    }
}

int
xps_add_relation(xps_context_t *ctx, char *source, char *target, char *type)
{
    xps_relation_t *node;
    xps_part_t *part;

    part = xps_find_part(ctx, source);

    /* No existing part found. Create a blank part and hook it in. */
    if (part == NULL)
    {
	part = xps_new_part(ctx, source, 0);
	if (!part)
	    return gs_rethrow(-1, "cannot create new part");
    }

    /* Check for duplicates. */
    for (node = part->relations; node; node = node->next)
	if (!strcmp(node->target, target))
	    return 0;

    node = xps_alloc(ctx, sizeof(xps_relation_t));
    if (!node)
	return gs_rethrow(-1, "cannot create new relation");

    node->target = xps_strdup(ctx, target);
    node->type = xps_strdup(ctx, type);
    if (!node->target || !node->type)
    {
	if (node->target) xps_free(ctx, node->target);
	if (node->type) xps_free(ctx, node->type);
	xps_free(ctx, node);
	return gs_rethrow(-1, "cannot copy relation strings");
    }

    node->next = part->relations;
    part->relations = node;

    return 0;
}

void
xps_free_relations(xps_context_t *ctx, xps_relation_t *node)
{
    xps_relation_t *next;
    while (node)
    {
	next = node->next;
	xps_free(ctx, node->target);
	xps_free(ctx, node->type);
	xps_free(ctx, node);
	node = next;
    }
}

/*
 * <DocumentReference> -- fixdocseq
 * <PageContent> -- fixdoc
 *
 * TODO: We should really look at the root StartPart relationship
 * for the FixedDocumentSequence and follow the DocumentReferences
 * therein for the page sequence. For now, we'll cheat and read
 * any PageContent references in the order they are in the file.
 */

void xps_debug_fixdocseq(xps_context_t *ctx)
{
    xps_document_t *fixdoc = ctx->first_fixdoc;
    xps_page_t *page = ctx->first_page;

    if (ctx->start_part)
	dprintf1("start part %s\n", ctx->start_part);

    while (fixdoc)
    {
	dprintf1("fixdoc %s\n", fixdoc->name);
	fixdoc = fixdoc->next;
    }

    while (page)
    {
	dprintf3("page %s w=%d h=%d\n", page->name, page->width, page->height);
	page = page->next;
    }
}

int
xps_add_fixed_document(xps_context_t *ctx, char *name)
{
    xps_document_t *fixdoc;

    /* Check for duplicates first */
    for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
	if (!strcmp(fixdoc->name, name))
	    return 0;

    if (xps_doc_trace)
	dprintf1("doc: adding fixdoc %s\n", name);

    fixdoc = xps_alloc(ctx, sizeof(xps_document_t));
    if (!fixdoc)
	return -1;

    fixdoc->name = xps_strdup(ctx, name);
    if (!fixdoc->name)
    {
	xps_free(ctx, fixdoc);
	return -1;
    }

    fixdoc->next = NULL;

    if (!ctx->first_fixdoc)
    {
	ctx->first_fixdoc = fixdoc;
	ctx->last_fixdoc = fixdoc;
    }
    else
    {
	ctx->last_fixdoc->next = fixdoc;
	ctx->last_fixdoc = fixdoc;
    }

    return 0;
}

void
xps_free_fixed_documents(xps_context_t *ctx)
{
    xps_document_t *node = ctx->first_fixdoc;
    while (node)
    {
	xps_document_t *next = node->next;
	xps_free(ctx, node->name);
	xps_free(ctx, node);
	node = next;
    }
}

int
xps_add_fixed_page(xps_context_t *ctx, char *name, int width, int height)
{
    xps_page_t *page;

    /* Check for duplicates first */
    for (page = ctx->first_page; page; page = page->next)
	if (!strcmp(page->name, name))
	    return 0;

    if (xps_doc_trace)
	dprintf1("doc: adding page %s\n", name);

    page = xps_alloc(ctx, sizeof(xps_page_t));
    if (!page)
	return -1;

    page->name = xps_strdup(ctx, name);
    if (!page->name)
    {
	xps_free(ctx, page);
	return -1;
    }

    page->width = width;
    page->height = height;
    page->next = NULL;

    if (!ctx->first_page)
    {
	ctx->first_page = page;
	ctx->last_page = page;
    }
    else
    {
	ctx->last_page->next = page;
	ctx->last_page = page;
    }

    /* first page, or we processed all the previous pages */
    if (ctx->next_page == NULL)
	ctx->next_page = page;

    return 0;
}

void
xps_free_fixed_pages(xps_context_t *ctx)
{
    xps_page_t *node = ctx->first_page;
    while (node)
    {
	xps_page_t *next = node->next;
	xps_free(ctx, node->name);
	xps_free(ctx, node);
	node = next;
    }
}

/*
 * Periodically free old parts and resources that
 * will not be used any more. This looks at discard control
 * information, and assumes that a given fixed page will
 * not be drawn more than once.
 */

void
xps_free_used_parts(xps_context_t *ctx)
{
    /* TODO: actually do what we should. for now we just free cached resources. */

    xps_part_t *part = ctx->first_part;
    while (part)
    {       
	xps_part_t *next = part->next;
	xps_free_part_caches(ctx, part);
	part = next;
    }
}

/*
 * Parse the metadata [Content_Types.xml] and _rels/XXX.rels parts.
 * These should be parsed eagerly as they are interleaved, so the
 * parsing needs to be able to cope with incomplete xml.
 *
 * We re-parse the part every time a new part of the piece comes in.
 * The binary trees in xps_context_t make sure that the information
 * is not duplicated (since the entries are often parsed many times).
 *
 * We hook up unique expat handlers for this, and ignore any expat
 * errors that occur.
 */

static void
xps_part_from_relation(char *output, char *name)
{
    char *p, *q;
    strcpy(output, name);
    p = strstr(output, "_rels/");
    q = strstr(name, "_rels/");
    if (p)
    {
	*p = 0;
	strcat(output, q + 6);
    }
    p = strstr(output, ".rels");
    if (p)
    {
	*p = 0;
    }
}

static void
xps_handle_metadata(void *zp, char *name, char **atts)
{
    xps_context_t *ctx = zp;
    int i;

    if (!strcmp(name, "Default"))
    {
	char *extension = NULL;
	char *type = NULL;

	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "Extension"))
		extension = atts[i + 1];
	    if (!strcmp(atts[i], "ContentType"))
		type = atts[i + 1];
	}

	if (extension && type)
	    xps_add_default(ctx, extension, type);
    }

    if (!strcmp(name, "Override"))
    {
	char *partname = NULL;
	char *type = NULL;

	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "PartName"))
		partname = atts[i + 1];
	    if (!strcmp(atts[i], "ContentType"))
		type = atts[i + 1];
	}

	if (partname && type)
	    xps_add_override(ctx, partname, type);
    }

    if (!strcmp(name, "Relationship"))
    {
	char srcbuf[1024];
	char tgtbuf[1024];
	char dirbuf[1024];
	char *target = NULL;
	char *type = NULL;
	char *p;

	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "Target"))
		target = atts[i + 1];
	    if (!strcmp(atts[i], "Type"))
		type = atts[i + 1];
	}

	if (target && type)
	{
	    xps_part_from_relation(srcbuf, ctx->last_part->name);

	    strcpy(dirbuf, srcbuf);
	    p = strrchr(dirbuf, '/');
	    if (p)
		p[1] = 0;

	    xps_absolute_path(tgtbuf, dirbuf, target);

	    xps_add_relation(ctx, srcbuf, tgtbuf, type);
	}
    }

    if (!strcmp(name, "DocumentReference"))
    {
	char *source = NULL;
	char srcbuf[1024];

	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "Source"))
		source = atts[i + 1];
	}

	if (source)
	{
	    xps_absolute_path(srcbuf, ctx->pwd, source);
	    xps_add_fixed_document(ctx, srcbuf);
	}
    }

    if (!strcmp(name, "PageContent"))
    {
	char *source = NULL;
	char srcbuf[1024];
	int width = 0;
	int height = 0;

	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "Source"))
		source = atts[i + 1];
	    if (!strcmp(atts[i], "Width"))
		width = atoi(atts[i + 1]);
	    if (!strcmp(atts[i], "Height"))
		height = atoi(atts[i + 1]);
	}

	if (source)
	{
	    xps_absolute_path(srcbuf, ctx->pwd, source);
	    xps_add_fixed_page(ctx, srcbuf, width, height);
	}
    }
}

static int
xps_process_metadata(xps_context_t *ctx, xps_part_t *part)
{
    XML_Parser xp;
    char *s;

    /* Save directory name part */
    strcpy(ctx->pwd, part->name);
    s = strrchr(ctx->pwd, '/');
    if (s)
	s[1] = 0;

    xp = XML_ParserCreate(NULL);
    if (!xp)
	return -1;

    XML_SetUserData(xp, ctx);
    XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
    XML_SetStartElementHandler(xp, (XML_StartElementHandler)xps_handle_metadata);

    (void) XML_Parse(xp, part->data, part->size, 1);

    XML_ParserFree(xp);

    return 0;
}

/*
 * Scan FixedPage XML for required resources:
 *
 *   <Glyphs FontUri=... >
 *   <ImageBrush ImageSource=... >
 *   <ResourceDictionary Source=... >
 */

static void
xps_trim_url(char *path)
{
    char *p = strrchr(path, '#');
    if (p)
	*p = 0;
}

static void
xps_parse_color_relation(xps_context_t *ctx, char *string)
{
    char path[1024];
    char buf[1024];
    char *sp, *ep;

    /* "ContextColor /Resources/Foo.icc 1,0.3,0.5,1.0" */

    strcpy(buf, string);
    sp = strchr(buf, ' ');
    if (sp)
    {
	sp ++;
	ep = strchr(sp, ' ');
	if (ep)
	{
	    *ep = 0;
	    xps_absolute_path(path, ctx->pwd, sp);
	    xps_trim_url(path);
	    xps_add_relation(ctx, ctx->state, path, REL_REQUIRED_RESOURCE);
	}
    }
}

static void
xps_parse_content_relations_imp(void *zp, char *name, char **atts)
{
    xps_context_t *ctx = zp;
    char path[1024];
    int i;

    if (!strcmp(name, "Glyphs"))
    {
	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "FontUri"))
	    {
		xps_absolute_path(path, ctx->pwd, atts[i+1]);
		xps_trim_url(path);
		xps_add_relation(ctx, ctx->state, path, REL_REQUIRED_RESOURCE);
	    }
	}
    }

    if (!strcmp(name, "ImageBrush"))
    {
	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "ImageSource"))
	    {
		xps_absolute_path(path, ctx->pwd, atts[i+1]);
		xps_trim_url(path);
		xps_add_relation(ctx, ctx->state, path, REL_REQUIRED_RESOURCE);
	    }
	}
    }

    if (!strcmp(name, "ResourceDictionary"))
    {
	for (i = 0; atts[i]; i += 2)
	{
	    if (!strcmp(atts[i], "Source"))
	    {
		xps_absolute_path(path, ctx->pwd, atts[i+1]);
		xps_trim_url(path);
		xps_add_relation(ctx, ctx->state, path, REL_REQUIRED_RESOURCE_RECURSIVE);
	    }
	}
    }

    if (!strcmp(name, "SolidColorBrush") || !strcmp(name, "GradientStop"))
    {
	for (i = 0; atts[i]; i += 2)
	    if (!strcmp(atts[i], "Color"))
		xps_parse_color_relation(ctx, atts[i + 1]);
    }

    if (!strcmp(name, "Glyphs") || !strcmp(name, "Path"))
    {
	for (i = 0; atts[i]; i += 2)
	    if (!strcmp(atts[i], "Fill") || !strcmp(atts[i], "Stroke"))
		xps_parse_color_relation(ctx, atts[i + 1]);
    }
}

int
xps_parse_content_relations(xps_context_t *ctx, xps_part_t *part)
{
    XML_Parser xp;

    ctx->state = part->name;

    if (xps_doc_trace)
	dprintf1("doc: parsing relations from content (%s)\n", part->name);

    xp = XML_ParserCreate(NULL);
    if (!xp)
	return -1;

    XML_SetUserData(xp, ctx);
    XML_SetParamEntityParsing(xp, XML_PARAM_ENTITY_PARSING_NEVER);
    XML_SetStartElementHandler(xp, (XML_StartElementHandler)xps_parse_content_relations_imp);

    (void) XML_Parse(xp, part->data, part->size, 1);

    XML_ParserFree(xp);

    if (xps_doc_trace)
    {
	xps_relation_t *rel;
	for (rel = part->relations; rel; rel = rel->next)
	    dprintf1("  relation %s\n", rel->target);
    }

    ctx->state = NULL;

    return 0;
}

static int
xps_validate_resources(xps_context_t *ctx, xps_part_t *part)
{
    xps_relation_t *rel;
    xps_part_t *subpart;

    for (rel = part->relations; rel; rel = rel->next)
    {
	if (!strcmp(rel->type, REL_REQUIRED_RESOURCE_RECURSIVE))
	{
	    subpart = xps_find_part(ctx, rel->target);
	    if (!subpart || !subpart->complete)
		return 0;
	    if (!subpart->relations_complete)
	    {
		xps_parse_content_relations(ctx, subpart);
		subpart->relations_complete = 1;
	    }
	    if (!xps_validate_resources(ctx, subpart))
		return 0;
	}

	if (!strcmp(rel->type, REL_REQUIRED_RESOURCE))
	{
	    subpart = xps_find_part(ctx, rel->target);
	    if (!subpart || !subpart->complete)
		return 0;
	}
    }

    return 1;
}

int
xps_process_part(xps_context_t *ctx, xps_part_t *part)
{
    xps_document_t *fixdoc;

    if (getenv("XPS_DOC_TRACE"))
	xps_doc_trace = 1;

    if (xps_doc_trace)
	dprintf2("doc: found part %s %s\n", part->name, part->complete ? "" : "(piece)");

    /*
     * These two are magic Open Packaging Convention names.
     */

    if (strstr(part->name, "[Content_Types].xml"))
    {
	xps_process_metadata(ctx, part);
    }

    if (strstr(part->name, "_rels/"))
    {
	xps_process_metadata(ctx, part);

	if (part->complete)
	{
	    char realname[1024];
	    xps_part_t *realpart;
	    xps_part_from_relation(realname, part->name);
	    realpart = xps_find_part(ctx, realname);
	    if (realpart)
	    {
		realpart->relations_complete = 1;
	    }
	}
    }

    /*
     * For the rest we need to track the relationships
     * and content-types given by the previous two types.
     *
     * We can't do anything until we have the relationship
     * for the start part.
     */

    if (!ctx->start_part)
    {
	xps_part_t *rootpart;
	rootpart = xps_find_part(ctx, "/");
	if (rootpart)
	{
	    xps_relation_t *rel;
	    for (rel = rootpart->relations; rel; rel = rel->next)
	    {
		if (!strcmp(rel->type, REL_START_PART))
		{
		    xps_part_t *startpart;

		    ctx->start_part = rel->target;

		    if (xps_doc_trace)
			dprintf1("doc: adding start part '%s'\n", ctx->start_part);

		    startpart = xps_find_part(ctx, rel->target);
		    if (startpart)
			xps_process_metadata(ctx, startpart);
		}
	    }
	}
    }

    /*
     * Read the start part (which is a FixedDocumentSequence) if it
     * is the current part.
     */

    if (ctx->start_part)
    {
	if (!strcmp(part->name, ctx->start_part))
	{
	    xps_process_metadata(ctx, part);
	}
    }

    /*
     * Follow the FixedDocumentSequence and parse the
     * listed FixedDocuments that we have available.
     */

    for (fixdoc = ctx->first_fixdoc; fixdoc; fixdoc = fixdoc->next)
    {
	xps_part_t *fixdocpart = xps_find_part(ctx, fixdoc->name);
	if (fixdocpart)
	{
	    xps_process_metadata(ctx, fixdocpart);
	    if (!fixdocpart->complete)
		break; /* incomplete fixdocpart, try parsing more later */
	}
    }

    /*
     * If we know which page part is next, check if we
     * have all the page dependencies. If everything is
     * ready: parse and render.
     */

    while (ctx->next_page)
    {
	xps_part_t *pagepart = xps_find_part(ctx, ctx->next_page->name);
	if (pagepart && pagepart->complete)
	{
	    xps_relation_t *rel;
	    int have_resources;
	    char *s;

	    /* Set current directory for resolving relative path names */
	    strcpy(ctx->pwd, pagepart->name);
	    s = strrchr(ctx->pwd, '/');
	    if (s)
		s[1] = 0;

	    if (!pagepart->relations_complete)
	    {
		xps_parse_content_relations(ctx, pagepart);
		pagepart->relations_complete = 1;
	    }

	    if (xps_validate_resources(ctx, pagepart))
	    {
		int code = xps_parse_fixed_page(ctx, pagepart);
		if (code < 0)
		    return code;

		ctx->next_page = ctx->next_page->next;

		xps_free_used_parts(ctx);

		continue;
	    }
	}

	break;
    }

    return 0;
}


