/**
 * @file printer_xml.c
 * @author Michal Vasko <mvasko@cesnet.cz>
 * @author Radek Krejci <rkrejci@cesnet.cz>
 * @brief XML printer for libyang data structure
 *
 * Copyright (c) 2015 - 2022 CESNET, z.s.p.o.
 *
 * This source code is licensed under BSD 3-Clause License (the "License").
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://opensource.org/licenses/BSD-3-Clause
 */

#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "context.h"
#include "dict.h"
#include "log.h"
#include "ly_common.h"
#include "out.h"
#include "out_internal.h"
#include "parser_data.h"
#include "plugins_exts/metadata.h"
#include "plugins_internal.h"
#include "plugins_types.h"
#include "printer_data.h"
#include "printer_internal.h"
#include "set.h"
#include "tree.h"
#include "tree_data.h"
#include "tree_schema.h"
#include "xml.h"

/**
 * @brief XML printer context.
 */
struct xmlpr_ctx {
    struct ly_out *out;       /**< output specification */
    uint16_t level;           /**< current indentation level: 0 - no formatting, >= 1 indentation levels */
    uint32_t options;         /**< [Data printer flags](@ref dataprinterflags) */
    const struct ly_ctx *ctx; /**< libyang context */
    struct ly_set prefix;     /**< printed namespace prefixes */
    struct ly_set ns;         /**< printed namespaces */
};

#define LYXML_PREFIX_REQUIRED 0x01  /**< The prefix is not just a suggestion but a requirement. */
#define LYXML_PREFIX_DEFAULT  0x02  /**< The namespace is required to be a default (without prefix) */

static LY_ERR xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node);

/**
 * @brief Print a namespace if not already printed.
 *
 * @param[in] pctx XML printer context.
 * @param[in] ns Namespace to print, expected to be in dictionary.
 * @param[in] new_prefix Suggested new prefix, NULL for a default namespace without prefix. Stored in the dictionary.
 * @param[in] prefix_opts Prefix options.
 * @return Printed prefix of the namespace to use.
 */
static const char *
xml_print_ns(struct xmlpr_ctx *pctx, const char *ns, const char *new_prefix, uint32_t prefix_opts)
{
    uint32_t i;

    for (i = pctx->ns.count; i > 0; --i) {
        if (!new_prefix) {
            /* find default namespace */
            if (!pctx->prefix.objs[i - 1]) {
                if (!strcmp(pctx->ns.objs[i - 1], ns)) {
                    /* matching default namespace */
                    return pctx->prefix.objs[i - 1];
                }
                /* not matching default namespace */
                break;
            }
        } else {
            /* find prefixed namespace */
            if (!strcmp(pctx->ns.objs[i - 1], ns)) {
                if (!pctx->prefix.objs[i - 1]) {
                    /* default namespace is not interesting */
                    continue;
                }

                if (!strcmp(pctx->prefix.objs[i - 1], new_prefix) || !(prefix_opts & LYXML_PREFIX_REQUIRED)) {
                    /* the same prefix or can be any */
                    return pctx->prefix.objs[i - 1];
                }
            }
        }
    }

    /* suitable namespace not found, must be printed */
    ly_print_(pctx->out, " xmlns%s%s=\"%s\"", new_prefix ? ":" : "", new_prefix ? new_prefix : "", ns);

    /* and added into namespaces */
    if (new_prefix) {
        LY_CHECK_RET(lydict_insert(pctx->ctx, new_prefix, 0, &new_prefix), NULL);
    }
    LY_CHECK_RET(ly_set_add(&pctx->prefix, (void *)new_prefix, 1, NULL), NULL);
    LY_CHECK_RET(ly_set_add(&pctx->ns, (void *)ns, 1, &i), NULL);

    /* return it */
    return pctx->prefix.objs[i];
}

/**
 * @brief Print an opaque name (opaque node or an attribute) namespace.
 *
 * @param[in] pctx XML printer context.
 * @param[in] format Opaque name format.
 * @param[in] name Opaque name to print.
 * @param[in] prefix_opts Prefix options.
 * @return Printed prefix of the namespace to use.
 */
static const char *
xml_print_ns_opaq(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, const struct ly_opaq_name *name, uint32_t prefix_opts)
{
    const struct lys_module *mod;
    const char *ns_prefix;

    switch (format) {
    case LY_VALUE_XML:
        if (name->module_ns) {
            return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts);
        }
        break;
    case LY_VALUE_JSON:
        if (name->module_name) {
            mod = ly_ctx_get_module_latest(pctx->ctx, name->module_name);
            if (mod) {
                /* do not use the JSON prefix (module name) but YANG module prefix instead, if any */
                ns_prefix = (name->prefix && !(prefix_opts & LYXML_PREFIX_DEFAULT)) ? mod->prefix : NULL;

                return xml_print_ns(pctx, mod->ns, ns_prefix, prefix_opts);
            }
        }
        break;
    default:
        /* cannot be created */
        LOGINT(pctx->ctx);
    }

    return NULL;
}

/**
 * @brief Print prefix data.
 *
 * @param[in] ctx XML printer context.
 * @param[in] format Value prefix format, only ::LY_VALUE_XML supported.
 * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix).
 * @param[in] prefix_opts Prefix options changing the meaning of parameters.
 * @return LY_ERR value.
 */
static void
xml_print_ns_prefix_data(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, void *prefix_data, uint32_t prefix_opts)
{
    const struct ly_set *set;
    const struct lyxml_ns *ns;
    uint32_t i;

    switch (format) {
    case LY_VALUE_XML:
        set = prefix_data;
        for (i = 0; i < set->count; ++i) {
            ns = set->objs[i];
            if (!ns->prefix) {
                /* default namespace is not for the element */
                continue;
            }

            xml_print_ns(pctx, ns->uri, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : ns->prefix, prefix_opts);
        }
        break;
    default:
        /* cannot be created */
        LOGINT(pctx->ctx);
    }
}

/**
 * @brief Print metadata of a node.
 *
 * @param[in] pctx XML printer context.
 * @param[in] node Data node with metadata.
 */
static void
xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node)
{
    struct lyd_meta *meta;
    const struct lys_module *df_mod = NULL, *mod;
    struct ly_set ns_list = {0};
    LY_ARRAY_COUNT_TYPE u;
    ly_bool dynamic, filter_attrs = 0;
    const char *value;
    uint32_t i;

    /* with-defaults */
    if (node->schema->nodetype & LYD_NODE_TERM) {
        if (((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) ||
                ((pctx->options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(node))) {
            /* we have implicit OR explicit default node, print attribute only if context include with-defaults schema */
            if (ly_ctx_get_module_latest(LYD_CTX(node), "ietf-netconf-with-defaults")) {
                df_mod = ly_ctx_get_module_latest(LYD_CTX(node), "default");
            }
            if (df_mod) {
                ly_print_(pctx->out, " %s:default=\"true\"", xml_print_ns(pctx, df_mod->ns, df_mod->prefix, 0));
            }
        }
    }

    /* check for NETCONF filter unqualified attributes */
    if (!strcmp(node->schema->module->name, "notifications")) {
        filter_attrs = 1;
    } else {
        LY_ARRAY_FOR(node->schema->exts, u) {
            if (!strcmp(node->schema->exts[u].def->name, "get-filter-element-attributes") &&
                    !strcmp(node->schema->exts[u].def->module->name, "ietf-netconf")) {
                filter_attrs = 1;
                break;
            }
        }
    }

    for (meta = node->meta; meta; meta = meta->next) {
        if (!lyd_metadata_should_print(meta)) {
            continue;
        }

        /* store the module of the default namespace, NULL because there is none */
        ly_set_add(&ns_list, NULL, 0, NULL);

        /* print the value */
        value = LYSC_GET_TYPE_PLG(meta->value.realtype->plugin_ref)->print(LYD_CTX(node),
                &meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);

        /* print namespaces connected with the value's prefixes */
        for (i = 1; i < ns_list.count; ++i) {
            mod = ns_list.objs[i];
            xml_print_ns(pctx, mod->ns, mod->prefix, 1);
        }
        ly_set_erase(&ns_list, NULL);

        mod = meta->annotation->module;
        if (filter_attrs && !strcmp(mod->name, "ietf-netconf") && (!strcmp(meta->name, "type") ||
                !strcmp(meta->name, "select"))) {
            /* print special NETCONF filter unqualified attributes */
            ly_print_(pctx->out, " %s=\"", meta->name);
        } else {
            /* print the metadata with its namespace */
            ly_print_(pctx->out, " %s:%s=\"", xml_print_ns(pctx, mod->ns, mod->prefix, 1), meta->name);
        }

        /* print metadata value */
        if (value && value[0]) {
            lyxml_dump_text(pctx->out, value, 1);
        }
        ly_print_(pctx->out, "\"");
        if (dynamic) {
            free((void *)value);
        }
    }
}

/**
 * @brief Print generic XML element despite of the data node type.
 *
 * Prints the element name, attributes and necessary namespaces.
 *
 * @param[in] ctx XML printer context.
 * @param[in] node Data node to be printed.
 */
static void
xml_print_node_open(struct xmlpr_ctx *pctx, const struct lyd_node *node)
{
    /* print node name */
    ly_print_(pctx->out, "%*s<%s", INDENT, node->schema->name);

    /* print default namespace */
    xml_print_ns(pctx, node->schema->module->ns, NULL, 0);

    /* print metadata */
    xml_print_meta(pctx, node);
}

/**
 * @brief Print attributes of an opaque node.
 *
 * @param[in] pctx XML printer context.
 * @param[in] attr First attribute in a list to print.
 * @return LY_ERR value.
 */
static LY_ERR
xml_print_attr(struct xmlpr_ctx *pctx, const struct lyd_attr *attr)
{
    const char *pref;

    LY_LIST_FOR(attr, attr) {
        pref = NULL;
        if (attr->name.prefix) {
            /* print attribute namespace */
            pref = xml_print_ns_opaq(pctx, attr->format, &attr->name, 0);
        }

        /* print namespaces connected with the value's prefixes */
        if (attr->val_prefix_data) {
            xml_print_ns_prefix_data(pctx, attr->format, attr->val_prefix_data, LYXML_PREFIX_REQUIRED);
        }

        /* print the attribute with its prefix and value */
        ly_print_(pctx->out, " %s%s%s=\"", pref ? pref : "", pref ? ":" : "", attr->name.name);
        lyxml_dump_text(pctx->out, attr->value, 1);
        ly_print_(pctx->out, "\""); /* print attribute value terminator */
    }

    return LY_SUCCESS;
}

static LY_ERR
xml_print_opaq_open(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
{
    /* print node name */
    ly_print_(pctx->out, "%*s<%s", INDENT, node->name.name);

    if (node->name.prefix || node->name.module_ns) {
        /* print default namespace */
        xml_print_ns_opaq(pctx, node->format, &node->name, LYXML_PREFIX_DEFAULT);
    }

    /* print attributes */
    LY_CHECK_RET(xml_print_attr(pctx, node->attr));

    return LY_SUCCESS;
}

/**
 * @brief Print XML element representing lyd_node_term.
 *
 * @param[in] ctx XML printer context.
 * @param[in] node Data node to be printed.
 * @return LY_ERR value.
 */
static LY_ERR
xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node)
{
    LY_ERR rc = LY_SUCCESS;
    struct ly_set ns_list = {0};
    ly_bool dynamic = 0;
    const char *value = NULL;
    const struct lys_module *mod;
    uint32_t i;

    /* store the module of the default namespace */
    if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) {
        LOGMEM(pctx->ctx);
        goto cleanup;
    }

    /* print the value */
    value = LYSC_GET_TYPE_PLG(((struct lysc_node_leaf *)node->schema)->type->plugin_ref)->print(LYD_CTX(node),
            &node->value, LY_VALUE_XML, &ns_list, &dynamic, NULL);
    LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup);

    /* print node opening */
    xml_print_node_open(pctx, &node->node);

    /* print namespaces connected with the values's prefixes */
    for (i = 1; i < ns_list.count; ++i) {
        mod = ns_list.objs[i];
        ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns);
    }

    if (!value[0]) {
        ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
    } else {
        ly_print_(pctx->out, ">");
        lyxml_dump_text(pctx->out, value, 0);
        ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
    }

cleanup:
    ly_set_erase(&ns_list, NULL);
    if (dynamic) {
        free((void *)value);
    }
    return rc;
}

/**
 * @brief Print XML element representing lyd_node_inner.
 *
 * @param[in] ctx XML printer context.
 * @param[in] node Data node to be printed.
 * @return LY_ERR value.
 */
static LY_ERR
xml_print_inner(struct xmlpr_ctx *pctx, const struct lyd_node_inner *node)
{
    LY_ERR ret;
    struct lyd_node *child;

    xml_print_node_open(pctx, &node->node);

    LY_LIST_FOR(node->child, child) {
        if (lyd_node_should_print(child, pctx->options)) {
            break;
        }
    }
    if (!child) {
        /* there are no children that will be printed */
        ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
        return LY_SUCCESS;
    }

    /* children */
    ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");

    LEVEL_INC;
    LY_LIST_FOR(node->child, child) {
        ret = xml_print_node(pctx, child);
        LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
    }
    LEVEL_DEC;

    ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");

    return LY_SUCCESS;
}

static LY_ERR
xml_print_anydata(struct xmlpr_ctx *pctx, const struct lyd_node_any *node)
{
    struct lyd_node_any *any = (struct lyd_node_any *)node;
    struct lyd_node *iter;
    uint32_t prev_opts;
    LY_ERR ret;

    if ((node->schema->nodetype == LYS_ANYDATA) && node->value) {
        LOGINT_RET(pctx->ctx);
    }

    xml_print_node_open(pctx, &node->node);

    if (any->child) {
        /* close opening tag and print data */
        prev_opts = pctx->options;
        pctx->options &= ~LYD_PRINT_SIBLINGS;
        LEVEL_INC;

        ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
        LY_LIST_FOR(any->child, iter) {
            ret = xml_print_node(pctx, iter);
            LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
        }

        LEVEL_DEC;
        pctx->options = prev_opts;

        /* closing tag */
        ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");
    } else if (any->value && any->value[0]) {
        /* close opening tag and print data, escape XML-sensitive characters */
        ly_print_(pctx->out, ">");
        lyxml_dump_text(pctx->out, any->value, 0);

        /* closing tag */
        ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : "");
    } else {
        /* no content, close */
        ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
    }

    return LY_SUCCESS;
}

static LY_ERR
xml_print_opaq(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node)
{
    LY_ERR ret;
    struct lyd_node *child;

    LY_CHECK_RET(xml_print_opaq_open(pctx, node));

    if (node->value[0]) {
        /* print namespaces connected with the value's prefixes */
        if (node->val_prefix_data) {
            xml_print_ns_prefix_data(pctx, node->format, node->val_prefix_data, LYXML_PREFIX_REQUIRED);
        }

        ly_print_(pctx->out, ">");
        lyxml_dump_text(pctx->out, node->value, 0);
    }

    if (node->child) {
        /* children */
        if (!node->value[0]) {
            ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : "");
        }

        LEVEL_INC;
        LY_LIST_FOR(node->child, child) {
            ret = xml_print_node(pctx, child);
            LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret);
        }
        LEVEL_DEC;

        ly_print_(pctx->out, "%*s</%s>%s", INDENT, node->name.name, DO_FORMAT ? "\n" : "");
    } else if (node->value[0]) {
        ly_print_(pctx->out, "</%s>%s", node->name.name, DO_FORMAT ? "\n" : "");
    } else {
        /* no value or children */
        ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : "");
    }

    return LY_SUCCESS;
}

/**
 * @brief Print XML element representing lyd_node.
 *
 * @param[in] ctx XML printer context.
 * @param[in] node Data node to be printed.
 * @return LY_ERR value.
 */
static LY_ERR
xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node)
{
    LY_ERR ret = LY_SUCCESS;
    uint32_t ns_count;

    if (!lyd_node_should_print(node, pctx->options)) {
        /* do not print at all */
        return LY_SUCCESS;
    }

    /* remember namespace definition count on this level */
    ns_count = pctx->ns.count;

    if (!node->schema) {
        ret = xml_print_opaq(pctx, (const struct lyd_node_opaq *)node);
    } else {
        switch (node->schema->nodetype) {
        case LYS_CONTAINER:
        case LYS_LIST:
        case LYS_NOTIF:
        case LYS_RPC:
        case LYS_ACTION:
            ret = xml_print_inner(pctx, (const struct lyd_node_inner *)node);
            break;
        case LYS_LEAF:
        case LYS_LEAFLIST:
            ret = xml_print_term(pctx, (const struct lyd_node_term *)node);
            break;
        case LYS_ANYXML:
        case LYS_ANYDATA:
            ret = xml_print_anydata(pctx, (const struct lyd_node_any *)node);
            break;
        default:
            LOGINT(pctx->ctx);
            ret = LY_EINT;
            break;
        }
    }

    /* remove all added namespaces */
    while (ns_count < pctx->ns.count) {
        lydict_remove(pctx->ctx, pctx->prefix.objs[pctx->prefix.count - 1]);
        ly_set_rm_index(&pctx->prefix, pctx->prefix.count - 1, NULL);
        ly_set_rm_index(&pctx->ns, pctx->ns.count - 1, NULL);
    }

    return ret;
}

LY_ERR
xml_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options)
{
    const struct lyd_node *node;
    struct xmlpr_ctx pctx = {0};

    if (!root) {
        if ((out->type == LY_OUT_MEMORY) || (out->type == LY_OUT_CALLBACK)) {
            ly_print_(out, "");
        }
        goto finish;
    }

    pctx.out = out;
    pctx.level = 0;
    pctx.options = options;
    pctx.ctx = LYD_CTX(root);

    /* content */
    LY_LIST_FOR(root, node) {
        LY_CHECK_RET(xml_print_node(&pctx, node));
        if (!(options & LYD_PRINT_SIBLINGS)) {
            break;
        }
    }

finish:
    assert(!pctx.prefix.count && !pctx.ns.count);
    ly_set_erase(&pctx.prefix, NULL);
    ly_set_erase(&pctx.ns, NULL);
    ly_print_flush(out);
    return LY_SUCCESS;
}
