/* Copyright (C) 2001-2023 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied,
   modified or distributed except as expressly authorized under the terms
   of the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  39 Mesa Street, Suite 108A, San Francisco,
   CA 94129, USA, for further information.
*/


/* XPS interpreter - resource functions */

#include "ghostxps.h"

static xps_item_t *
xps_find_resource(xps_context_t *ctx, xps_resource_t *dict, char *name, char **urip)
{
    xps_resource_t *head, *node;
    for (head = dict; head; head = head->parent)
    {
        for (node = head; node; node = node->next)
        {
            if (!strcmp(node->name, name))
            {
                if (urip && head->base_uri)
                    *urip = head->base_uri;
                return node->data;
            }
        }
    }
    return NULL;
}

static xps_item_t *
xps_parse_resource_reference(xps_context_t *ctx, xps_resource_t *dict, char *att, char **urip)
{
    char name[1024];
    char *s;

    if (strstr(att, "{StaticResource ") != att)
        return NULL;

    gs_strlcpy(name, att + 16, sizeof name);
    s = strrchr(name, '}');
    if (s)
        *s = 0;

    return xps_find_resource(ctx, dict, name, urip);
}

void
xps_resolve_resource_reference(xps_context_t *ctx, xps_resource_t *dict,
        char **attp, xps_item_t **tagp, char **urip)
{
    if (*attp)
    {
        xps_item_t *rsrc = xps_parse_resource_reference(ctx, dict, *attp, urip);
        if (rsrc)
        {
            *attp = NULL;
            *tagp = rsrc;
        }
    }
}

static int
xps_parse_remote_resource_dictionary(xps_context_t *ctx, xps_resource_t **dictp, char *base_uri, char *source_att)
{
    char part_name[1024];
    char part_uri[1024];
    xps_resource_t *dict = *dictp;
    xps_part_t *part;
    xps_item_t *xml;
    char *s;
    int code;

    /* External resource dictionaries MUST NOT reference other resource dictionaries */
    xps_absolute_path(part_name, base_uri, source_att, sizeof part_name);
    part = xps_read_part(ctx, part_name);
    if (!part)
    {
        return gs_throw1(-1, "cannot find remote resource part '%s'", part_name);
    }

    xml = xps_parse_xml(ctx, part->data, part->size);
    if (!xml)
    {
        xps_free_part(ctx, part);
        return gs_rethrow(-1, "cannot parse xml");
    }

    if (strcmp(xps_tag(xml), "ResourceDictionary"))
    {
        xps_free_item(ctx, xml);
        xps_free_part(ctx, part);
        return gs_throw1(-1, "expected ResourceDictionary element (found %s)", xps_tag(xml));
    }

    gs_strlcpy(part_uri, part_name, sizeof part_uri);
    s = strrchr(part_uri, '/');
    if (s)
        s[1] = 0;

    code = xps_parse_resource_dictionary(ctx, &dict, part_uri, xml);
    if (code)
    {
        xps_free_item(ctx, xml);
        xps_free_part(ctx, part);
        return gs_rethrow1(code, "cannot parse remote resource dictionary: %s", part_uri);
    }

    if (dict != NULL)
        dict->base_xml = xml; /* pass on ownership */
    else
        xps_free_item(ctx, xml);

    xps_free_part(ctx, part);

    *dictp = dict;
    return gs_okay;
}

int
xps_parse_resource_dictionary(xps_context_t *ctx, xps_resource_t **dictp, char *base_uri, xps_item_t *root)
{
    xps_resource_t *head;
    xps_resource_t *entry;
    xps_item_t *node;
    char *source;
    char *key;
    int code;

    if (*dictp)
    {
        gs_warn("multiple resource dictionaries; ignoring all but the first");
        return gs_okay;
    }

    source = xps_att(root, "Source");
    if (source)
    {
        code = xps_parse_remote_resource_dictionary(ctx, dictp, base_uri, source);
        if (code)
            return gs_rethrow(code, "cannot parse remote resource dictionary");
        return gs_okay;
    }

    head = NULL;

    for (node = xps_down(root); node; node = xps_next(node))
    {
        /* Usually "x:Key"; we have already processed and stripped namespace */
        key = xps_att(node, "Key");
        if (key)
        {
            entry = xps_alloc(ctx, sizeof(xps_resource_t));
            if (!entry)
                return gs_throw(gs_error_VMerror, "cannot allocate resource entry");
            entry->name = key;
            entry->base_uri = NULL;
            entry->base_xml = NULL;
            entry->data = node;
            entry->next = head;
            entry->parent = NULL;
            head = entry;
        }
    }

    if (head)
    {
        head->base_uri = xps_strdup(ctx, base_uri);
    }
    else
    {
        gs_warn("empty resource dictionary");
    }

    *dictp = head;
    return gs_okay;
}

void
xps_free_resource_dictionary(xps_context_t *ctx, xps_resource_t *dict)
{
    xps_resource_t *next;
    while (dict)
    {
        next = dict->next;
        if (dict->base_xml)
            xps_free_item(ctx, dict->base_xml);
        if (dict->base_uri)
            xps_free(ctx, dict->base_uri);
        xps_free(ctx, dict);
        dict = next;
    }
}

void
xps_debug_resource_dictionary(xps_context_t *ctx, xps_resource_t *dict)
{
    while (dict)
    {
        if (dict->base_uri)
            dmprintf1(ctx->memory, "URI = '%s'\n", dict->base_uri);
        dmprintf2(ctx->memory, "KEY = '%s' VAL = "PRI_INTPTR"\n",
                  dict->name, (intptr_t)dict->data);
        if (dict->parent)
        {
            dmputs(ctx->memory, "PARENT = {\n");
            xps_debug_resource_dictionary(ctx, dict->parent);
            dmputs(ctx->memory, "}\n");
        }
        dict = dict->next;
    }
}
