/*
 * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 * Copyright (C) 2007 OpenedHand Ltd
 *
 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 *          Jorn Baayen <jorn@openedhand.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 */

/**
 * SECTION:gupnp-last-change-parser
 * @short_description: A/V LastChange event XML parser
 *
 * #GUPnPLastChangeParser parses XML strings from LastChange events that are
 * generated by AVTransport and RenderingControl services.
 *
 */
#include <config.h>


#include <gobject/gvaluecollector.h>

#include "gupnp-last-change-parser.h"
#include "gvalue-util.h"
#include "xml-util.h"

G_DEFINE_TYPE (GUPnPLastChangeParser,
               gupnp_last_change_parser,
               G_TYPE_OBJECT)

static void
gupnp_last_change_parser_init (G_GNUC_UNUSED GUPnPLastChangeParser *parser)
{
}

static void
gupnp_last_change_parser_dispose (GObject *object)
{
        GObjectClass   *gobject_class;

        gobject_class = G_OBJECT_CLASS (gupnp_last_change_parser_parent_class);
        gobject_class->dispose (object);
}

static void
gupnp_last_change_parser_class_init (GUPnPLastChangeParserClass *klass)
{
        GObjectClass *object_class;

        object_class = G_OBJECT_CLASS (klass);

        object_class->dispose = gupnp_last_change_parser_dispose;
}

/* Reads a value of state variable @variable_name to an initialised GValue pair
 * from the InstanceID node of a LastChange xml doc */
static gboolean
read_state_variable (const char *variable_name,
                     GValue     *value,
                     xmlNode    *instance_node)
{
        xmlNode    *variable_node;
        const char *val_str;

        variable_node = av_xml_util_get_element (instance_node,
                                                 variable_name,
                                                 NULL);
        if (!variable_node)
                return FALSE;

        val_str = av_xml_util_get_attribute_content (variable_node, "val");
        if (!val_str) {
                g_warning ("No value provided for variable \"%s\" in "
                           "LastChange event",
                           variable_name);

                return FALSE;
        }

        av_gvalue_util_set_value_from_string (value, val_str);

        return TRUE;
}

static xmlNode *
get_instance_node (xmlDoc *doc,
                   guint   instance_id)
{
        xmlNode *node;

        if (doc->children == NULL)
                return NULL;

        for (node = doc->children->children;
             node;
             node = node->next) {
                if (node->type != XML_ELEMENT_NODE)
                        continue;

                if (!xmlStrcmp (node->name, BAD_CAST ("InstanceID")) &&
                    av_xml_util_get_uint_attribute (node, "val", 0) ==
                                        instance_id)
                        break;
        }

        return node;
}

/**
 * gupnp_last_change_parser_new:
 *
 * Return value: A new #GUPnPLastChangeParser
 **/
GUPnPLastChangeParser *
gupnp_last_change_parser_new (void)
{
        return g_object_new (GUPNP_TYPE_LAST_CHANGE_PARSER,
                             NULL);
}

/**
 * gupnp_last_change_parser_parse_last_change_valist:
 * @parser: A #GUPnPLastChangeParser
 * @instance_id: The ID of the AV instance caller is interested in
 * @last_change_xml: The xml from the "LastChange" event to parse
 * @error: The location where to store any error, or NULL
 * @var_args: A va_list of tuples of state variable name, state variable type,
 * and state variable value location, terminated with NULL. The state variable
 * values should be freed after use
 *
 * See gupnp_last_change_parser_parse_last_change(); this version takes a
 * va_list for use by language bindings.
 *
 * Return value: TRUE on success.
 **/
gboolean
gupnp_last_change_parser_parse_last_change_valist
                         (G_GNUC_UNUSED GUPnPLastChangeParser *parser,
                          guint                                instance_id,
                          const char                          *last_change_xml,
                          GError                             **error,
                          va_list                              var_args)
{
        const char *variable_name;
        xmlDoc  *doc;
        xmlNode *instance_node;

        g_return_val_if_fail (last_change_xml, FALSE);

        doc = xmlParseDoc ((const xmlChar *) last_change_xml);
        if (doc == NULL) {
                g_set_error (error,
                             G_MARKUP_ERROR,
                             G_MARKUP_ERROR_PARSE,
                             "Could not parse LastChange xml");

                return FALSE;
        }

        instance_node = get_instance_node (doc, instance_id);
        if (instance_node == NULL) {
                /* This is not an error since the caller of this function
                 * doesn't (need to) know if the instance of his interest is
                 * part of the LastChange event received.
                 */
                xmlFreeDoc (doc);

                return FALSE;
        }

        /* Variables */
        variable_name = va_arg (var_args, const char *);
        while (variable_name) {
                GType variable_type;
                GValue value = { 0, };
                char *copy_error = NULL;

                variable_type = va_arg (var_args, GType);

                g_value_init (&value, variable_type);

                if (read_state_variable (variable_name,
                                         &value,
                                         instance_node)) {
                        G_VALUE_LCOPY (&value, var_args, 0, &copy_error);
                } else {
                        va_arg (var_args, gpointer);
                }

                g_value_unset (&value);

                if (copy_error) {
                        g_warning ("Error copying value: %s", copy_error);

                        g_free (copy_error);
                }

                variable_name = va_arg (var_args, const char *);
        }

        /* Cleanup */
        xmlFreeDoc (doc);

        return TRUE;
}

/**
 * gupnp_last_change_parser_parse_last_change:
 * @parser: A #GUPnPLastChangeParser
 * @instance_id: The ID of the AV instance caller is interested in
 * @last_change_xml: The xml from the "LastChange" event to parse
 * @error: The location where to store any error, or NULL
 * @...: tuples of state variable name, state variable type, and state
 * variable value location, terminated with NULL. The state variable values
 * should be freed after use.
 *
 * Parses the xml fragment from a LastChange event.
 *
 * Return value: TRUE on success.
 **/
gboolean
gupnp_last_change_parser_parse_last_change
                                (GUPnPLastChangeParser *parser,
                                 guint                  instance_id,
                                 const char            *last_change_xml,
                                 GError               **error,
                                 ...)
{
        va_list var_args;
        gboolean ret;

        va_start (var_args, error);
        ret = gupnp_last_change_parser_parse_last_change_valist
                                                (parser,
                                                 instance_id,
                                                 last_change_xml,
                                                 error,
                                                 var_args);
        va_end (var_args);

        return ret;
}

