/**
 * @file printer_xml.c
 * @author Michal Vasko <mvasko@cesnet.cz>
 * @brief XML printer for libyang data structure
 *
 * Copyright (c) 2015 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
 */

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <inttypes.h>

#include "common.h"
#include "parser.h"
#include "printer.h"
#include "xml_internal.h"
#include "tree_data.h"
#include "tree_schema.h"
#include "resolve.h"
#include "tree_internal.h"

#define INDENT ""
#define LEVEL (level ? level*2-2 : 0)

struct mlist {
    struct mlist *next;
    struct lys_module *module;
} *mlist = NULL, *mlist_new;

static int
modlist_add(struct mlist **mlist, const struct lys_module *mod)
{
    struct mlist *iter;

    for (iter = *mlist; iter; iter = iter->next) {
        if (mod == iter->module) {
            break;
        }
    }

    if (!iter) {
        iter = malloc(sizeof *iter);
        LY_CHECK_ERR_RETURN(!iter, LOGMEM, EXIT_FAILURE);
        iter->next = *mlist;
        iter->module = (struct lys_module *)mod;
        *mlist = iter;
    }

    return EXIT_SUCCESS;
}

static void
xml_print_ns(struct lyout *out, const struct lyd_node *node, int options)
{
    struct lyd_node *next, *cur, *node2;
    struct lyd_attr *attr;
    const struct lys_module *wdmod = NULL;
    struct mlist *mlist = NULL, *miter;
    int r;

    assert(out);
    assert(node);

    /* add node attribute modules */
    for (attr = node->attr; attr; attr = attr->next) {
        if (!strcmp(node->schema->name, "filter") &&
                (!strcmp(node->schema->module->name, "ietf-netconf") ||
                 !strcmp(node->schema->module->name, "notifications"))) {
            /* exception for NETCONF's filter attributes */
            continue;
        } else {
            r = modlist_add(&mlist, lys_main_module(attr->annotation->module));
        }
        if (r) {
            goto print;
        }
    }

    /* add node children nodes and attribute modules */
    if (!(node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST | LYS_ANYDATA))) {
        if (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG)) {
            /* get with-defaults module and print its namespace */
            wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
            if (wdmod && modlist_add(&mlist, wdmod)) {
                goto print;
            }
        }

        LY_TREE_FOR(node->child, node2) {
            LY_TREE_DFS_BEGIN(node2, next, cur) {
                for (attr = cur->attr; attr; attr = attr->next) {
                    if (!strcmp(cur->schema->name, "filter") &&
                            (!strcmp(cur->schema->module->name, "ietf-netconf") ||
                             !strcmp(cur->schema->module->name, "notifications"))) {
                        /* exception for NETCONF's filter attributes */
                        continue;
                    } else {
                        r = modlist_add(&mlist, lys_main_module(attr->annotation->module));
                    }
                    if (r) {
                        goto print;
                    }
                }
            LY_TREE_DFS_END(node2, next, cur)}
        }
    }

print:
    /* print used namespaces */
    while (mlist) {
        miter = mlist;
        mlist = mlist->next;

        ly_print(out, " xmlns:%s=\"%s\"", miter->module->prefix, miter->module->ns);
        free(miter);
    }
}

static int
xml_print_attrs(struct lyout *out, const struct lyd_node *node, int options)
{
    struct lyd_attr *attr;
    const char **prefs, **nss;
    const char *xml_expr = NULL, *mod_name;
    uint32_t ns_count, i;
    int rpc_filter = 0;
    const struct lys_module *wdmod = NULL;
    char *p;
    size_t len;

    /* with-defaults */
    if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) {
        if ((node->dflt && (options & (LYP_WD_ALL_TAG | LYP_WD_IMPL_TAG))) ||
                (!node->dflt && (options & LYP_WD_ALL_TAG) && lyd_wd_default((struct lyd_node_leaf_list *)node))) {
            /* we have implicit OR explicit default node */
            /* get with-defaults module */
            wdmod = ly_ctx_get_module(node->schema->module->ctx, "ietf-netconf-with-defaults", NULL, 1);
            if (wdmod) {
                /* print attribute only if context include with-defaults schema */
                ly_print(out, " %s:default=\"true\"", wdmod->prefix);
            }
        }
    }
    /* technically, check for the extension get-filter-element-attributes from ietf-netconf */
    if (!strcmp(node->schema->name, "filter")
            && (!strcmp(node->schema->module->name, "ietf-netconf") || !strcmp(node->schema->module->name, "notifications"))) {
        rpc_filter = 1;
    }

    for (attr = node->attr; attr; attr = attr->next) {
        if (rpc_filter) {
            /* exception for NETCONF's filter's attributes */
            if (!strcmp(attr->name, "select")) {
                /* xpath content, we have to convert the JSON format into XML first */
                xml_expr = transform_json2xml(node->schema->module, attr->value_str, 0, &prefs, &nss, &ns_count);
                if (!xml_expr) {
                    /* error */
                    ly_print(out, "\"(!error!)\"");
                    return EXIT_FAILURE;
                }

                for (i = 0; i < ns_count; ++i) {
                    ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
                }
                free(prefs);
                free(nss);
            }
            ly_print(out, " %s=\"", attr->name);
        } else {
            ly_print(out, " %s:%s=\"", attr->annotation->module->prefix, attr->name);
        }

        switch (attr->value_type & LY_DATA_TYPE_MASK) {
        case LY_TYPE_BINARY:
        case LY_TYPE_STRING:
        case LY_TYPE_BITS:
        case LY_TYPE_ENUM:
        case LY_TYPE_BOOL:
        case LY_TYPE_DEC64:
        case LY_TYPE_INT8:
        case LY_TYPE_INT16:
        case LY_TYPE_INT32:
        case LY_TYPE_INT64:
        case LY_TYPE_UINT8:
        case LY_TYPE_UINT16:
        case LY_TYPE_UINT32:
        case LY_TYPE_UINT64:
            if (attr->value_str) {
                /* xml_expr can contain transformed xpath */
                lyxml_dump_text(out, xml_expr ? xml_expr : attr->value_str);
            }
            break;

        case LY_TYPE_IDENT:
            if (!attr->value_str) {
                break;
            }
            p = strchr(attr->value_str, ':');
            assert(p);
            len = p - attr->value_str;
            mod_name = attr->annotation->module->name;
            if (!strncmp(attr->value_str, mod_name, len) && !mod_name[len]) {
                lyxml_dump_text(out, ++p);
            } else {
                /* avoid code duplication - use instance-identifier printer which gets necessary namespaces to print */
                goto printinst;
            }
            break;
        case LY_TYPE_INST:
printinst:
            xml_expr = transform_json2xml(node->schema->module, ((struct lyd_node_leaf_list *)node)->value_str, 1,
                                          &prefs, &nss, &ns_count);
            if (!xml_expr) {
                /* error */
                ly_print(out, "(!error!)");
                return EXIT_FAILURE;
            }

            for (i = 0; i < ns_count; ++i) {
                ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
            }
            free(prefs);
            free(nss);

            lyxml_dump_text(out, xml_expr);
            lydict_remove(node->schema->module->ctx, xml_expr);
            break;

        /* LY_TYPE_LEAFREF not allowed */
        case LY_TYPE_EMPTY:
            break;

        default:
            /* error */
            ly_print(out, "(!error!)");
            return EXIT_FAILURE;
        }

        ly_print(out, "\"");

        if (xml_expr) {
            lydict_remove(node->schema->module->ctx, xml_expr);
        }
    }

    return EXIT_SUCCESS;
}

static int
xml_print_leaf(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
{
    const struct lyd_node_leaf_list *leaf = (struct lyd_node_leaf_list *)node, *iter;
    const struct lys_type *type;
    struct lys_tpdf *tpdf;
    const char *ns, *mod_name;
    const char **prefs, **nss;
    const char *xml_expr;
    uint32_t ns_count, i;
    LY_DATA_TYPE datatype;
    char *p;
    size_t len;

    if (toplevel || !node->parent || nscmp(node, node->parent)) {
        /* print "namespace" */
        ns = lyd_node_module(node)->ns;
        ly_print(out, "%*s<%s xmlns=\"%s\"", LEVEL, INDENT, node->schema->name, ns);
    } else {
        ly_print(out, "%*s<%s", LEVEL, INDENT, node->schema->name);
    }

    if (toplevel) {
        xml_print_ns(out, node, options);
    }

    if (xml_print_attrs(out, node, options)) {
        return EXIT_FAILURE;
    }
    datatype = leaf->value_type & LY_DATA_TYPE_MASK;
printvalue:
    switch (datatype) {
    case LY_TYPE_STRING:
        type = lyd_leaf_type((struct lyd_node_leaf_list *)leaf);
        if (!type) {
            /* error */
            ly_print(out, "\"(!error!)\"");
            return EXIT_FAILURE;
        }
        for (tpdf = type->der;
             tpdf->module && (strcmp(tpdf->name, "xpath1.0") || strcmp(tpdf->module->name, "ietf-yang-types"));
             tpdf = tpdf->type.der);
        /* special handling of ietf-yang-types xpath1.0 */
        if (tpdf->module) {
            /* avoid code duplication - use instance-identifier printer which gets necessary namespaces to print */
            datatype = LY_TYPE_INST;
            goto printvalue;
        }
        /* fallthrough */
    case LY_TYPE_BINARY:
    case LY_TYPE_BITS:
    case LY_TYPE_ENUM:
    case LY_TYPE_BOOL:
    case LY_TYPE_DEC64:
    case LY_TYPE_INT8:
    case LY_TYPE_INT16:
    case LY_TYPE_INT32:
    case LY_TYPE_INT64:
    case LY_TYPE_UINT8:
    case LY_TYPE_UINT16:
    case LY_TYPE_UINT32:
    case LY_TYPE_UINT64:
        if (!leaf->value_str || !leaf->value_str[0]) {
            ly_print(out, "/>");
        } else {
            ly_print(out, ">");
            lyxml_dump_text(out, leaf->value_str);
            ly_print(out, "</%s>", node->schema->name);
        }
        break;

    case LY_TYPE_IDENT:
        if (!leaf->value_str || !leaf->value_str[0]) {
            ly_print(out, "/>");
            break;
        }
        p = strchr(leaf->value_str, ':');
        assert(p);
        len = p - leaf->value_str;
        mod_name = leaf->schema->module->name;
        if (!strncmp(leaf->value_str, mod_name, len) && !mod_name[len]) {
            ly_print(out, ">");
            lyxml_dump_text(out, ++p);
            ly_print(out, "</%s>", node->schema->name);
        } else {
            /* avoid code duplication - use instance-identifier printer which gets necessary namespaces to print */
            datatype = LY_TYPE_INST;
            goto printvalue;
        }
        break;
    case LY_TYPE_INST:
        xml_expr = transform_json2xml(node->schema->module, ((struct lyd_node_leaf_list *)node)->value_str, 1,
                                      &prefs, &nss, &ns_count);
        if (!xml_expr) {
            /* error */
            ly_print(out, "\"(!error!)\"");
            return EXIT_FAILURE;
        }

        for (i = 0; i < ns_count; ++i) {
            ly_print(out, " xmlns:%s=\"%s\"", prefs[i], nss[i]);
        }
        free(prefs);
        free(nss);

        if (xml_expr[0]) {
            ly_print(out, ">");
            lyxml_dump_text(out, xml_expr);
            ly_print(out, "</%s>", node->schema->name);
        } else {
            ly_print(out, "/>");
        }
        lydict_remove(node->schema->module->ctx, xml_expr);
        break;

    case LY_TYPE_LEAFREF:
        iter = (struct lyd_node_leaf_list *)leaf->value.leafref;
        while (iter && (iter->value_type == LY_TYPE_LEAFREF)) {
            iter = (struct lyd_node_leaf_list *)iter->value.leafref;
        }
        if (!iter) {
            /* unresolved and invalid, but we can learn the correct type anyway */
            type = lyd_leaf_type((struct lyd_node_leaf_list *)leaf);
            if (!type) {
                /* error */
                ly_print(out, "\"(!error!)\"");
                return EXIT_FAILURE;
            }
            datatype = type->base;
        } else {
            datatype = iter->value_type & LY_DATA_TYPE_MASK;
        }
        goto printvalue;

    case LY_TYPE_EMPTY:
        ly_print(out, "/>");
        break;

    default:
        /* error */
        ly_print(out, "\"(!error!)\"");
        return EXIT_FAILURE;
    }

    if (level) {
        ly_print(out, "\n");
    }

    return EXIT_SUCCESS;
}

static int
xml_print_container(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
{
    struct lyd_node *child;
    const char *ns;

    if (toplevel || !node->parent || nscmp(node, node->parent)) {
        /* print "namespace" */
        ns = lyd_node_module(node)->ns;
        ly_print(out, "%*s<%s xmlns=\"%s\"", LEVEL, INDENT, node->schema->name, ns);
    } else {
        ly_print(out, "%*s<%s", LEVEL, INDENT, node->schema->name);
    }

    if (toplevel) {
        xml_print_ns(out, node, options);
    }

    if (xml_print_attrs(out, node, options)) {
        return EXIT_FAILURE;
    }

    if (!node->child) {
        ly_print(out, "/>%s", level ? "\n" : "");
        return EXIT_SUCCESS;
    }
    ly_print(out, ">%s", level ? "\n" : "");

    LY_TREE_FOR(node->child, child) {
        if (xml_print_node(out, level ? level + 1 : 0, child, 0, options)) {
            return EXIT_FAILURE;
        }
    }

    ly_print(out, "%*s</%s>%s", LEVEL, INDENT, node->schema->name, level ? "\n" : "");

    return EXIT_SUCCESS;
}

static int
xml_print_list(struct lyout *out, int level, const struct lyd_node *node, int is_list, int toplevel, int options)
{
    struct lyd_node *child;
    const char *ns;

    if (is_list) {
        /* list print */
        if (toplevel || !node->parent || nscmp(node, node->parent)) {
            /* print "namespace" */
            ns = lyd_node_module(node)->ns;
            ly_print(out, "%*s<%s xmlns=\"%s\"", LEVEL, INDENT, node->schema->name, ns);
        } else {
            ly_print(out, "%*s<%s", LEVEL, INDENT, node->schema->name);
        }

        if (toplevel) {
            xml_print_ns(out, node, options);
        }
        if (xml_print_attrs(out, node, options)) {
            return EXIT_FAILURE;
        }

        if (!node->child) {
            ly_print(out, "/>%s", level ? "\n" : "");
            return EXIT_SUCCESS;
        }
        ly_print(out, ">%s", level ? "\n" : "");

        LY_TREE_FOR(node->child, child) {
            if (xml_print_node(out, level ? level + 1 : 0, child, 0, options)) {
                return EXIT_FAILURE;
            }
        }

        ly_print(out, "%*s</%s>%s", LEVEL, INDENT, node->schema->name, level ? "\n" : "");
    } else {
        /* leaf-list print */
        xml_print_leaf(out, level, node, toplevel, options);
    }

    return EXIT_SUCCESS;
}

static int
xml_print_anydata(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
{
    char *buf;
    struct lyd_node_anydata *any = (struct lyd_node_anydata *)node;
    struct lyd_node *iter;
    const char *ns;

    if (toplevel || !node->parent || nscmp(node, node->parent)) {
        /* print "namespace" */
        ns = lyd_node_module(node)->ns;
        ly_print(out, "%*s<%s xmlns=\"%s\"", LEVEL, INDENT, node->schema->name, ns);
    } else {
        ly_print(out, "%*s<%s", LEVEL, INDENT, node->schema->name);
    }

    if (toplevel) {
        xml_print_ns(out, node, options);
    }
    if (xml_print_attrs(out, node, options)) {
        return EXIT_FAILURE;
    }
    if (!(void*)any->value.tree || (any->value_type == LYD_ANYDATA_CONSTSTRING && !any->value.str[0])) {
        /* no content */
        ly_print(out, "/>%s", level ? "\n" : "");
    } else {
        /* close opening tag ... */
        ly_print(out, ">");
        /* ... and print anydata content */
        switch (any->value_type) {
        case LYD_ANYDATA_CONSTSTRING:
            lyxml_dump_text(out, any->value.str);
            break;
        case LYD_ANYDATA_DATATREE:
            if (any->value.tree) {
                if (level) {
                    ly_print(out, "\n");
                }
                LY_TREE_FOR(any->value.tree, iter) {
                    if (xml_print_node(out, level ? level + 1 : 0, iter, 0, (options & ~(LYP_WITHSIBLINGS | LYP_NETCONF)))) {
                        return EXIT_FAILURE;
                    }
                }
            }
            break;
        case LYD_ANYDATA_XML:
            lyxml_print_mem(&buf, any->value.xml, (level ? LYXML_PRINT_FORMAT | LYXML_PRINT_NO_LAST_NEWLINE : 0)
                                                   | LYXML_PRINT_SIBLINGS);
            ly_print(out, "%s%s", level ? "\n" : "", buf);
            free(buf);
            break;
        case LYD_ANYDATA_SXML:
            /* print without escaping special characters */
            ly_print(out, "%s", any->value.str);
            break;
        case LYD_ANYDATA_JSON:
            /* JSON format is not supported */
            LOGWRN("Unable to print anydata content (type %d) as XML.", any->value_type);
            break;
        case LYD_ANYDATA_STRING:
        case LYD_ANYDATA_SXMLD:
        case LYD_ANYDATA_JSOND:
            /* dynamic strings are used only as input parameters */
            assert(0);
            break;
        }

        /* closing tag */
        ly_print(out, "</%s>%s", node->schema->name, level ? "\n" : "");
    }

    return EXIT_SUCCESS;
}

int
xml_print_node(struct lyout *out, int level, const struct lyd_node *node, int toplevel, int options)
{
    int ret = EXIT_SUCCESS;

    if (!lyd_wd_toprint(node, options)) {
        /* wd says do not print */
        return EXIT_SUCCESS;
    }

    switch (node->schema->nodetype) {
    case LYS_NOTIF:
    case LYS_RPC:
    case LYS_ACTION:
    case LYS_CONTAINER:
        ret = xml_print_container(out, level, node, toplevel, options);
        break;
    case LYS_LEAF:
        ret = xml_print_leaf(out, level, node, toplevel, options);
        break;
    case LYS_LEAFLIST:
        ret = xml_print_list(out, level, node, 0, toplevel, options);
        break;
    case LYS_LIST:
        ret = xml_print_list(out, level, node, 1, toplevel, options);
        break;
    case LYS_ANYXML:
    case LYS_ANYDATA:
        ret = xml_print_anydata(out, level, node, toplevel, options);
        break;
    default:
        LOGINT;
        ret = EXIT_FAILURE;
        break;
    }

    return ret;
}

int
xml_print_data(struct lyout *out, const struct lyd_node *root, int options)
{
    const struct lyd_node *node, *next;
    struct lys_node *parent = NULL;
    int level, action_input = 0;

    assert(root);

    level = (options & LYP_FORMAT ? 1 : 0);

    if (options & LYP_NETCONF) {
        if (root->schema->nodetype != LYS_RPC) {
            /* learn whether we are printing an action */
            LY_TREE_DFS_BEGIN(root, next, node) {
                if (node->schema->nodetype == LYS_ACTION) {
                    break;
                }
                LY_TREE_DFS_END(root, next, node);
            }
        } else {
            node = root;
        }

        if (node) {
            if (node->child) {
                for (parent = lys_parent(node->child->schema); parent && (parent->nodetype == LYS_USES); parent = lys_parent(parent));
            }
            if (parent && (parent->nodetype == LYS_OUTPUT)) {
                /* rpc/action output - skip the container */
                root = node->child;
            } else if (node->schema->nodetype == LYS_ACTION) {
                /* action input - print top-level action element */
                action_input = 1;
            }
        }
    }

    if (action_input) {
        ly_print(out, "%*s<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">%s", LEVEL, INDENT, level ? "\n" : "");
        if (level) {
            ++level;
        }
    }

    /* content */
    LY_TREE_FOR(root, node) {
        if (xml_print_node(out, level, node, 1, options)) {
            return EXIT_FAILURE;
        }
        if (!(options & LYP_WITHSIBLINGS)) {
            break;
        }
    }

    if (action_input) {
        if (level) {
            --level;
        }
        ly_print(out, "%*s</action>%s", LEVEL, INDENT, level ? "\n" : "");
    }

    ly_print_flush(out);

    return EXIT_SUCCESS;
}

