/*
 * Copyright (C) 2006, 2007 OpenedHand Ltd.
 * Copyright (C) 2007 Zeeshan Ali.
 * Copyright (C) 2012 Intel Corporation
 *
 * Author: Jorn Baayen <jorn@openedhand.com>
 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 * Author: Krzesimir Nowak <krnowak@openismus.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <string.h>

#include "xml-util.h"

typedef struct _GUPnPXMLNamespaceDescription
{
        const char *uri;
        const char *prefix;
} GUPnPXMLNamespaceDescription;


static GUPnPXMLNamespaceDescription gupnp_xml_namespaces[] =
{
        { "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/", NULL },
        { "http://purl.org/dc/elements/1.1/", "dc" },
        { "urn:schemas-dlna-org:metadata-1-0/", "dlna" },
        { "http://www.pv.com/pvns/", "pv" },
        { "urn:schemas-upnp-org:metadata-1-0/upnp/", "upnp" },
        { NULL }
};

GUPnPAVXMLDoc *
xml_doc_new (xmlDoc *doc)
{
        GUPnPAVXMLDoc *ret = NULL;

        g_return_val_if_fail (doc, NULL);

        ret = g_new0 (GUPnPAVXMLDoc, 1);
        ret->refcount = 1;
        ret->doc = doc;

        return ret;
}

GUPnPAVXMLDoc *
xml_doc_ref (GUPnPAVXMLDoc *doc)
{
        g_return_val_if_fail (doc, NULL);
        g_return_val_if_fail (doc->refcount > 0, NULL);
        g_atomic_int_inc (&doc->refcount);

        return doc;
}

void
xml_doc_unref (GUPnPAVXMLDoc *doc)
{
        g_return_if_fail (doc);
        g_return_if_fail (doc->refcount > 0);

        if (g_atomic_int_dec_and_test (&doc->refcount)) {
                xmlFreeDoc (doc->doc);
                doc->doc = NULL;
        }
}

xmlNode *
xml_util_get_element (xmlNode *node,
                      ...)
{
        va_list var_args;

        va_start (var_args, node);

        while (TRUE) {
                const char *arg;

                arg = va_arg (var_args, const char *);
                if (!arg)
                        break;

                for (node = node->children; node; node = node->next) {
                        if (node->name == NULL)
                                continue;

                        if (!g_ascii_strcasecmp (arg, (char *) node->name))
                                break;
                }

                if (!node)
                        break;
        }

        va_end (var_args);

        return node;
}

GList *
xml_util_get_child_elements_by_name (xmlNode *node, const char *name)
{
       GList *children = NULL;

       for (node = node->children; node; node = node->next) {
               if (node->name == NULL)
                       continue;

               if (strcmp (name, (char *) node->name) == 0) {
                       children = g_list_append (children, node);
               }
       }

       return children;
}

const char *
xml_util_get_child_element_content (xmlNode    *node,
                                    const char *child_name)
{
        xmlNode *child_node;
        const char *content;

        child_node = xml_util_get_element (node, child_name, NULL);
        if (!child_node || !(child_node->children))
                return NULL;

        content = (const char *) child_node->children->content;
        if (!content)
                return NULL;

        return content;
}

guint
xml_util_get_uint_child_element (xmlNode    *node,
                                 const char *child_name,
                                 guint       default_value)
{
        const char *content;

        content = xml_util_get_child_element_content (node, child_name);
        if (!content)
                return default_value;

        return strtoul (content, NULL, 0);
}

guint64
xml_util_get_uint64_child_element (xmlNode    *node,
                                   const char *child_name,
                                   guint64     default_value)
{
        const char *content;

        content = xml_util_get_child_element_content (node, child_name);
        if (!content)
                return default_value;

        return g_ascii_strtoull (content, NULL, 0);
}

const char *
xml_util_get_attribute_content (xmlNode    *node,
                                const char *attribute_name)
{
        xmlAttr *attribute;

        for (attribute = node->properties;
             attribute;
             attribute = attribute->next) {
                if (attribute->name == NULL)
                        continue;

                if (strcmp (attribute_name, (char *) attribute->name) == 0)
                        break;
        }

        if (attribute)
                return (const char *) attribute->children->content;
        else
                return NULL;
}

gboolean
xml_util_get_boolean_attribute (xmlNode    *node,
                                const char *attribute_name)
{
        const char *content;
        gchar   *str;
        gboolean ret;

        content = xml_util_get_attribute_content (node, attribute_name);
        if (!content)
                return FALSE;

        str = (char *) content;
        if (g_ascii_strcasecmp (str, "true") == 0 ||
            g_ascii_strcasecmp (str, "yes") == 0)
                ret = TRUE;
        else if (g_ascii_strcasecmp (str, "false") == 0 ||
                 g_ascii_strcasecmp (str, "no") == 0)
                ret = FALSE;
        else {
                int i;

                i = atoi (str);
                ret = i ? TRUE : FALSE;
        }

        return ret;
}

guint
xml_util_get_uint_attribute (xmlNode    *node,
                             const char *attribute_name,
                             guint       default_value)
{
        return (guint) xml_util_get_long_attribute (node,
                                                    attribute_name,
                                                    (glong) default_value);
}

gint
xml_util_get_int_attribute (xmlNode    *node,
                            const char *attribute_name,
                            gint        default_value)
{
        return (gint) xml_util_get_long_attribute (node,
                                                   attribute_name,
                                                   (glong) default_value);
}

glong
xml_util_get_long_attribute (xmlNode    *node,
                             const char *attribute_name,
                             glong       default_value)
{
    return (glong) xml_util_get_int64_attribute (node,
                                                 attribute_name,
                                                 (gint64) default_value);
}

gint64
xml_util_get_int64_attribute (xmlNode    *node,
                              const char *attribute_name,
                              gint64      default_value)
{
        const char *content;

        content = xml_util_get_attribute_content (node, attribute_name);
        if (!content)
                return default_value;

        return g_ascii_strtoll (content, NULL, 0);
}

xmlNode *
xml_util_set_child (xmlNode    *parent_node,
                    GUPnPXMLNamespace ns,
                    xmlNsPtr   *xmlns,
                    xmlDoc     *doc,
                    const char *name,
                    const char *value)
{
        xmlNode *node;
        xmlChar *escaped;

        node = xml_util_get_element (parent_node, name, NULL);
        if (node == NULL) {
                xmlNsPtr ns_ptr = NULL;

                ns_ptr = xml_util_get_ns (doc, ns, xmlns);
                node = xmlNewChild (parent_node,
                                    ns_ptr,
                                    (unsigned char *) name,
                                    NULL);
        }

        escaped = xmlEncodeSpecialChars (doc, (const unsigned char *) value);
        xmlNodeSetContent (node, escaped);
        xmlFree (escaped);

        return node;
}

void
xml_util_unset_child (xmlNode    *parent_node,
                      const char *name)
{
        xmlNode *node;

        node = xml_util_get_element (parent_node, name, NULL);
        if (node != NULL) {
                xmlUnlinkNode (node);
                xmlFreeNode (node);
        }
}

gboolean
xml_util_verify_attribute_is_boolean (xmlNode    *node,
                                      const char *attribute_name)
{
        const char *content;
        char *str;

        content = xml_util_get_attribute_content (node, attribute_name);
        if (content == NULL)
            return FALSE;

        str = (char *) content;

        return g_ascii_strcasecmp (str, "true") == 0 ||
               g_ascii_strcasecmp (str, "yes") == 0 ||
               g_ascii_strcasecmp (str, "false") == 0 ||
               g_ascii_strcasecmp (str, "no") == 0 ||
               g_ascii_strcasecmp (str, "0") == 0 ||
               g_ascii_strcasecmp (str, "1") == 0;
}

char *
xml_util_get_child_string (xmlNode    *parent_node,
                           xmlDoc     *doc,
                           const char *name)
{
        xmlBuffer *buffer;
        char      *ret;
        xmlNode   *node;

        node = xml_util_get_element (parent_node, name, NULL);
        if (!node)
                return NULL;

        buffer = xmlBufferCreate ();
        xmlNodeDump (buffer,
                     doc,
                     node,
                     0,
                     0);
        ret = g_strndup ((char *) xmlBufferContent (buffer),
                         xmlBufferLength (buffer));
        xmlBufferFree (buffer);

        return ret;
}

gboolean
xml_util_node_deep_equal (xmlNode *first,
                          xmlNode *second)
{
        GHashTable *first_attributes;
        xmlAttr *attribute;
        gboolean equal;

        if (first == NULL && second == NULL)
                return TRUE;
        if (first == NULL || second == NULL)
                return FALSE;

        if (xmlStrcmp (first->name, second->name))
                return FALSE;

        equal = FALSE;
        first_attributes = xml_util_get_attributes_map (first);
        /* compare attributes */
        for (attribute = second->properties;
             attribute != NULL;
             attribute = attribute->next) {
                const xmlChar *value = NULL;
                const xmlChar *key = attribute->name;

                if (g_hash_table_lookup_extended (first_attributes,
                                                  key,
                                                  NULL,
                                                  (gpointer *) &value))
                        if (!xmlStrcmp (value, attribute->children->content)) {
                                g_hash_table_remove (first_attributes, key);

                                continue;
                        }

                goto out;
        }

        if (g_hash_table_size (first_attributes))
                goto out;

        /* compare content */
        if (xmlStrcmp (first->content, second->content))
                goto out;
        equal = TRUE;
 out:
        g_hash_table_unref (first_attributes);
        if (equal) {
                xmlNode *first_child;
                xmlNode *second_child;

                for (first_child = first->children,
                     second_child = second->children;
                     first_child != NULL && second_child != NULL;
                     first_child = first_child->next,
                     second_child = second_child->next)
                        if (!xml_util_node_deep_equal (first_child, second_child))
                                return FALSE;
                if (first_child != NULL || second_child != NULL)
                        return FALSE;
        }

        return equal;
}

xmlNode *
xml_util_find_node (xmlNode *haystack,
                    xmlNode *needle)
{
        xmlNodePtr iter;

        if (xml_util_node_deep_equal (haystack, needle))
                return haystack;

        for (iter = haystack->children; iter != NULL; iter = iter->next) {
                xmlNodePtr found_node = xml_util_find_node (iter, needle);

                if (found_node != NULL)
                        return found_node;
        }

        return NULL;
}

xmlNode *
xml_util_copy_node (xmlNode *node)
{
        xmlNode *dup = xmlCopyNode (node, 1);

        /* TODO: remove useless namespace definition. */

        return dup;
}

GHashTable *
xml_util_get_attributes_map (xmlNode *node)
{
        xmlAttr *attribute;
        GHashTable *attributes_map = g_hash_table_new (g_str_hash,
                                                       g_str_equal);

        for (attribute = node->properties;
             attribute != NULL;
             attribute = attribute->next)
                g_hash_table_insert (attributes_map,
                                     (gpointer) attribute->name,
                                     (gpointer) attribute->children->content);

        return attributes_map;
}

/**
 * xml_util_create_namespace:
 * @root: (allow-none): Document root node or %NULL for anonymous ns.
 * @ns: Namespace
 * @returns: Newly created namespace on root node
 */
xmlNsPtr
xml_util_create_namespace (xmlNodePtr root, GUPnPXMLNamespace ns)
{
        g_return_val_if_fail (ns < GUPNP_XML_NAMESPACE_COUNT, NULL);

        return xmlNewNs (root,
                         (const xmlChar *) gupnp_xml_namespaces[ns].uri,
                         (const xmlChar *) gupnp_xml_namespaces[ns].prefix);
}

/**
 * xml_util_lookup_namespace:
 * @doc: #xmlDoc
 * @ns: namespace to look up (except DIDL-Lite, which doesn't have a prefix)
 * @returns: %NULL if namespace does not exist, a pointer to the namespace
 * otherwise.
 */
xmlNsPtr
xml_util_lookup_namespace (xmlDocPtr doc, GUPnPXMLNamespace ns)
{
        xmlNsPtr *ns_list, *it, retval = NULL;
        const char *ns_prefix = NULL;
        const char *ns_uri = NULL;

        g_return_val_if_fail (ns < GUPNP_XML_NAMESPACE_COUNT, NULL);

        ns_prefix = gupnp_xml_namespaces[ns].prefix;
        ns_uri = gupnp_xml_namespaces[ns].uri;

        ns_list = xmlGetNsList (doc, xmlDocGetRootElement (doc));
        if (ns_list == NULL)
                return NULL;

        for (it = ns_list; *it != NULL; it++) {
                const char *it_prefix = (const char *) (*it)->prefix;
                const char *it_uri = (const char *) (*it)->href;

                if (it_prefix == NULL) {
                        if (ns_prefix != NULL)
                                continue;

                        if (g_ascii_strcasecmp (it_uri, ns_uri) == 0) {
                                retval = *it;
                                break;
                        }

                        continue;
                }

                if (g_ascii_strcasecmp (it_prefix, ns_prefix) == 0) {
                        retval = *it;
                        break;
                }
        }

        xmlFree (ns_list);

        return retval;
}

/**
 * xml_util_get_ns:
 * @doc: A #xmlDoc.
 * @ns: A #GUPnPXMLNamespace.
 * @ns_out: (out) (allow-none): return location for the namespace or %NULL.
 *
 * Lazy-create a XML namespace on @doc.
 *
 * If @ns_out is non-%NULL, the function will return @ns_out immediately.
 * @returns: either the existing #xmlNsPtr or a newly created one.
 */
xmlNsPtr
xml_util_get_ns (xmlDocPtr doc, GUPnPXMLNamespace ns, xmlNsPtr *ns_out)
{
        xmlNsPtr tmp_ns;

        /* User supplied namespace, just return that */
        if (ns_out != NULL && *ns_out != NULL)
                return *ns_out;

        tmp_ns = xml_util_lookup_namespace (doc, ns);
        if (!tmp_ns)
                tmp_ns = xml_util_create_namespace (xmlDocGetRootElement (doc),
                                                    ns);

        if (ns_out != NULL)
                *ns_out = tmp_ns;

        return tmp_ns;
}

G_DEFINE_BOXED_TYPE (GUPnPAVXMLDoc, xml_doc, xml_doc_ref, xml_doc_unref)
