/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2018-2020, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
 */

/**
 * Special fast event functions
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "common/thread/thread.h"
#include "common/avl/avl.h"

#include "reportxml.h"
#include "refobject.h"

/* For XMLSTR() */
#include "cfgfile.h"

#include "logging.h"
#define CATMODULE "reportxml"

/* The report XML document type */
struct reportxml_tag {
    /* base object */
    refobject_base_t __base;
    /* the root report XML node of the document */
    reportxml_node_t *root;
};

/* The report XML node type */
struct reportxml_node_tag {
    /* base object */
    refobject_base_t __base;
    /* an XML node used to store the attributes */
    xmlNodePtr xmlnode;
    /* the type of the node */
    reportxml_node_type_t type;
    /* the report XML childs */
    reportxml_node_t **childs;
    size_t childs_len;
    /* the XML childs (used by <extension>) */
    xmlNodePtr *xml_childs;
    size_t xml_childs_len;
    /* the node's text content (used by <text>) */
    char *content;
};

/* The report XML database type */
struct reportxml_database_tag {
    /* base object */
    refobject_base_t __base;
    /* the lock used to ensure the database object is thread safe. */
    mutex_t lock;
    /* The tree of definitions */
    avl_tree *definitions;
};

/* The nodeattr structure is used to store definition of node attributes */
struct nodeattr;
struct nodeattr {
    /* name of the attribute */
    const char *name;
    /* the type of the attribute. This is based on the DTD */
    const char *type;
    /* the default value for the attribute if any */
    const char *def;
    /* whether the attribute is required or not */
    int required;
    /* a function that can be used to check the content of the attribute if any */
    int (*checker)(const struct nodeattr *attr, const char *str);
    /* NULL terminated list of possible values (if enum) */
    const char *values[32];
};

/* The type of the content an node has */
enum nodecontent {
    /* The node may not have any content */
    NC_NONE,
    /* The node may have children */
    NC_CHILDS,
    /* The node may have a text content */
    NC_CONTENT,
    /* The node may have XML children */
    NC_XML
};

/* This structure is used to define a node */
struct nodedef {
    /* the type of the node */
    reportxml_node_type_t type;
    /* the name of the corresponding XML node */
    const char *name;
    /* The type of the content the node may have */
    enum nodecontent content;
    /* __attr__eol terminated list of attributes the node may have */
    const struct nodeattr *attr[12];
    /* REPORTXML_NODE_TYPE__ERROR terminated list of child node types the node may have */
    const reportxml_node_type_t childs[12];
};

/* Prototypes */
static int __attach_copy_of_node_or_definition(reportxml_node_t *parent, reportxml_node_t *node, reportxml_database_t *db, ssize_t depth);
static reportxml_node_t *      __reportxml_database_build_node_ext(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t *acst_type_ret);
static xmlNodePtr              reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace);

/* definition of known attributes */
static const struct nodeattr __attr__eol[1]             = {{NULL,           NULL,           NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_version[1]          = {{"version",      "CDATA",        "0.0.1",  1,  NULL, {"0.0.1", NULL}}};
static const struct nodeattr __attr_xmlns[1]            = {{"xmlns",        "URI",          "http://icecast.org/specs/reportxml-0.0.1",    1,  NULL, {"http://icecast.org/specs/reportxml-0.0.1", NULL}}};
static const struct nodeattr __attr_id[1]               = {{"id",           "ID",           NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_definition[1]       = {{"definition",   "UUID",         NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr__definition[1]      = {{"_definition",  "UUID",         NULL,     0,  NULL, {NULL}}}; /* for internal use only */
static const struct nodeattr __attr_akindof[1]          = {{"akindof",      "UUIDs",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_lang[1]             = {{"lang",         "LanguageCode", NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_dir[1]              = {{"dir",          NULL,           NULL,     0,  NULL, {"ltr", "rtl", NULL}}};
static const struct nodeattr __attr_template[1]         = {{"template",     "UUID",         NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_defines[1]          = {{"defines",      "UUID",         NULL,     1,  NULL, {NULL}}};
static const struct nodeattr __attr_function[1]         = {{"function",     "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_filename[1]         = {{"filename",     "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_line[1]             = {{"line",         "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_binary[1]           = {{"binary",       "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_offset[1]           = {{"offset",       "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_absolute[1]         = {{"absolute",     "iso8601",      NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_relative[1]         = {{"relative",     "iso8601",      NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_name[1]             = {{"name",         "CDATA",        NULL,     1,  NULL, {NULL}}};
static const struct nodeattr __attr_member[1]           = {{"member",       "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_value[1]            = {{"value",        "CDATA",        NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_state[1]            = {{"state",        NULL,           "set",    1,  NULL, {"declared", "set", "uninitialized", "missing", NULL}}};
static const struct nodeattr __attr_href[1]             = {{"href",         "URI",          NULL,     0,  NULL, {NULL}}};
static const struct nodeattr __attr_application[1]      = {{"application",  "URI",          NULL,     1,  NULL, {NULL}}};
static const struct nodeattr __attr__action_type[1]     = {{"type",         NULL,           NULL,     1,  NULL, {"retry", "choice", "see-other", "authenticate", "pay", "change-protocol", "slow-down", "ask-user", "ask-admin", "bug", NULL}}};
static const struct nodeattr __attr__resource_type[1]   = {{"type",         NULL,           NULL,     1,  NULL, {"actor", "manipulation-target", "helper", "related", "result", "parameter", NULL}}};
static const struct nodeattr __attr__value_type[1]      = {{"type",         NULL,           NULL,     1,  NULL, {"null", "int", "float", "uuid", "string", "structure", "uri", "pointer", "version", "protocol", "nodename", "username", "password", "boolean", "list", "unordered-list", "bit-field", "enum", "flag", "blob", "blob-base64", "timestamp", NULL}}};
static const struct nodeattr __attr__reference_type[1]  = {{"type",         NULL,           NULL,     1,  NULL, {"documentation", "log", "report", "related", NULL}}};

/* definition of known nodes */
/* Helper:
 * grep '^ *REPORTXML_NODE_TYPE_' reportxml.h | sed 's/^\( *REPORTXML_NODE_TYPE_\([^,]*\)\),*$/\1 \2/;' | while read l s; do c=$(tr A-Z a-z <<<"$s"); printf "    {%-32s \"%-16s 0, {__attr__eol}},\n" "$l," "$c\","; done
 */
#define __BASIC_ELEMENT __attr_id, __attr_definition, __attr_akindof, __attr__definition
static const struct nodedef __nodedef[] = {
    {REPORTXML_NODE_TYPE_REPORT,      "report",         NC_CHILDS,  {__attr_id, __attr_version, __attr_xmlns, __attr__eol},
        {REPORTXML_NODE_TYPE_INCIDENT, REPORTXML_NODE_TYPE_DEFINITION, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_DEFINITION,  "definition",     NC_CHILDS,  {__BASIC_ELEMENT, __attr_template, __attr_defines, __attr__eol},
        {REPORTXML_NODE_TYPE_INCIDENT, REPORTXML_NODE_TYPE_STATE, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_FIX, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REASON, REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_INCIDENT,    "incident",       NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_STATE, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_FIX, REPORTXML_NODE_TYPE_REASON, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_STATE,       "state",          NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_BACKTRACE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_BACKTRACE,   "backtrace",      NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_POSITION, REPORTXML_NODE_TYPE_MORE, REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_POSITION,    "position",       NC_CHILDS,  {__BASIC_ELEMENT, __attr_function, __attr_filename, __attr_line, __attr_binary, __attr_offset, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_MORE,        "more",           NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_FIX,         "fix",            NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_ACTION, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_ACTION,      "action",         NC_CHILDS,  {__BASIC_ELEMENT, __attr__action_type, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_TIMESTAMP, REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_REASON,      "reason",         NC_CHILDS,  {__BASIC_ELEMENT, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_RESOURCE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_TEXT,        "text",           NC_CONTENT, {__BASIC_ELEMENT, __attr_lang, __attr_dir, __attr__eol},
        {REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_TIMESTAMP,   "timestamp",      NC_NONE,    {__BASIC_ELEMENT, __attr_absolute, __attr_relative, __attr__eol},
        {REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_RESOURCE,    "resource",       NC_CHILDS,  {__BASIC_ELEMENT, __attr__resource_type, __attr_name, __attr__eol},
        {REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_VALUE,       "value",          NC_CHILDS,  {__BASIC_ELEMENT, __attr_member, __attr_value, __attr_state, __attr__value_type, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_REFERENCE, REPORTXML_NODE_TYPE_VALUE, REPORTXML_NODE_TYPE_POSITION, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_REFERENCE,   "reference",      NC_CHILDS,  {__BASIC_ELEMENT, __attr__reference_type, __attr_href, __attr__eol},
        {REPORTXML_NODE_TYPE_TEXT, REPORTXML_NODE_TYPE_EXTENSION, REPORTXML_NODE_TYPE__ERROR}},
    {REPORTXML_NODE_TYPE_EXTENSION,   "extension",      NC_XML,     {__BASIC_ELEMENT, __attr_application, __attr__eol},
        {REPORTXML_NODE_TYPE__ERROR}}
};

static const struct nodedef * __get_nodedef(reportxml_node_type_t type)
{
    size_t i;

    for (i = 0; i < (sizeof(__nodedef)/sizeof(*__nodedef)); i++) {
        if (__nodedef[i].type == type) {
            return &(__nodedef[i]);
        }
    }

    return NULL;
}

static const struct nodedef * __get_nodedef_by_name(const char *name)
{
    size_t i;

    for (i = 0; i < (sizeof(__nodedef)/sizeof(*__nodedef)); i++) {
        if (strcmp(__nodedef[i].name, name) == 0) {
            return &(__nodedef[i]);
        }
    }

    return NULL;
}

static const struct nodeattr * __get_nodeattr(const struct nodedef * nodedef, const char *key)
{
    size_t i;

    if (!nodedef || !key)
        return NULL;

    for (i = 0; i < (sizeof(nodedef->attr)/sizeof(*nodedef->attr)); i++) {
        if (nodedef->attr[i]->name == NULL) {
            /* we reached the end of the list */
            return NULL;
        }

        if (strcmp(nodedef->attr[i]->name, key) == 0) {
            return nodedef->attr[i];
        }
    }

    return NULL;
}


static void __report_free(refobject_t self, void **userdata)
{
    reportxml_t *report = REFOBJECT_TO_TYPE(self, reportxml_t *);

    refobject_unref(report->root);
}

static int __report_new(refobject_t self, const refobject_type_t *type, va_list ap)
{
    reportxml_t *ret = REFOBJECT_TO_TYPE(self, reportxml_t*);
    reportxml_node_t *root = reportxml_node_new(REPORTXML_NODE_TYPE_REPORT, NULL, NULL, NULL);

    if (!root)
        return -1;

    ret->root = root;

    return 0;
}

REFOBJECT_DEFINE_TYPE(reportxml_t,
        REFOBJECT_DEFINE_TYPE_FREE(__report_free),
        REFOBJECT_DEFINE_TYPE_NEW(__report_new)
        );

static reportxml_t *    reportxml_new_with_root(reportxml_node_t *root)
{
    reportxml_t *ret;

    if (!root)
        return NULL;

    ret = refobject_new__new(reportxml_t, NULL, NULL, NULL);
    ret->root = root;

    return ret;
}

reportxml_t *           reportxml_new(void)
{
    return refobject_new(reportxml_t);
}

reportxml_node_t *      reportxml_get_root_node(reportxml_t *report)
{
    if (!report)
        return NULL;

    if (refobject_ref(report->root) != 0)
        return NULL;

    return report->root;
}

reportxml_node_t *      reportxml_get_node_by_attribute(reportxml_t *report, const char *key, const char *value, int include_definitions)
{
    if (!report || !key || !value)
        return NULL;

    return reportxml_node_get_child_by_attribute(report->root, key, value, include_definitions);
}

reportxml_node_t *      reportxml_get_node_by_type(reportxml_t *report, reportxml_node_type_t type, int include_definitions)
{
    if (!report)
        return NULL;

    return reportxml_node_get_child_by_type(report->root, type, include_definitions);
}

reportxml_t *           reportxml_parse_xmldoc(xmlDocPtr doc)
{
    reportxml_node_t *root;
    reportxml_t *ret;
    xmlNodePtr xmlroot;

    if (!doc)
        return NULL;

    xmlroot = xmlDocGetRootElement(doc);
    if (!xmlroot)
        return NULL;

    root = reportxml_node_parse_xmlnode(xmlroot);
    if (!root)
        return NULL;

    if (reportxml_node_get_type(root) != REPORTXML_NODE_TYPE_REPORT) {
        refobject_unref(root);
        return NULL;
    }

    ret = reportxml_new_with_root(root);
    if (!ret) {
        refobject_unref(root);
        return NULL;
    }

    return ret;
}

xmlDocPtr               reportxml_render_xmldoc(reportxml_t *report, int set_namespace)
{
    xmlDocPtr ret;
    xmlNodePtr node;

    if (!report)
        return NULL;

    node = reportxml_node_render_xmlnode_with_ns(report->root, NULL, set_namespace);
    if (!node)
        return NULL;

    ret = xmlNewDoc(XMLSTR("1.0"));
    if (!ret) {
        xmlFreeNode(node);
        return NULL;
    }

    xmlDocSetRootElement(ret, node);

    return ret;
}

static void __report_node_free(refobject_t self, void **userdata)
{
    size_t i;

    reportxml_node_t *node = REFOBJECT_TO_TYPE(self, reportxml_node_t *);
    xmlFreeNode(node->xmlnode);

    for (i = 0; i < node->childs_len; i++) {
        refobject_unref(node->childs[i]);
    }

    for (i = 0; i < node->xml_childs_len; i++) {
        xmlFreeNode(node->xml_childs[i]);
    }

    free(node->content);
    free(node->childs);
    free(node->xml_childs);
}

REFOBJECT_DEFINE_TYPE(reportxml_node_t,
        REFOBJECT_DEFINE_TYPE_FREE(__report_node_free)
        );

reportxml_node_t *      reportxml_node_new(reportxml_node_type_t type, const char *id, const char *definition, const char *akindof)
{
    reportxml_node_t *ret;
    const struct nodedef *nodedef = __get_nodedef(type);
    size_t i;

    if (!nodedef)
        return NULL;

    ret = refobject_new__new(reportxml_node_t, NULL, NULL, NULL);

    if (ret == NULL)
        return NULL;

    ret->type = type;

    ret->xmlnode = xmlNewNode(NULL, XMLSTR(nodedef->name));

    if (!ret->xmlnode) {
        refobject_unref(ret);
        return NULL;
    }

    for (i = 0; i < (sizeof(nodedef->attr)/sizeof(*nodedef->attr)); i++) {
        const struct nodeattr *nodeattr = nodedef->attr[i];
        if (nodeattr->name == NULL)
            break;

        if (nodeattr->def) {
            if (reportxml_node_set_attribute(ret, nodeattr->name, nodeattr->def) != 0) {
                refobject_unref(ret);
                return NULL;
            }
        }
    }

#define _set_attr(x) \
    if ((x)) { \
        if (reportxml_node_set_attribute(ret, # x , (x)) != 0) { \
            refobject_unref(ret); \
            return NULL; \
        } \
    }

    _set_attr(id)
    _set_attr(definition)
    _set_attr(akindof)

    return ret;
}

reportxml_node_t *      reportxml_node_parse_xmlnode(xmlNodePtr xmlnode)
{
    reportxml_node_t *node;

    const struct nodedef *nodedef;

    if (!xmlnode)
        return NULL;

    nodedef = __get_nodedef_by_name((const char *)xmlnode->name);
    if (!nodedef) {
        ICECAST_LOG_ERROR("Can not parse XML node: Unknown name <%s>", xmlnode->name);
        return NULL;
    }

    node = reportxml_node_new(nodedef->type, NULL, NULL, NULL);
    if (!node)
        return NULL;

    if (xmlnode->properties) {
        xmlAttrPtr cur = xmlnode->properties;

        do {
            xmlChar *value = xmlNodeListGetString(xmlnode->doc, cur->children, 1);
            if (!value) {
                refobject_unref(node);
                return NULL;
            }

            if (reportxml_node_set_attribute(node, (const char*)cur->name, (const char*)value) != 0) {
                refobject_unref(node);
                ICECAST_LOG_DEBUG("Can not parse XML node: attribute \"%H\" value \"%H\" is invalid on node of type <%s>", cur->name, value, nodedef->name);
                return NULL;
            }

            xmlFree(value);
        } while ((cur = cur->next));
    }

    if (xmlnode->xmlChildrenNode) {
        xmlNodePtr cur = xmlnode->xmlChildrenNode;

        do {
            if (nodedef->content == NC_XML) {
                if (reportxml_node_add_xml_child(node, cur) != 0) {
                    refobject_unref(node);
                    return NULL;
                }
            } else {
                reportxml_node_t *child;

                if (xmlIsBlankNode(cur))
                    continue;

                if (cur->type == XML_COMMENT_NODE)
                    continue;

                if (cur->type == XML_TEXT_NODE) {
                    xmlChar *value = xmlNodeListGetString(xmlnode->doc, cur, 1);

                    if (!value) {
                        refobject_unref(node);
                        return NULL;
                    }

                    if (reportxml_node_set_content(node, (const char *)value) != 0) {
                        refobject_unref(node);
                        return NULL;
                    }

                    xmlFree(value);
                    continue;
                }

                child = reportxml_node_parse_xmlnode(cur);
                if (!child) {
                    refobject_unref(node);
                    return NULL;
                }

                if (reportxml_node_add_child(node, child) != 0) {
                    refobject_unref(child);
                    refobject_unref(node);
                    return NULL;
                }
                refobject_unref(child);
            }
        } while ((cur = cur->next));
    }

    return node;
}

static reportxml_node_t *      __reportxml_node_copy_with_db(reportxml_node_t *node, reportxml_database_t *db, ssize_t depth)
{
    reportxml_node_t *ret;
    ssize_t child_count;
    ssize_t xml_child_count;
    size_t i;

    if (!node)
        return NULL;

    child_count = reportxml_node_count_child(node);
    if (child_count < 0)
        return NULL;

    xml_child_count = reportxml_node_count_xml_child(node);
    if (xml_child_count < 0)
        return NULL;

    ret = reportxml_node_parse_xmlnode(node->xmlnode);
    if (!ret)
        return NULL;

    if (node->content) {
        if (reportxml_node_set_content(ret, node->content) != 0) {
            refobject_unref(ret);
            return NULL;
        }
    }

    for (i = 0; i < (size_t)child_count; i++) {
        reportxml_node_t *child = reportxml_node_get_child(node, i);

        if (db && depth > 0) {
            if (__attach_copy_of_node_or_definition(ret, child, db, depth) != 0) {
                refobject_unref(child);
                refobject_unref(ret);
                return NULL;
            }
            refobject_unref(child);
        } else {
            reportxml_node_t *copy = __reportxml_node_copy_with_db(child, NULL, -1);

            refobject_unref(child);

            if (!copy) {
                refobject_unref(ret);
                return NULL;
            }

            if (reportxml_node_add_child(ret, copy) != 0) {
                refobject_unref(copy);
                refobject_unref(ret);
                return NULL;
            }

            refobject_unref(copy);
        }
    }

    for (i = 0; i < (size_t)xml_child_count; i++) {
        xmlNodePtr child = reportxml_node_get_xml_child(node, i);
        if (reportxml_node_add_xml_child(ret, child) != 0) {
            xmlFreeNode(child);
            refobject_unref(ret);
            return NULL;
        }
        xmlFreeNode(child);
    }

    return ret;
}

reportxml_node_t *      reportxml_node_copy(reportxml_node_t *node)
{
    return __reportxml_node_copy_with_db(node, NULL, -1);
}

static xmlNodePtr              reportxml_node_render_xmlnode_with_ns(reportxml_node_t *node, xmlNsPtr ns, int set_namespace)
{
    xmlNodePtr ret;
    ssize_t child_count;
    ssize_t xml_child_count;
    size_t i;
    xmlChar *definition;
    xmlChar *xmlns;

    if (!node)
        return NULL;

    child_count = reportxml_node_count_child(node);
    if (child_count < 0)
        return NULL;

    xml_child_count = reportxml_node_count_xml_child(node);
    if (xml_child_count < 0)
        return NULL;

    ret = xmlCopyNode(node->xmlnode, 2);
    if (!ret)
        return NULL;

    definition = xmlGetProp(ret, XMLSTR("_definition"));
    if (definition) {
        xmlSetProp(ret, XMLSTR("definition"), definition);
        xmlUnsetProp(ret, XMLSTR("_definition"));
        xmlFree(definition);
    }

    xmlns = xmlGetProp(ret, XMLSTR("xmlns"));
    if (xmlns) {
        xmlUnsetProp(ret, XMLSTR("xmlns"));
        ns = xmlNewNs(ret, xmlns, NULL);
        xmlFree(xmlns);
    }

    if (ns && set_namespace)
        xmlSetNs(ret, ns);

    for (i = 0; i < (size_t)child_count; i++) {
        reportxml_node_t *child = reportxml_node_get_child(node, i);
        xmlNodePtr xmlchild;

        if (!child) {
            xmlFreeNode(ret);
            return NULL;
        }

        xmlchild = reportxml_node_render_xmlnode_with_ns(child, ns, set_namespace);
        refobject_unref(child);
        if (!xmlchild) {
            xmlFreeNode(ret);
            return NULL;
        }

        xmlAddChild(ret, xmlchild);
    }

    if (node->content) {
        xmlNodePtr xmlchild = xmlNewText(XMLSTR(node->content));

        if (!xmlchild) {
            xmlFreeNode(ret);
            return NULL;
        }

        xmlAddChild(ret, xmlchild);
    }

    for (i = 0; i < (size_t)xml_child_count; i++) {
        xmlNodePtr xmlchild = reportxml_node_get_xml_child(node, i);

        if (!xmlchild) {
            xmlFreeNode(ret);
            return NULL;
        }

        xmlAddChild(ret, xmlchild);
    }


    return ret;
}

xmlNodePtr              reportxml_node_render_xmlnode(reportxml_node_t *node)
{
    return reportxml_node_render_xmlnode_with_ns(node, NULL, 1);
}

reportxml_node_type_t   reportxml_node_get_type(reportxml_node_t *node)
{
    if (!node)
        return REPORTXML_NODE_TYPE__ERROR;

    return node->type;
}

int                     reportxml_node_set_attribute(reportxml_node_t *node, const char *key, const char *value)
{
    const struct nodedef *nodedef;
    const struct nodeattr *nodeattr;
    size_t i;

    if (!node || !key || !value)
        return -1;

    nodedef = __get_nodedef(node->type);
    nodeattr = __get_nodeattr(nodedef, key);
    if (!nodeattr)
        return -1;

    if (nodeattr->values[0] != NULL) {
        int found = 0;
        for (i = 0; i < (sizeof(nodeattr->values)/sizeof(*nodeattr->values)); i++) {
            if (nodeattr->values[i] == NULL)
                break;

            if (strcmp(nodeattr->values[i], value) == 0) {
                found = 1;
                break;
            }
        }

        if (!found)
            return -1;
    }

    if (nodeattr->checker) {
        if (nodeattr->checker(nodeattr, value) != 0) {
            return -1;
        }
    }

    xmlSetProp(node->xmlnode, XMLSTR(key), XMLSTR(value));

    return 0;
}

char *                  reportxml_node_get_attribute(reportxml_node_t *node, const char *key)
{
    xmlChar *k;
    char *n;

    if (!node || !key)
        return NULL;

    /* We do this super complicated thing as we do not know if we can use free() to free a xmlChar* object. */
    k = xmlGetProp(node->xmlnode, XMLSTR(key));
    if (!k)
        return NULL;

    n = strdup((char*)k);
    xmlFree(k);

    return n;
}

int                     reportxml_node_add_child(reportxml_node_t *node, reportxml_node_t *child)
{
    const struct nodedef *nodedef;
    reportxml_node_t **n;
    size_t i;
    int found;

    if (!node || !child)
        return -1;

    nodedef = __get_nodedef(node->type);

    if (nodedef->content != NC_CHILDS)
        return -1;

    found = 0;
    for (i = 0; nodedef->childs[i] != REPORTXML_NODE_TYPE__ERROR; i++) {
        if (nodedef->childs[i] == child->type) {
            found = 1;
            break;
        }
    }

    if (!found)
        return -1;

    n = realloc(node->childs, sizeof(*n)*(node->childs_len + 1));
    if (!n)
        return -1;

    node->childs = n;

    if (refobject_ref(child) != 0)
        return -1;

    node->childs[node->childs_len++] = child;

//    ICECAST_LOG_DEBUG("Child %p added to Node %p", child, node);

    return 0;
}

ssize_t                 reportxml_node_count_child(reportxml_node_t *node)
{
    if (!node)
        return -1;

    return node->childs_len;
}

reportxml_node_t *      reportxml_node_get_child(reportxml_node_t *node, size_t idx)
{
    if (!node)
        return NULL;

    if (idx >= node->childs_len)
        return NULL;

    if (refobject_ref(node->childs[idx]) != 0)
        return NULL;

    return node->childs[idx];
}

reportxml_node_t *      reportxml_node_get_child_by_attribute(reportxml_node_t *node, const char *key, const char *value, int include_definitions)
{
    reportxml_node_t *ret;
    xmlChar *k;
    size_t i;

    if (!node || !key ||!value)
        return NULL;

    k = xmlGetProp(node->xmlnode, XMLSTR(key));
    if (k != NULL) {
        if (strcmp((const char*)k, value) == 0) {
            xmlFree(k);

            if (refobject_ref(node) != 0)
                return NULL;

            return node;
        }
        xmlFree(k);
    }

    if (node->type == REPORTXML_NODE_TYPE_DEFINITION && !include_definitions)
        return NULL;

    for (i = 0; i < node->childs_len; i++) {
        ret = reportxml_node_get_child_by_attribute(node->childs[i], key, value, include_definitions);
        if (ret != NULL)
            return ret;
    }

    return NULL;
}

reportxml_node_t *      reportxml_node_get_child_by_type(reportxml_node_t *node, reportxml_node_type_t type, int include_definitions)
{
    size_t i;

    if (!node)
        return NULL;

    if (node->type == type) {
        if (refobject_ref(node) != 0)
            return NULL;
        return node;
    }

    if (node->type == REPORTXML_NODE_TYPE_DEFINITION && !include_definitions)
        return NULL;

    for (i = 0; i < node->childs_len; i++) {
        reportxml_node_t *ret;

        ret = reportxml_node_get_child_by_type(node->childs[i], type, include_definitions);
        if (ret != NULL)
            return ret;
    }

    return NULL;
}

int                     reportxml_node_set_content(reportxml_node_t *node, const char *value)
{
    const struct nodedef *nodedef;
    char *n = NULL;

    if (!node)
        return -1;

    nodedef = __get_nodedef(node->type);

    if (nodedef->content != NC_CONTENT)
        return -1;

    if (value) {
        n = strdup(value);
        if (!n)
            return -1;
    }

    free(node->content);
    node->content = n;

    return 0;
}

char *              reportxml_node_get_content(reportxml_node_t *node)
{
    if (!node)
        return NULL;

    if (node->content) {
        return strdup(node->content);
    } else {
        return NULL;
    }
}

int                     reportxml_node_add_xml_child(reportxml_node_t *node, xmlNodePtr child)
{
    const struct nodedef *nodedef;
    xmlNodePtr *n;

    if (!node || !child)
        return -1;

    nodedef = __get_nodedef(node->type);

    if (nodedef->content != NC_XML)
        return -1;

    n = realloc(node->xml_childs, sizeof(*n)*(node->xml_childs_len + 1));
    if (!n)
        return -1;

    node->xml_childs = n;

    node->xml_childs[node->xml_childs_len] = xmlCopyNode(child, 1);
    if (node->xml_childs[node->xml_childs_len] == NULL)
        return -1;

    node->xml_childs_len++;

    return 0;
}

ssize_t                 reportxml_node_count_xml_child(reportxml_node_t *node)
{
    if (!node)
        return -1;

    return node->xml_childs_len;
}

xmlNodePtr              reportxml_node_get_xml_child(reportxml_node_t *node, size_t idx)
{
    xmlNodePtr ret;

    if (!node)
        return NULL;

    if (idx >= node->xml_childs_len)
        return NULL;

    ret = xmlCopyNode(node->xml_childs[idx], 1);

    return ret;
}

static void __database_free(refobject_t self, void **userdata)
{
    reportxml_database_t *db = REFOBJECT_TO_TYPE(self, reportxml_database_t *);

    if (db->definitions)
        avl_tree_free(db->definitions, (avl_free_key_fun_type)refobject_unref);

    thread_mutex_destroy(&(db->lock));
}

static int __compare_definitions(void *arg, void *a, void *b)
{
    char *id_a, *id_b;
    int ret = 0;

    id_a = reportxml_node_get_attribute(a, "defines");
    id_b = reportxml_node_get_attribute(b, "defines");

    if (!id_a || !id_b || id_a == id_b) {
        ret = 0;
    } else {
        ret = strcmp(id_a, id_b);
    }

    free(id_a);
    free(id_b);

    return ret;
}

static int __database_new(refobject_t self, const refobject_type_t *type, va_list ap)
{
    reportxml_database_t *ret = REFOBJECT_TO_TYPE(self, reportxml_database_t*);

    thread_mutex_create(&(ret->lock));

    ret->definitions = avl_tree_new(__compare_definitions, NULL);
    if (!ret->definitions)
        return -1;

    return 0;
}

REFOBJECT_DEFINE_TYPE(reportxml_database_t,
        REFOBJECT_DEFINE_TYPE_FREE(__database_free),
        REFOBJECT_DEFINE_TYPE_NEW(__database_new)
        );

reportxml_database_t *  reportxml_database_new(void)
{
    return refobject_new(reportxml_database_t);
}

int                     reportxml_database_add_report(reportxml_database_t *db, reportxml_t *report)
{
    reportxml_node_t *root;
    ssize_t count;
    size_t i;

    if (!db || !report)
        return -1;

    root = reportxml_get_root_node(report);
    if (!root)
        return -1;

    count = reportxml_node_count_child(root);
    if (count < 0)
        return -1;

    thread_mutex_lock(&(db->lock));

    for (i = 0; i < (size_t)count; i++) {
        reportxml_node_t *node = reportxml_node_get_child(root, i);
        reportxml_node_t *copy;

        if (reportxml_node_get_type(node) != REPORTXML_NODE_TYPE_DEFINITION) {
            refobject_unref(node);
            continue;
        }

        copy = reportxml_node_copy(node);
        refobject_unref(node);

        if (!copy)
            continue;

        avl_insert(db->definitions, copy);
    }

    thread_mutex_unlock(&(db->lock));

    refobject_unref(root);

    return 0;
}

static int __attach_copy_of_node_or_definition(reportxml_node_t *parent, reportxml_node_t *node, reportxml_database_t *db, ssize_t depth)
{
    reportxml_node_t *copy;
    reportxml_node_t *def = NULL;
    char *definition;

    if (!parent || !node || !db)
        return -1;


    if (depth >= 2) {
        definition = reportxml_node_get_attribute(node, "definition");
        if (definition) {
            ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, definition=\"%H\"", parent, node, depth, definition);
            def = reportxml_database_build_node(db, definition, depth - 1);
            ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Definition for \"%H\" at %p", parent, node, depth, definition, def);
            free(definition);
        }
    }

    if (def) {
        ssize_t count = reportxml_node_count_child(def);
        size_t i;

        ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Found definition.", parent, node, depth);

        if (count < 0) {
            refobject_unref(def);
            ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
            return -1;
        }

        for (i = 0; i < (size_t)count; i++) {
            reportxml_node_t *child = reportxml_node_get_child(def, i);

            ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Itering, child #%zu (%p)", parent, node, depth, i, child);

            if (__attach_copy_of_node_or_definition(parent, child, db, depth - 1) != 0) {
                refobject_unref(child);
                refobject_unref(def);
                ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
                return -1;
            }

            refobject_unref(child);
        }

        refobject_unref(def);

        ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- 0", parent, node, depth);
        return 0;
    } else {
        int ret;

        ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi, Found no definition.", parent, node, depth);

        copy = __reportxml_node_copy_with_db(node, db, depth - 1);
        if (!copy) {
            ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- -1", parent, node, depth);
            return -1;
        }

        ret = reportxml_node_add_child(parent, copy);

        refobject_unref(copy);

        ICECAST_LOG_DEBUG("parent=%p, node=%p, depth=%zi <- %i", parent, node, depth, ret);
        return ret;
    }
}

static reportxml_node_t *      __reportxml_database_build_node_ext(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t *acst_type_ret)
{
    reportxml_node_t *search;
    reportxml_node_t *found;
    reportxml_node_t *ret;
    enum {
        ACST_FIRST,
        ACST_YES,
        ACST_NO,
    } all_childs_same_type = ACST_FIRST;
    reportxml_node_type_t acst_type = REPORTXML_NODE_TYPE__ERROR;
    char *template;
    ssize_t count;
    size_t i;

    if (!db || !id)
        return NULL;

    /* Assign default depth in case it's set to -1 */
    if (depth < 0)
        depth = 8;

    ICECAST_LOG_DEBUG("Looking up \"%H\" in database %p with depth %zu", id, db, depth);

    if (!depth)
        return NULL;

    search = reportxml_node_new(REPORTXML_NODE_TYPE_DEFINITION, NULL, NULL, NULL);
    if (!search)
        return NULL;

    if (reportxml_node_set_attribute(search, "defines", id) != 0) {
        refobject_unref(search);
        return NULL;
    }

    thread_mutex_lock(&(db->lock));
    if (avl_get_by_key(db->definitions, REFOBJECT_TO_TYPE(search, void *), (void**)&found) != 0) {
        thread_mutex_unlock(&(db->lock));
        refobject_unref(search);
        return NULL;
    }

    refobject_unref(search);

    if (refobject_ref(found) != 0) {
        thread_mutex_unlock(&(db->lock));
        return NULL;
    }
    thread_mutex_unlock(&(db->lock));

    count = reportxml_node_count_child(found);
    if (count < 0) {
        refobject_unref(found);
        return NULL;
    }

    template = reportxml_node_get_attribute(found, "template");
    if (template) {
        reportxml_node_t *tpl = reportxml_database_build_node(db, template, depth - 1);

        free(template);

        if (tpl) {
            ret = reportxml_node_copy(tpl);
            refobject_unref(tpl);
        } else {
            ret = NULL;
        }
    } else {
        ret = reportxml_node_new(REPORTXML_NODE_TYPE_DEFINITION, NULL, NULL, NULL);
    }

    if (!ret) {
        refobject_unref(found);
        return NULL;
    }

    for (i = 0; i < (size_t)count; i++) {
        /* TODO: Look up definitions of our childs and childs' childs. */

        reportxml_node_t *node = reportxml_node_get_child(found, i);
        reportxml_node_type_t type = reportxml_node_get_type(node);

        switch (all_childs_same_type) {
            case ACST_FIRST:
                acst_type = type;
                all_childs_same_type = ACST_YES;
            break;
            case ACST_YES:
                if (acst_type != type)
                    all_childs_same_type = ACST_NO;
            break;
            case ACST_NO:
                /* noop */
            break;
        }

        /* We want depth, not depth - 1 here. __attach_copy_of_node_or_definition() takes care of this for us. */
        if (__attach_copy_of_node_or_definition(ret, node, db, depth) != 0) {
            refobject_unref(node);
            refobject_unref(found);
            refobject_unref(ret);
            ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p) in report. BAD.", i, node, ret);
            return NULL;
        }

        refobject_unref(node);
    }

    refobject_unref(found);

    if (all_childs_same_type == ACST_YES) {
        count = reportxml_node_count_child(ret);
        if (count < 0) {
            refobject_unref(ret);
            return NULL;
        }

        for (i = 0; i < (size_t)count; i++) {
            reportxml_node_t *node = reportxml_node_get_child(ret, i);

            if (!node) {
                refobject_unref(ret);
                return NULL;
            }

            if (reportxml_node_set_attribute(node, "_definition", id) != 0) {
                refobject_unref(node);
                refobject_unref(ret);
                return NULL;
            }

            refobject_unref(node);
        }
    }

    if (acst_type_ret) {
        if (all_childs_same_type == ACST_YES) {
            *acst_type_ret = acst_type;
        } else {
            *acst_type_ret = REPORTXML_NODE_TYPE__ERROR;
        }
    }

    return ret;
}

reportxml_node_t *      reportxml_database_build_node(reportxml_database_t *db, const char *id, ssize_t depth)
{
    return __reportxml_database_build_node_ext(db, id, depth, NULL);
}

/* We try to build a a report from the definition. Exat structure depends on what is defined. */
reportxml_t *           reportxml_database_build_report(reportxml_database_t *db, const char *id, ssize_t depth)
{
    reportxml_node_t *definition;
    reportxml_node_t *child;
    reportxml_node_t *root;
    reportxml_node_t *attach_to;
    reportxml_node_type_t type;
    reportxml_t *ret;
    ssize_t count;
    size_t i;

    if (!db || !id)
        return NULL;

    /* first find the definition itself.  This will be some REPORTXML_NODE_TYPE_DEFINITION node. */
    definition = __reportxml_database_build_node_ext(db, id, depth, &type);
    if (!definition) {
        ICECAST_LOG_WARN("No matching definition for \"%H\"", id);
        return NULL;
    }

    /* Let's see how many children we have. */
    count = reportxml_node_count_child(definition);
    if (count < 0) {
        refobject_unref(definition);
        ICECAST_LOG_ERROR("Can not get child count from definition. BAD.");
        return NULL;
    } else if (count == 0) {
        /* Empty definition? Not exactly an exciting report... */
        ICECAST_LOG_WARN("Empty definition for \"%H\". Returning empty report. This is likely an error.", id);
        refobject_unref(definition);
        return refobject_new(reportxml_t);
    }

    if (type == REPORTXML_NODE_TYPE__ERROR) {
        /* Now the hard part: find out what level we are. */
        child = reportxml_node_get_child(definition, 0);
        if (!child) {
            refobject_unref(definition);
            ICECAST_LOG_ERROR("Can not get first child. BAD.");
            return NULL;
        }

        type = reportxml_node_get_type(child);
        refobject_unref(child);
    }

    /* check for supported configurations */
    switch (type) {
        case REPORTXML_NODE_TYPE_INCIDENT:
        case REPORTXML_NODE_TYPE_STATE:
        break;
        default:
            refobject_unref(definition);
            ICECAST_LOG_WARN("Unsupported type of first child.");
            return NULL;
        break;
    }

    ret = refobject_new(reportxml_t);
    if (!ret) {
        refobject_unref(definition);
        ICECAST_LOG_ERROR("Can not allocate new report. BAD.");
        return NULL;
    }

    root = reportxml_get_root_node(ret);
    if (!root) {
        refobject_unref(definition);
        refobject_unref(ret);
        ICECAST_LOG_ERROR("Can not get root node from report. BAD.");
        return NULL;
    }

    if (type == REPORTXML_NODE_TYPE_INCIDENT) {
        refobject_ref(attach_to = root);
    } else if (type == REPORTXML_NODE_TYPE_STATE) {
        attach_to = reportxml_node_new(REPORTXML_NODE_TYPE_INCIDENT, NULL, NULL, NULL);
        if (attach_to) {
            if (reportxml_node_add_child(root, attach_to) != 0) {
                refobject_unref(attach_to);
                attach_to = NULL;
            }
        }
    } else {
        attach_to = NULL;
    }

    refobject_unref(root);

    if (!attach_to) {
        refobject_unref(definition);
        refobject_unref(ret);
        ICECAST_LOG_ERROR("No point to attach to in report. BAD.");
        return NULL;
    }

    /* now move nodes. */

    for (i = 0; i < (size_t)count; i++) {
        child = reportxml_node_get_child(definition, i);

        if (reportxml_node_get_type(child) == type) {
            /* Attach definition to all childs that are the same type.
             * As long as we work to-the-specs all childs are of the same type.
             * But if we work in relaxed mode, there might be other tags.
             */
            reportxml_node_set_attribute(child, "definition", id);
        }

        /* we can directly attach as it's a already a copy. */
        if (reportxml_node_add_child(attach_to, child) != 0) {
            refobject_unref(definition);
            refobject_unref(attach_to);
            refobject_unref(ret);
            ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p) in report. BAD.", i, child, attach_to);
            return NULL;
        }

        refobject_unref(child);
    }

    refobject_unref(definition);
    refobject_unref(attach_to);

    return ret;
}

reportxml_node_t *      reportxml_database_build_fragment(reportxml_database_t *db, const char *id, ssize_t depth, reportxml_node_type_t type)
{
    reportxml_node_t *definition;
    reportxml_node_t *ret;
    reportxml_node_type_t got;
    ssize_t count;
    size_t i;

    if (!db || !id)
        return NULL;

    /* first find the definition itself.  This will be some REPORTXML_NODE_TYPE_DEFINITION node. */
    definition = __reportxml_database_build_node_ext(db, id, depth, &got);
    if (!definition) {
        ICECAST_LOG_WARN("No matching definition for \"%H\"", id);
        return NULL;
    }

    /* Let's see how many children we have. */
    count = reportxml_node_count_child(definition);
    if (count < 0) {
        refobject_unref(definition);
        ICECAST_LOG_ERROR("Can not get child count from definition. BAD.");
        return NULL;
    } else if (count == 0) {
        /* Empty definition? Not exactly an exciting report... */
        ICECAST_LOG_WARN("Empty definition for \"%H\". Returning empty node. This is likely an error.", id);
        refobject_unref(definition);
        return reportxml_node_new(type, NULL, id, NULL);
    } else if (count == 1 && got == type) {
        ret = reportxml_node_get_child(definition, 0);
        refobject_unref(definition);
        return ret;
    } else if (got == type) {
        refobject_unref(definition);
        ICECAST_LOG_ERROR("Definition lists multiple childs of target type. BAD.");
        return NULL;
    }

    ret = reportxml_node_new(type, NULL, NULL, NULL);
    if (!ret) {
        refobject_unref(definition);
        ICECAST_LOG_ERROR("Can not allocate new node. BAD.");
        return NULL;
    }

    for (i = 0; i < (size_t)count; i++) {
        reportxml_node_t *child = reportxml_node_get_child(definition, i);

        /* we can directly attach as it's a already a copy. */
        if (reportxml_node_add_child(ret, child) != 0) {
            refobject_unref(definition);
            refobject_unref(ret);
            ICECAST_LOG_ERROR("Can not attach child #%zu (%p) to attachment point (%p). BAD.", i, child, ret);
            return NULL;
        }

        refobject_unref(child);
    }

    refobject_unref(definition);
    return ret;
}
