/*
 * This file is part of GNAC - Gnome Audio Converter
 *
 * Copyright (C) 2007 - 2012 Gnac
 *    
 *    - DUPASQUIER  Benoit    <bdupasqu@src.gnome.org>
 *    - JOAQUIM     David     <djoaquim@src.gnome.org>
 *    - ROUX        Alexandre <alexroux@src.gnome.org>
 *
 * GNAC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * GNAC 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNAC; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <glib/gi18n.h>
#include <glib/gstdio.h>

#include <libxml/xpath.h>

#include "gnac-profiles-xml-engine.h"
#include "gnac-profiles-utils.h"
#include "gnac-utils.h"


XMLDoc *
gnac_profiles_xml_engine_load_doc_xpath(const gchar *filename)
{
  xmlDocPtr doc = xmlParseFile(filename);
  if (!doc) return NULL;

  xmlXPathContextPtr xpath_context = xmlXPathNewContext(doc);
  if (!xpath_context) {
    xmlFreeDoc(doc);
    return NULL;
  }

  XMLDoc *dx = g_malloc(sizeof(XMLDoc));
  dx->doc = doc;
  dx->xpath_context = xpath_context;

  return dx;
}


static xmlXPathObjectPtr
gnac_profiles_xml_engine_evaluate_query(const gchar        *query,
                                        xmlXPathContextPtr  xpath_context)
{
  return xmlXPathEvalExpression(BAD_CAST query, xpath_context);
}


static gchar *
gnac_profiles_xml_engine_get_property(XMLDoc      *doc,
                                      const gchar *query,
                                      const gchar *property_name)
{
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      query, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  xmlChar *property = xmlGetProp(nodes->nodeTab[0], (xmlChar *) property_name);

  xmlXPathFreeObject(xpath_obj);

  return (gchar *) property;
}


gchar *
gnac_profiles_xml_engine_get_format_id(XMLDoc *doc)
{
  return gnac_profiles_xml_engine_get_property(doc, "//profiles/profile", "id");
}


gchar *
gnac_profiles_xml_engine_get_variable_type(XMLDoc      *doc,
                                           const gchar *expr)
{
  return gnac_profiles_xml_engine_get_property(doc, expr, "type");
}


gchar *
gnac_profiles_xml_engine_get_text_node(XMLDoc      *doc,
                                       const gchar *expr)
{
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  xmlNodePtr node = nodes->nodeTab[0];
  if (!node || !(node->children)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  xmlXPathFreeObject(xpath_obj);

  xmlChar *content = xmlNodeGetContent(node->children);
  gchar *translated = g_strdup(gettext((gchar *) content));
  xmlFree(content);

  return translated;
}


gchar *
gnac_profiles_xml_engine_query_name(XMLDoc      *doc,
                                    const gchar *format)
{
  gchar *query = g_strconcat("//profile[@id='", format, "']/name", NULL);
  gchar *name = gnac_profiles_xml_engine_get_text_node(doc, query);
  g_free(query);
  return name;
}


gchar *
gnac_profiles_xml_engine_query_extension(XMLDoc      *doc,
                                         const gchar *format)
{
  gchar *query = g_strconcat("//profile[@id='", format,
      "']/output-file-extension", NULL);
  gchar *extension = gnac_profiles_xml_engine_get_text_node(doc, query);
  g_free(query);
  return extension;
}


gchar *
gnac_profiles_xml_engine_query_description(XMLDoc      *doc,
                                           const gchar *format)
{
  gchar *query = g_strconcat("//profile[@id='", format, "']/description", NULL);
  gchar *description = gnac_profiles_xml_engine_get_text_node(doc, query);
  g_free(query);
  return description;
}


static gboolean
gnac_profiles_xml_engine_str_equal(const xmlChar *str1,
                                   const gchar   *str2)
{
  return gnac_utils_str_equal((const gchar *) str1, str2);
}


static gboolean
gnac_profiles_xml_engine_node_name_equal(xmlNode     *node,
                                         const gchar *name)
{
  return gnac_profiles_xml_engine_str_equal(node->name, name);
}


static gboolean
gnac_profiles_xml_engine_is_i18n_node(xmlNodePtr node)
{
  return gnac_profiles_xml_engine_str_equal(node->properties->name, "lang");
}


GList *
gnac_profiles_xml_engine_get_list_values(XMLDoc      *doc,
                                         const gchar *expr)
{
  xmlXPathObjectPtr  xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  gint i;
  GList *list = NULL;

  for (i = 0; i < nodes->nodeNr; i++) {
    xmlNode *current_node = nodes->nodeTab[i];
    
    if (!gnac_profiles_xml_engine_is_i18n_node(current_node)) {
      Value *val = g_malloc(sizeof(Value));
      val->name = gettext(
          (gchar *) xmlNodeGetContent(current_node->children));
      val->value =
          (gchar *) xmlNodeGetContent(current_node->properties->children);
      list = g_list_prepend(list, val);
    }
  }

  xmlXPathFreeObject(xpath_obj);

  return g_list_reverse(list);
}


ComboValues *
gnac_profiles_xml_engine_get_combo_values(XMLDoc      *doc,
                                          const gchar *expr)
{
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  xmlNode *values_first = NULL;
  xmlNode *values_last = NULL;
  gchar   *default_value = NULL;
  gchar   *variable_name = NULL;

  xmlNodePtr current_node = nodes->nodeTab[0]->children;

  while ((current_node = current_node->next)) {
    gchar *content = (gchar *) xmlNodeGetContent(current_node->children);

    if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "variable-name"))
    {
      variable_name = g_strdup(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "default-value"))
    {
      default_value = g_strdup(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "possible-values"))
    {
      values_first = current_node->children->next;
      values_last = current_node->last;
    }

    g_free(content);
  }

  if (values_first && values_last && default_value && variable_name) {
    GList *values = NULL;
    GList *names = NULL;

    xmlNode *current_node = values_first;

    while (current_node && current_node != values_last) {
      if (!gnac_profiles_xml_engine_is_i18n_node(current_node)) {
        values = g_list_prepend(values,
            xmlNodeGetContent(current_node->properties->children));
        xmlChar *content = xmlNodeGetContent(current_node->children);
        names = g_list_prepend(names, g_strdup(gettext((gchar *) content)));
        xmlFree(content);
      }

      current_node = current_node->next;

      if (xmlNodeIsText(current_node)) {
        current_node = current_node->next;
      }
    }

    xmlXPathFreeObject(xpath_obj);

    ComboValues *combo_values = g_malloc(sizeof(ComboValues));
    combo_values->names = g_list_reverse(names);
    combo_values->values = g_list_reverse(values);
    combo_values->default_value = default_value;
    combo_values->variable_name = variable_name;

    return combo_values;
  }
  
  xmlXPathFreeObject(xpath_obj);

  return NULL;
}


SliderValues *
gnac_profiles_xml_engine_get_slider_values(XMLDoc      *doc,
                                          const gchar  *expr)
{
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  gdouble  min = 0.0;
  gdouble  max = 0.0;
  gdouble  step = 0.0;
  gdouble  default_value = 0.0;
  gchar   *variable_name = NULL;

  xmlNodePtr current_node = nodes->nodeTab[0]->children;

  while ((current_node = current_node->next)) {
    gchar *content = (gchar *) xmlNodeGetContent(current_node->children);

    if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "variable-name"))
    {
      variable_name = g_strdup(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "default-value"))
    {
      default_value = gnac_profiles_utils_gchararray_to_gdouble(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "min-value"))
    {
      min = gnac_profiles_utils_gchararray_to_gdouble(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "max-value"))
    {
      max = gnac_profiles_utils_gchararray_to_gdouble(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "step-value"))
    {
      step = gnac_profiles_utils_gchararray_to_gdouble(content);
    }

    g_free(content);
  }

  if (variable_name) {
    xmlXPathFreeObject(xpath_obj);

    SliderValues *slider_values = g_malloc(sizeof(SliderValues));
    slider_values->min = min;
    slider_values->max = max;
    slider_values->step = step;
    slider_values->default_value = default_value;
    slider_values->variable_name = variable_name;

    return slider_values;
  }

  xmlXPathFreeObject(xpath_obj);

  return NULL;
}


CheckValues *
gnac_profiles_xml_engine_get_check_values(XMLDoc      *doc,
                                          const gchar *expr)
{
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return NULL;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return NULL;
  }

  gchar    *variable_name = NULL;
  gboolean  default_value = FALSE;

  xmlNodePtr current_node = nodes->nodeTab[0]->children;

  while ((current_node = current_node->next)) {
    gchar *content = (gchar *) xmlNodeGetContent(current_node->children);

    if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "variable-name"))
    {
      variable_name = g_strdup(content);
    }
    else if (gnac_profiles_xml_engine_node_name_equal(current_node,
        "default-value"))
    {
      default_value = gnac_utils_str_equal(content, "true");
    }

    g_free(content);
  }

  if (variable_name) {
    xmlXPathFreeObject(xpath_obj);

    CheckValues *check_values = g_malloc(sizeof(CheckValues));
    check_values->default_value = default_value;
    check_values->variable_name = variable_name;

    return check_values;
  }

  xmlXPathFreeObject(xpath_obj);

  return NULL;
}


void
gnac_profiles_xml_engine_modify_values(XMLDoc      *doc,
                                       const gchar *expr,
                                       ...)
{
  xmlXPathObjectPtr  xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return;

  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return;
  }
  
  va_list ap;
  va_start(ap, expr);

  gint i;
  const gchar *value = va_arg(ap, const gchar *);

  for (i = nodes->nodeNr-1; i >= 0; i--) {
    gchar *value_formatted = gnac_profiles_xml_engine_format_text_to_xml(value);
    xmlNodeSetContent(nodes->nodeTab[i], BAD_CAST value_formatted);
    g_free(value_formatted);
    
    if (nodes->nodeTab[i]->type != XML_NAMESPACE_DECL) {
      nodes->nodeTab[i] = NULL;
    }
    
    value = va_arg(ap, const gchar *);
  }

  xmlXPathFreeObject(xpath_obj);
}


void
gnac_profiles_xml_engine_add_values(XMLDoc *doc, ...)
{
  const gchar *expr = "/audio-profile/format-specific";
  xmlXPathObjectPtr xpath_obj = gnac_profiles_xml_engine_evaluate_query(
      expr, doc->xpath_context);
  if (!xpath_obj) return;
  
  xmlNodeSetPtr nodes = xpath_obj->nodesetval;
  if (xmlXPathNodeSetIsEmpty(nodes)) {
    xmlXPathFreeObject(xpath_obj);
    return;
  }

  xmlNodePtr format_node = nodes->nodeTab[0];
  const xmlChar *base_indent = format_node->prev->content;
  xmlChar *properties_indent = (xmlChar *)
      g_strconcat((const gchar *) base_indent, "  ", NULL);
  xmlAddChild(format_node, xmlNewText(BAD_CAST "  "));

  va_list ap;
  va_start(ap, doc);

  const xmlChar *name = va_arg(ap, const xmlChar *);

  while (name) {
    const gchar *value = va_arg(ap, const gchar *);
    xmlNodePtr node = xmlNewNode(format_node->ns, name);
    node = xmlAddChild(format_node, node);

    if (!gnac_utils_string_is_null_or_empty(value)) {
      gchar *formatted = gnac_profiles_xml_engine_format_text_to_xml(value);
      xmlNodeSetContent(node, BAD_CAST formatted);
      g_free(formatted);
    }
    
    name = va_arg(ap, const xmlChar *);
    xmlAddChild(format_node, xmlNewText(name ? properties_indent : base_indent));
  }

  va_end(ap);

  g_free(properties_indent);
  xmlXPathFreeObject(xpath_obj);
}


gchar *
gnac_profiles_xml_engine_format_text_to_xml(const gchar *text)
{
  if (!text) return NULL;

  return g_markup_escape_text(text, -1);
}


void
gnac_profiles_xml_engine_save_doc(XMLDoc      *doc,
                                  const gchar *filename)
{
  gchar *path = g_strconcat(GNAC_SAVED_PROFILES_URL_WITH_EXT(filename), NULL);
  xmlSaveFormatFile(path, doc->doc, 0);
  g_free(path);
}


void
gnac_profiles_xml_engine_free_doc_xpath(XMLDoc *doc)
{
  if (!doc) return;

  xmlXPathFreeContext(doc->xpath_context);
  xmlFreeDoc(doc->doc);
  g_free(doc);
}


void
gnac_profiles_xml_engine_free_combo_values(ComboValues *values)
{
  if (!values) return;

  g_list_free_full(values->values, (GDestroyNotify) xmlFree);
  g_list_free_full(values->names, (GDestroyNotify) g_free);
  g_free(values->default_value);
  g_free(values->variable_name);
  g_free(values);
}


void
gnac_profiles_xml_engine_free_slider_values(SliderValues *values)
{
  if (!values) return;

  g_free(values->variable_name);
  g_free(values);
}


void
gnac_profiles_xml_engine_free_check_values(CheckValues *values)
{
  if (!values) return;

  g_free(values->variable_name);
  g_free(values);
}
