/* OGMRip - A library for DVD ripping and encoding
 * Copyright (C) 2004-2012 Olivier Rolland <billl@users.sourceforge.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/**
 * SECTION:ogmrip-settings
 * @title: Settings
 * @short_description: Common interface for settings managers
 * @include: ogmrip-settings.h
 */

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

#include "ogmrip-settings.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <glib/gi18n.h>

#include <libxml/parser.h>

#define OGMRIP_SETTINGS_PRIV "__ogmrip_settings_binding_priv__"

typedef struct
{
  GSList *bindings;
  GParamSpecPool *pool;
} OGMRipSettingsPriv;

typedef struct
{
  OGMRipSettingsPriv *priv;

  OGMRipSettings *settings;
  GObject *object;

  OGMRipSetFunc set_func;
  OGMRipGetFunc get_func;
  gpointer data;

  gchar *property;
  gchar *section;
  gchar *key;

  GType type;

  gulong signal_handler;
  gulong notify_handler;
  gboolean blocked;
} OGMRipBinding;

static OGMRipSettings *default_settings;

/*
 * Binding
 */

static void
ogmrip_binding_disconnect_cb (OGMRipBinding *binding)
{
  binding->signal_handler = 0;
}

static void
ogmrip_binding_property_notify_cb (OGMRipBinding *binding)
{
  if (!binding->blocked)
  {
    GValue value = {0};

    g_value_init (&value, binding->type);
    (* binding->get_func) (binding->object, binding->property, &value, binding->data);
    ogmrip_settings_set_value (binding->settings, binding->section, binding->key, &value);
    g_value_unset (&value);
  }
}

static void
ogmrip_binding_key_notify_cb (OGMRipSettings *settings, const gchar *section, const gchar *key,
    const GValue *value, OGMRipBinding *binding)
{
  if (!binding->blocked)
  {
    g_signal_handlers_block_by_func (binding->object, ogmrip_binding_property_notify_cb, binding);

    if (value && G_IS_VALUE (value))
      (* binding->set_func) (binding->object, binding->property, value, binding->data);
    else
    {
      GValue val = {0};

      ogmrip_settings_get_value (binding->settings, binding->section, binding->key, &val);
      if (G_IS_VALUE (&val))
      {
        (* binding->set_func) (binding->object, binding->property, &val, binding->data);
        g_value_unset (&val);
      }
    }

    g_signal_handlers_unblock_by_func (binding->object, ogmrip_binding_property_notify_cb, binding);
  }
}

static void
ogmrip_binding_remove (OGMRipBinding *binding)
{
  binding->priv->bindings = g_slist_remove (binding->priv->bindings, binding);

  if (binding->signal_handler)
    g_signal_handler_disconnect (binding->object, binding->signal_handler);

  g_free (binding->property);
  g_free (binding->section);
  g_free (binding->key);

  g_free (binding);
}

/*
 * Settings priv
 */

static void
ogmrip_binding_priv_free (OGMRipSettingsPriv *priv)
{
  while (priv->bindings)
    ogmrip_binding_remove (priv->bindings->data);

  g_free (priv);
}

static OGMRipSettingsPriv *
ogmrip_settings_get_priv (OGMRipSettings *settings)
{
  OGMRipSettingsPriv *priv;

  priv = g_object_get_data (G_OBJECT (settings), OGMRIP_SETTINGS_PRIV);
  if (!priv)
  {
    priv = g_new0 (OGMRipSettingsPriv, 1);

    g_object_set_data_full (G_OBJECT (settings), OGMRIP_SETTINGS_PRIV,
        priv, (GDestroyNotify) ogmrip_binding_priv_free);
  }

  return priv;
}

static GParamSpecPool *
ogmrip_settings_get_pool (OGMRipSettings *settings)
{
  OGMRipSettingsPriv *priv;

  priv = ogmrip_settings_get_priv (settings);
  if (!priv->pool)
    priv->pool = g_param_spec_pool_new (FALSE);

  return priv->pool;
}

/*
 * Settings
 */

static void ogmrip_settings_class_init (gpointer g_iface);

GType
ogmrip_settings_get_type (void)
{
  static GType settings_type = 0;

  if (!settings_type)
  {
    settings_type = g_type_register_static_simple (G_TYPE_INTERFACE,
        "OGMRipSettings",
        sizeof (OGMRipSettingsIface),
        (GClassInitFunc) ogmrip_settings_class_init,
        0, NULL, 0);

    g_type_interface_add_prerequisite (settings_type, G_TYPE_OBJECT);
  }

  return settings_type;
}

static void
g_value_transform_string_int (const GValue *src_value, GValue *dest_value)
{
  const gchar *str;

  str = g_value_get_string (src_value);
  g_value_set_int (dest_value, atoi (str));
}

static void
g_value_transform_int_string (const GValue *src_value, GValue *dest_value)
{
  gchar *str;

  str = g_strdup_printf ("%d", g_value_get_int (src_value));
  g_value_take_string (dest_value, str);
}

static void
g_value_transform_string_uint (const GValue *src_value, GValue *dest_value)
{
  const gchar *str;

  str = g_value_get_string (src_value);
  g_value_set_uint (dest_value, atoi (str));
}

static void
g_value_transform_uint_string (const GValue *src_value, GValue *dest_value)
{
  gchar *str;

  str = g_strdup_printf ("%u", g_value_get_uint (src_value));
  g_value_take_string (dest_value, str);
}

static void
g_value_transform_string_double (const GValue *src_value, GValue *dest_value)
{
  const gchar *str;

  str = g_value_get_string (src_value);
  g_value_set_double (dest_value, strtod (str, NULL));
}

static void
g_value_transform_double_string (const GValue *src_value, GValue *dest_value)
{
  gchar *str;

  str = g_strdup_printf ("%lf", g_value_get_double (src_value));
  g_value_take_string (dest_value, str);
}

static void
g_value_transform_string_boolean (const GValue *src_value, GValue *dest_value)
{
  const gchar *str;

  str = g_value_get_string (src_value);
  if (g_ascii_strcasecmp (str, "true") == 0)
    g_value_set_boolean (dest_value, TRUE);
  else if (g_ascii_strcasecmp (str, "false") == 0)
    g_value_set_boolean (dest_value, FALSE);
}

static void
g_value_transform_boolean_string (const GValue *src_value, GValue *dest_value)
{
  g_value_set_static_string (dest_value,
      g_value_get_boolean (src_value) ? "true" : "false");
}

static void
ogmrip_settings_install_key_internal (OGMRipSettings *settings, GParamSpec *pspec)
{
  GParamSpecPool *pool;

  pool = ogmrip_settings_get_pool (settings);

  g_param_spec_pool_insert (pool, pspec, OGMRIP_TYPE_SETTINGS);
}

static GType
ogmrip_settings_get_key_type_internal (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  GParamSpecPool *pool;
  GParamSpec *pspec;

  pool = ogmrip_settings_get_pool (settings);
  pspec = g_param_spec_pool_lookup (pool, key, OGMRIP_TYPE_SETTINGS, FALSE);
  if (!pspec)
    return G_TYPE_NONE;

  return pspec->value_type;
}

static void
ogmrip_settings_class_init (gpointer g_iface)
{
  OGMRipSettingsIface *iface = g_iface;

  iface->get_type = ogmrip_settings_get_key_type_internal;
  iface->install_key = ogmrip_settings_install_key_internal;

  g_value_register_transform_func (G_TYPE_STRING, G_TYPE_INT, g_value_transform_string_int);
  g_value_register_transform_func (G_TYPE_INT, G_TYPE_STRING, g_value_transform_int_string);

  g_value_register_transform_func (G_TYPE_STRING, G_TYPE_UINT, g_value_transform_string_uint);
  g_value_register_transform_func (G_TYPE_UINT, G_TYPE_STRING, g_value_transform_uint_string);

  g_value_register_transform_func (G_TYPE_STRING, G_TYPE_DOUBLE, g_value_transform_string_double);
  g_value_register_transform_func (G_TYPE_DOUBLE, G_TYPE_STRING, g_value_transform_double_string);

  g_value_register_transform_func (G_TYPE_STRING, G_TYPE_BOOLEAN, g_value_transform_string_boolean);
  g_value_register_transform_func (G_TYPE_BOOLEAN, G_TYPE_STRING, g_value_transform_boolean_string);
}

/**
 * ogmrip_settings_get_default:
 *
 * Gets the default setting manager if it exists.
 *
 * Returns: the default #OGMRipSettings, or NULL
 */
OGMRipSettings *
ogmrip_settings_get_default (void)
{
  return default_settings;
}

/**
 * ogmrip_settings_set_default:
 * @settings: an #OGMRipSettings, or NULL
 *
 * Sets the default setting manager. If @settings is NULL, the current default
 * setting manager is removed.
 */
void
ogmrip_settings_set_default (OGMRipSettings *settings)
{
  g_return_if_fail (settings == NULL || OGMRIP_IS_SETTINGS (settings));

  if (default_settings)
    g_object_unref (default_settings);

  if (settings)
    g_object_ref (settings);

  default_settings = settings;
}

/**
 * ogmrip_settings_install_key:
 * @settings: an #OGMRipSettings
 * @pspec: a #GParamSpec
 *
 * Installs a new key.
 */
void
ogmrip_settings_install_key (OGMRipSettings *settings, GParamSpec *pspec)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_PARAM_SPEC (pspec));

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->install_key)
    (* iface->install_key) (settings, pspec);
}

/**
 * ogmrip_settings_find_key:
 * @settings: an #OGMRipSettings
 * @key: the name of the key to look up
 *
 * Looks up the GParamSpec for a key.
 *
 * Returns: the GParamSpec for the key, or NULL
 */
GParamSpec *
ogmrip_settings_find_key (OGMRipSettings *settings, const gchar *key)
{
  GParamSpecPool *pool;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), NULL);
  g_return_val_if_fail (key != NULL, NULL);

  pool = ogmrip_settings_get_pool (settings);

  return g_param_spec_pool_lookup (pool, key, OGMRIP_TYPE_SETTINGS, FALSE);
}

static GParamSpec *
g_param_spec_copy (const gchar *name, GParamSpec *pspec)
{
  GParamSpec *pspec_new = NULL;
  GType type;

  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), NULL);

  type = G_PARAM_SPEC_TYPE (pspec);
  if (type == G_TYPE_PARAM_BOOLEAN)
    pspec_new = g_param_spec_boolean (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_BOOLEAN (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_CHAR)
    pspec_new =  g_param_spec_char (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_CHAR (pspec)->minimum, G_PARAM_SPEC_CHAR (pspec)->maximum,
        G_PARAM_SPEC_CHAR (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_UCHAR)
    pspec_new =  g_param_spec_uchar (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_UCHAR (pspec)->minimum, G_PARAM_SPEC_UCHAR (pspec)->maximum,
        G_PARAM_SPEC_UCHAR (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_INT)
    pspec_new =  g_param_spec_int (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_INT (pspec)->minimum, G_PARAM_SPEC_INT (pspec)->maximum,
        G_PARAM_SPEC_INT (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_UINT)
    pspec_new =  g_param_spec_uint (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_UINT (pspec)->minimum, G_PARAM_SPEC_UINT (pspec)->maximum,
        G_PARAM_SPEC_UINT (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_LONG)
    pspec_new =  g_param_spec_long (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_LONG (pspec)->minimum, G_PARAM_SPEC_LONG (pspec)->maximum,
        G_PARAM_SPEC_LONG (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_ULONG)
    pspec_new =  g_param_spec_ulong (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_ULONG (pspec)->minimum, G_PARAM_SPEC_ULONG (pspec)->maximum,
        G_PARAM_SPEC_ULONG (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_INT64)
    pspec_new =  g_param_spec_int64 (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_INT64 (pspec)->minimum, G_PARAM_SPEC_INT64 (pspec)->maximum,
        G_PARAM_SPEC_INT64 (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_UINT64)
    pspec_new =  g_param_spec_uint64 (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_UINT64 (pspec)->minimum, G_PARAM_SPEC_UINT64 (pspec)->maximum,
        G_PARAM_SPEC_UINT64 (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_FLOAT)
    pspec_new = g_param_spec_float (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_FLOAT (pspec)->minimum, G_PARAM_SPEC_FLOAT (pspec)->maximum,
        G_PARAM_SPEC_FLOAT (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_DOUBLE)
    pspec_new = g_param_spec_double (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_DOUBLE (pspec)->minimum, G_PARAM_SPEC_DOUBLE (pspec)->maximum,
        G_PARAM_SPEC_DOUBLE (pspec)->default_value, pspec->flags);
  else if (type == G_TYPE_PARAM_STRING)
    pspec_new =  g_param_spec_string (name,
        g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec),
        G_PARAM_SPEC_STRING (pspec)->default_value, pspec->flags);
  else
  {
    g_message ("name: %s, type: %s", name, G_PARAM_SPEC_TYPE_NAME (pspec));
    g_assert_not_reached ();
  }

  return pspec_new;
}

/**
 * ogmrip_settings_get_key_type:
 * @settings: an #OGMRipSettings
 * @section: the section of the key
 * @key: the name of the key to fetch
 *
 * Gets the type of the setting named by @key in @section.
 *
 * Returns: the type of @key, or %G_TYPE_NONE
 */
GType
ogmrip_settings_get_key_type (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), G_TYPE_NONE);
  g_return_val_if_fail (section != NULL, G_TYPE_NONE);
  g_return_val_if_fail (key != NULL, G_TYPE_NONE);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->get_type)
    return G_TYPE_NONE;

  return (* iface->get_type) (settings, section, key);
}

/**
 * ogmrip_settings_get_value:
 * @settings: an #OGMRipSettings
 * @section: the section of the key
 * @key: the name of the key to fetch
 * @value: a #GValue of the correct type
 *
 * Gets the value associated with the setting named by @key in @section.
 */
void
ogmrip_settings_get_value (OGMRipSettings *settings, const gchar *section, const gchar *key, GValue *value)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (key != NULL);
  g_return_if_fail (section != NULL);
  g_return_if_fail (value != NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->get_value)
  {
    (* iface->get_value) (settings, section, key, value);

    if (!G_IS_VALUE (value))
    {
      OGMRipSettingsPriv *priv;

      priv = ogmrip_settings_get_priv (settings);
      if (priv->pool)
      {
        GParamSpec *pspec;

        pspec = g_param_spec_pool_lookup (priv->pool, key, OGMRIP_TYPE_SETTINGS, FALSE);
        if (pspec)
        {
          g_value_init (value, pspec->value_type);
          g_param_value_set_default (pspec, value);
        }
      }
    }

    if (!G_IS_VALUE (value))
      g_warning ("Cannot set key '%s': no value", key);
  }
}

/**
 * ogmrip_settings_set_value:
 * @settings: an #OGMRipSettings
 * @section: the section of the key
 * @key: the name of the key to fetch
 * @value: a #GValue of the correct type
 *
 * Sets the setting named by @key in @section to @value.
 */
void
ogmrip_settings_set_value (OGMRipSettings *settings, const gchar *section, const gchar *key, const GValue *value)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (section != NULL);
  g_return_if_fail (key != NULL);
  g_return_if_fail (value != NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->set_value)
  {
    GValue dest_value = {0};
    GType type;

    type = ogmrip_settings_get_key_type (settings, section, key);
    if (G_TYPE_IS_VALUE (type))
    {
      g_value_init (&dest_value, type);

      if (type == G_VALUE_TYPE (value) || g_value_type_compatible (G_VALUE_TYPE (value), type))
        g_value_copy (value, &dest_value);
      else if (g_value_type_transformable (G_VALUE_TYPE (value), type))
        g_value_transform (value, &dest_value);
      else
        g_warning ("Cannot set key '%s': incompatible type", key);
    }

    if (G_IS_VALUE (&dest_value))
    {
      OGMRipSettingsPriv *priv;

      priv = ogmrip_settings_get_priv (settings);
      if (priv->pool)
      {
        GParamSpec *pspec;

        pspec = g_param_spec_pool_lookup (priv->pool, key, OGMRIP_TYPE_SETTINGS, FALSE);
        if (pspec)
          g_param_value_validate (pspec, &dest_value);
      }

      (* iface->set_value) (settings, section, key, &dest_value);
    }
  }
}

static void
ogmrip_settings_get_valist (OGMRipSettings *settings, const gchar *section, const gchar *key, va_list var_args)
{
  gpointer data;

  while (key)
  {
    GValue value = {0};

    ogmrip_settings_get_value (settings, section, key, &value);
    data = va_arg (var_args, gpointer);

    if (G_IS_VALUE (&value))
    {
      switch (G_VALUE_TYPE (&value))
      {
        case G_TYPE_INT:
          {
            gint *i = data;
            *i = g_value_get_int (&value);
          }
          break;
        case G_TYPE_UINT:
          {
            guint *i = data;
            *i = g_value_get_uint (&value);
          }
          break;
        case G_TYPE_BOOLEAN:
          {
            gboolean *b = data;
            *b = g_value_get_boolean (&value);
          }
          break;
        case G_TYPE_DOUBLE:
          {
            gdouble *d = data;
            *d = g_value_get_double (&value);
          }
          break;
        case G_TYPE_STRING:
          {
            gchar **str = data;
            *str = g_value_dup_string (&value);
          }
          break;
        default:
          break;
      }

      g_value_unset (&value);
    }

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

static void
ogmrip_settings_set_valist (OGMRipSettings *settings, const gchar *section, const gchar *key, va_list var_args)
{
  GType type;

  while (key)
  {
    GValue value = {0};

    type = ogmrip_settings_get_key_type (settings, section, key);
    g_value_init (&value, type);

    switch (type)
    {
      case G_TYPE_INT:
        g_value_set_int (&value, va_arg (var_args, gint));
        break;
      case G_TYPE_BOOLEAN:
        g_value_set_boolean (&value, va_arg (var_args, gboolean));
        break;
      case G_TYPE_DOUBLE:
        g_value_set_double (&value, va_arg (var_args, gdouble));
        break;
      case G_TYPE_STRING:
        {
          gchar *str;

          str = va_arg (var_args, gchar *);
          g_value_set_string (&value, str);
        }
        break;
      default:
        type = G_TYPE_NONE;
        break;
    }

    if (type != G_TYPE_NONE)
      ogmrip_settings_set_value (settings, section, key, &value);

    g_value_unset (&value);

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

/**
 * ogmrip_settings_get:
 * @settings: an #OGMRipSettings
 * @section: the section of the keys
 * @key: the name of the first key to fetch
 * @...: pointers to the locations to store the value of the first key, followed
 * by more name/pointer groupings, followed by %NULL.
 *
 * Gets the values associated with any number of settings in the same section.
 */
void
ogmrip_settings_get (OGMRipSettings *settings, const gchar *section, const gchar *key, ...)
{
  va_list var_args;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (section != NULL);

  va_start (var_args, key);
  ogmrip_settings_get_valist (settings, section, key, var_args);
  va_end (var_args);
}

/**
 * ogmrip_settings_set:
 * @settings: an #OGMRipSettings
 * @section: the section of the keys
 * @key: the name of the first key to set
 * @...: pointers to the value of the first key, followed by more name/pointer
 * groupings, followed by %NULL.
 *
 * Sets the values associated with any number of settings in the same section.
 */
void
ogmrip_settings_set (OGMRipSettings *settings, const gchar *section, const gchar *key, ...)
{
  va_list var_args;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (section != NULL);

  va_start (var_args, key);
  ogmrip_settings_set_valist (settings, section, key, var_args);
  va_end (var_args);
}

/**
 * ogmrip_settings_sync:
 * @settings: an #OGMRipSettings
 *
 * Blah
 */
void
ogmrip_settings_sync (OGMRipSettings *settings)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->sync)
    (* iface->sync) (settings);
}

/**
 * ogmrip_settings_build_section:
 * @settings: an #OGMRipSettings
 * @element: the first section element
 * @...: more section elements
 *
 * Builds a section from many section elements.
 *
 * Returns: the new section
 */
gchar *
ogmrip_settings_build_section (OGMRipSettings *settings, const gchar *element, ...)
{
  OGMRipSettingsIface *iface;
  va_list var_args;
  gchar *section;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->build_section)
    return NULL;

  va_start (var_args, element);
  section = (* iface->build_section) (settings, element, var_args);
  va_end (var_args);

  return section;
}

/**
 * ogmrip_settings_get_section_name:
 * @settings: an #OGMRipSettings
 * @section: a section
 *
 * Gets the name of the section.
 *
 * Returns: the name of the section
 */
const gchar *
ogmrip_settings_get_section_name (OGMRipSettings *settings, const gchar *section)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->get_section_name)
    return NULL;

  return (* iface->get_section_name) (settings, section);
}

/**
 * ogmrip_settings_get_subsections:
 * @settings: an #OGMRipSettings
 * @section: the section from which to get the subsections
 *
 * Lists the subsections in @section. The returned list contains allocated
 * strings. You should g_free() each string in the list, then g_slist_free() the
 * list itself.
 *
 * Returns: List of allocated subsection names
 */
GSList *
ogmrip_settings_get_subsections (OGMRipSettings *settings, const gchar *section)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->get_subsections)
    return NULL;

  return (* iface->get_subsections) (settings, section);;
}

/**
 * ogmrip_settings_get_keys:
 * @settings: an #OGMRipSettings
 * @section: the section from which to get the keys
 * @recursive: perform a recursive search
 *
 * Lists the keys in @section. The returned list contains allocated
 * strings. You should g_free() each string in the list, then g_slist_free() the
 * list itself.
 *
 * Returns: List of allocated key names
 */
GSList *
ogmrip_settings_get_keys (OGMRipSettings *settings, const gchar *section, gboolean recursive)
{
  GSList *list;
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->get_keys)
    return NULL;

  list = (* iface->get_keys) (settings, section, recursive);;
  list = g_slist_sort (list, (GCompareFunc) strcmp);

  return list;
}

/**
 * ogmrip_settings_remove_key:
 * @settings: an #OGMRipSettings
 * @section: a section
 * @key: the key to remove
 *
 * Removeѕ @key from @section.
 */
void
ogmrip_settings_remove_key (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (section != NULL);
  g_return_if_fail (key != NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->remove_key)
    (* iface->remove_key) (settings, section, key);
}

/**
 * ogmrip_settings_remove_section:
 * @settings: an #OGMRipSettings
 * @section: the section to remove
 *
 * Removeѕ @section and all its keys and subsections.
 */
void
ogmrip_settings_remove_section (OGMRipSettings *settings, const gchar *section)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (section != NULL);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->remove_section)
    (* iface->remove_section) (settings, section);
}

/**
 * ogmrip_settings_has_key:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 *
 * Returns whether a key exists or not.
 *
 * Returns: %TRUE if @key exists, %FALSE otherwise
 */
gboolean
ogmrip_settings_has_key (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), FALSE);
  g_return_val_if_fail (section != NULL, FALSE);
  g_return_val_if_fail (key != NULL, FALSE);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->has_key)
    return FALSE;

  return (* iface->has_key) (settings, section, key);
}

/**
 * ogmrip_settings_has_section:
 * @settings: an #OGMRipSettings
 * @section: the section
 *
 * Returns whether a section exists or not.
 *
 * Returns: %TRUE if @section exists, %FALSE otherwise
 */
gboolean
ogmrip_settings_has_section (OGMRipSettings *settings, const gchar *section)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), FALSE);
  g_return_val_if_fail (section != NULL, FALSE);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->has_section)
    return FALSE;

  return (* iface->has_section) (settings, section);
}

/**
 * ogmrip_settings_add_notify:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 * @func: function to call when changes occur
 * @data: user data to pass to @func
 *
 * Request notification of changes of @key in @section.
 *
 * Returns: a connection ID for removing the notification
 */
gulong
ogmrip_settings_add_notify (OGMRipSettings *settings, const gchar *section, const gchar *key, OGMRipNotifyFunc func, gpointer data)
{
  OGMRipSettingsIface *iface;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), 0);
  g_return_val_if_fail (section != NULL, 0);
  g_return_val_if_fail (key != NULL, 0);
  g_return_val_if_fail (func != NULL, 0);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (!iface->add_notify)
    return 0;

  return (* iface->add_notify) (settings, section, key, func, data);
}

typedef struct
{
  OGMRipSettings *settings;
  guint handler_id;
} OGMRipDisconnector;

static void
ogmrip_settings_notify_disconnector (gpointer data, GObject *gobject)
{
  OGMRipDisconnector *disconnector = data;

  ogmrip_settings_remove_notify (disconnector->settings, disconnector->handler_id);

  g_free (disconnector);
}

/**
 * ogmrip_settings_add_notify_while_alive:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 * @func: function to call when changes occur
 * @data: user data to pass to @func
 * @object: a #GObject
 *
 * Request notification of changes of @key in @section. When @object is destroyed,
 * the notification is automatically removed.
 *
 * Returns: a connection ID for removing the notification
 */
gulong
ogmrip_settings_add_notify_while_alive (OGMRipSettings *settings, const gchar *section, const gchar *key,
    OGMRipNotifyFunc func, gpointer data, GObject *object)
{
  OGMRipDisconnector *disconnector;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), 0);
  g_return_val_if_fail (G_IS_OBJECT (object), 0);
  g_return_val_if_fail (func != NULL, 0);
  g_return_val_if_fail (section != NULL, 0);
  g_return_val_if_fail (key != NULL, 0);

  disconnector = g_new0 (OGMRipDisconnector, 1);

  disconnector->settings = settings;
  disconnector->handler_id = ogmrip_settings_add_notify (settings, section, key, func, data);

  g_object_weak_ref (object, ogmrip_settings_notify_disconnector, disconnector);

  return disconnector->handler_id;
}

/**
 * ogmrip_settings_remove_notify:
 * @settings: an #OGMRipSettings
 * @handler_id: a connection ID
 *
 * Remove a notification using the ID returned from ogmrip_settings_add_notify()
 * or ogmrip_settings_add_notify_while_alive().
 */
void
ogmrip_settings_remove_notify (OGMRipSettings *settings, gulong handler_id)
{
  OGMRipSettingsIface *iface;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (handler_id != 0);

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  if (iface->remove_notify)
    (* iface->remove_notify) (settings, handler_id);
}

/**
 * ogmrip_settings_bind_custom:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 * @object: a #GObject
 * @property: a property of @object
 * @get_func: function called whenever @property changes setting a custom value to @key 
 * @set_func: function called whenever @key changes settings a custom value to @object
 * @data: user data to pass to @get_func and @set_func
 *
 * Binds @key in @section with @property of @object. Whenever @property changes,
 * @key is updated. Whenever @key changeѕ, @property is updated.
 */
void
ogmrip_settings_bind_custom (OGMRipSettings *settings, const gchar *section, const gchar *key,
    GObject *object, const gchar *property, OGMRipGetFunc get_func, OGMRipSetFunc set_func, gpointer data)
{
  OGMRipBinding *binding;
  gchar *signame;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_OBJECT (object));

  g_return_if_fail (section != NULL);
  g_return_if_fail (key != NULL);
  g_return_if_fail (property != NULL);
  g_return_if_fail (get_func != NULL);
  g_return_if_fail (set_func != NULL);

  binding = g_new0 (OGMRipBinding, 1);

  binding->key = g_strdup (key);
  binding->section = g_strdup (section);
  binding->property = g_strdup (property);
  binding->settings = settings;
  binding->object = object;

  binding->get_func = get_func;
  binding->set_func = set_func;
  binding->data = data;

  binding->type = ogmrip_settings_get_key_type (settings, section, key);

  g_object_weak_ref (object, (GWeakNotify) ogmrip_binding_remove, binding);

  binding->priv = ogmrip_settings_get_priv (settings);
  binding->priv->bindings = g_slist_prepend (binding->priv->bindings, binding);

  binding->notify_handler = ogmrip_settings_add_notify_while_alive (settings, section, key,
      (OGMRipNotifyFunc) ogmrip_binding_key_notify_cb, binding, object);

  signame = g_strdup_printf ("notify::%s", property);
  binding->signal_handler = g_signal_connect_data (object, signame,
      G_CALLBACK (ogmrip_binding_property_notify_cb), binding,
      (GClosureNotify) ogmrip_binding_disconnect_cb, G_CONNECT_SWAPPED); 
  g_free (signame);

  ogmrip_binding_key_notify_cb (settings, section, key, NULL, binding);
}

/**
 * ogmrip_settings_bind:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 * @object: a #GObject
 * @property: a property of @object
 *
 * Binds @key in @section with @property of @object. Whenever @property changes,
 * @key is updated. Whenever @key changeѕ, @property is updated.
 */
void
ogmrip_settings_bind (OGMRipSettings *settings, const gchar *section, const gchar *key, GObject *object, const gchar *property)
{
  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_OBJECT (object));

  g_return_if_fail (key != NULL);
  g_return_if_fail (property != NULL);

  ogmrip_settings_bind_custom (settings, section, key, object, property,
      (OGMRipGetFunc) g_object_get_property,
      (OGMRipSetFunc) g_object_set_property,
      NULL);
}

/**
 * ogmrip_settings_unbind:
 * @settings: an #OGMRipSettings
 * @object: a #GObject
 *
 * Removes the bindings associated to @object.
 */
void
ogmrip_settings_unbind (OGMRipSettings *settings, GObject *object)
{
  OGMRipSettingsIface *iface;
  OGMRipSettingsPriv *priv;
  OGMRipBinding *binding;
  GSList *link;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_OBJECT (object));

  iface = OGMRIP_SETTINGS_GET_IFACE (settings);

  priv = ogmrip_settings_get_priv (settings);

  link = priv->bindings;
  while (link)
  {
    binding = link->data;
    link = link->next;

    if (binding->object == object)
    {
      if (iface->remove_notify)
        (* iface->remove_notify) (settings, binding->notify_handler);

      g_object_weak_unref (binding->object,
          (GWeakNotify) ogmrip_binding_remove, binding);
      ogmrip_binding_remove (binding);
    }
  }
}

/**
 * ogmrip_settings_block:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 *
 * Blocks all notifications related to @key in @section. If @section is NULL, notifications
 * related to @key from all sections are blocked.
 */
void
ogmrip_settings_block (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipSettingsPriv *priv;
  OGMRipBinding *binding;
  GSList *link;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (key != NULL);

  priv = ogmrip_settings_get_priv (settings);

  link = priv->bindings;
  while (link)
  {
    binding = link->data;
    link = link->next;

    if ((!section || g_str_equal (section, binding->section)) && g_str_equal (key, binding->key))
    {
      binding->blocked = TRUE;
      break;
    }
  }
}

/**
 * ogmrip_settings_unblock:
 * @settings: an #OGMRipSettings
 * @section: the section
 * @key: the key
 *
 * Unblocks all notifications related to @key in @section. If @section is NULL, notifications
 * related to @key from all sections are unblocked.
 */
void
ogmrip_settings_unblock (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipSettingsPriv *priv;
  OGMRipBinding *binding;
  GSList *link;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (key != NULL);

  priv = ogmrip_settings_get_priv (settings);

  link = priv->bindings;
  while (link)
  {
    binding = link->data;
    link = link->next;

    if ((!section || g_str_equal (section, binding->section)) && g_str_equal (key, binding->key))
    {
      binding->blocked = FALSE;
      break;
    }
  }
}

/**
 * ogmrip_settings_install_key_from_property:
 * @settings: An #OGMRipSettings
 * @klass: A #GObjectClass
 * @section: A section
 * @key: A key
 * @property: A property
 *
 * Installs a new key using the GParamSpec of @property.
 */
void
ogmrip_settings_install_key_from_property (OGMRipSettings *settings, GObjectClass *klass, const gchar *section, const gchar *key, const gchar *property)
{
  GParamSpec *pspec;

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_OBJECT_CLASS (klass));
  g_return_if_fail (key != NULL);
  g_return_if_fail (property != NULL);

  pspec = g_object_class_find_property (klass, property);
  if (pspec)
  {
    gchar *full_key;

    if (section)
      full_key = ogmrip_settings_build_section (settings, section, key, NULL);
    else
      full_key = g_strdup (key);

    ogmrip_settings_install_key (settings, g_param_spec_copy (full_key, pspec));

    g_free (full_key);
  }
}

/**
 * ogmrip_settings_set_property_from_key:
 * @settings: An #OGMRipSettings
 * @object: A #GObject
 * @property: Name of the property to set
 * @section: Section of a key
 * @key: Name of a key
 *
 * Sets a property of an object using the value of a settings key.
 */
void
ogmrip_settings_set_property_from_key (OGMRipSettings *settings, GObject *object, const gchar *property, const gchar *section, const gchar *key)
{
  GValue value = {0};

  g_return_if_fail (OGMRIP_IS_SETTINGS (settings));
  g_return_if_fail (G_IS_OBJECT (object));
  g_return_if_fail (section != NULL);
  g_return_if_fail (property != NULL);
  g_return_if_fail (key != NULL);

  ogmrip_settings_get_value (settings, section, key, &value);

  g_object_set_property (object, property, &value);
  g_value_unset (&value);
}

static gchar *
g_value_to_string (const GValue *value)
{
  gchar *str = NULL;

  if (g_value_type_compatible (value->g_type, G_TYPE_STRING))
    str = g_value_dup_string (value);
  else if (g_value_type_transformable (value->g_type, G_TYPE_STRING))
  {
    GValue dst_value = {0};

    g_value_init (&dst_value, G_TYPE_STRING);
    g_value_transform (value, &dst_value);
    str = g_value_dup_string (&dst_value);
    g_value_unset (&dst_value);
  }

  return str;
}

typedef struct
{
  OGMRipSettings *settings;
  const gchar *section;
  FILE *f;
} DumpData;

static void
ogmrip_settings_dump_entry (gchar *key, DumpData *data)
{
  if (!g_str_equal (key, "version"))
  {
    GValue value = {0};
    gchar *str;

    ogmrip_settings_get_value (data->settings, data->section, key, &value);

    if (G_IS_VALUE (&value))
    {
      fprintf (data->f, "    <entry>\n");
      fprintf (data->f, "      <key>%s</key>\n", key);
      fprintf (data->f, "      <value>\n");

      switch (value.g_type)
      {
        case G_TYPE_INT:
        case G_TYPE_UINT:
        case G_TYPE_LONG:
        case G_TYPE_ULONG:
        case G_TYPE_INT64:
        case G_TYPE_UINT64:
          str = g_value_to_string (&value);
          fprintf (data->f, "        <int>%s</int>\n", str);
          g_free (str);
          break;
        case G_TYPE_FLOAT:
        case G_TYPE_DOUBLE:
          str = g_value_to_string (&value);
          fprintf (data->f, "        <double>%s</double>\n", str);
          g_free (str);
          break;
        case G_TYPE_STRING:
          str = g_markup_escape_text (g_value_get_string (&value), -1);
          fprintf (data->f, "        <string>%s</string>\n", (str[0] == ' ' && str[1] == '\0') ? "" : str);
          g_free (str);
          break;
        case G_TYPE_BOOLEAN:
          str = g_value_to_string (&value);
          fprintf (data->f, "        <bool>%s</bool>\n", str);
          g_free (str);
          break;
        default:
          g_message ("%s", g_type_name (value.g_type));
          g_assert_not_reached();
          break;
      }

      fprintf (data->f, "      </value>\n");
      fprintf (data->f, "    </entry>\n");
    }
  }

  g_free (key);
}

/**
 * ogmrip_settings_export:
 * @settings: An #OGMRipSettings
 * @filename: A filename to export into
 * @section: The section to export
 * @error: Return location for error
 *
 * Exports settings from @section in @filename.
 *
 * Returns: %TRUE if @section has been exported, %FALSE otherwise
 */
gboolean
ogmrip_settings_export (OGMRipSettings *settings, const gchar *section, const gchar *filename, GError **error)
{
  DumpData data;
  GSList *keys;

  gchar *version;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), FALSE);
  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (section != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  data.section = section;
  data.settings = settings;
  data.f = fopen (filename, "w");
  if (!data.f)
    return FALSE;

  fprintf (data.f, "<ogmrip>\n");

  ogmrip_settings_get (settings, section, "version", &version, NULL);
  if (version)
  {
    fprintf (data.f, "  <profile base=\"%s\" version=\"%s\">\n", section, version);
    g_free (version);
  }
  else
    fprintf (data.f, "  <profile base=\"%s\">\n", section);

  keys = ogmrip_settings_get_keys (settings, section, TRUE);
  g_slist_foreach (keys, (GFunc) ogmrip_settings_dump_entry, &data);
  g_slist_free (keys);

  fprintf (data.f, "  </profile>\n");
  fprintf (data.f, "</ogmrip>\n");

  fclose (data.f);

  return TRUE;
}

gboolean
xmlNodeCheckLang (xmlNode *node)
{
  const gchar * const * languages;

  xmlChar *lang;
  guint i;

  lang = xmlNodeGetLang (node);
  if (!lang)
    return FALSE;

  languages = g_get_language_names ();

  for (i = 0; languages[i]; i++)
    if (xmlStrEqual ((xmlChar *) languages[i], lang))
      break;

  xmlFree (lang);

  return languages[i] != NULL;
}

static void
xmlNodeContentToValue (xmlNode *node, GValue *value)
{
  xmlNode *iter;
  gchar *content = NULL;

  for (iter = node->children; iter; iter = iter->next)
    if (iter->type == XML_ELEMENT_NODE)
      break;

  if (!iter)
    return;

  if (g_str_equal (iter->name, "string"))
  {
    xmlNode *iter2;

    for (iter2 = iter; iter2; iter2 = iter2->next)
      if (xmlNodeCheckLang (iter2))
        break;

    if (iter2)
      iter = iter2;
  }

  content = (gchar *) xmlNodeGetContent (iter);
  if (content)
  {
    g_value_init (value, G_TYPE_STRING);
    g_value_take_string (value, content);
  }
}

static void
ogmrip_settings_parse_entry (OGMRipSettings *settings, xmlNode *node, const xmlChar *section)
{
  xmlNode *iter;
  GValue value = {0};
  gchar *key = NULL;

  for (iter = node->children; iter; iter = iter->next)
  {
    if (g_str_equal ((char *) iter->name, "key"))
      key = (char *) xmlNodeGetContent (iter);
    else if (g_str_equal ((char *) iter->name, "value"))
      xmlNodeContentToValue (iter, &value);
  }

  if (key && G_IS_VALUE (&value))
    ogmrip_settings_set_value (settings, (const gchar *) section, key, &value);

  if (G_IS_VALUE (&value))
    g_value_unset (&value);

  if (key)
    xmlFree (key);
}

static void
ogmrip_settings_parse_section (OGMRipSettings *settings, xmlNode *root, const xmlChar *section)
{
  xmlNode *iter;

  for (iter = root->children; iter; iter = iter->next)
    if (iter->type == XML_ELEMENT_NODE && g_str_equal ((char *) iter->name, "entry"))
      ogmrip_settings_parse_entry (settings, iter, section);

  ogmrip_settings_sync (settings);
}

/**
 * ogmrip_settings_compare_versions:
 * @version1: A profile's version
 * @version2: Another profile's version
 *
 * Compares the versions of two profiles.
 *
 * Returns: Negative value if @version1 < @version2; zero if @version1 = @version2;
 * positive value if @version1 > @version2
 */
gint
ogmrip_settings_compare_versions (const gchar *version1, const gchar *version2)
{
  gint major1 = 0, minor1 = 0, major2 = 0, minor2 = 0;
  gchar *end;

  errno = 0;

  if (version1)
  {
    major1 = strtoul (version1, &end, 10);
    if (!errno && *end == '.')
      minor1 = strtoul (end + 1, NULL, 10);
  }

  if (version2)
  {
    major2 = strtoul (version2, &end, 10);
    if (!errno && *end == '.')
      minor2 = strtoul (end + 1, NULL, 10);
  }

  if (major1 == major2)
    return minor1 - minor2;

  return major1 - major2;
}

/**
 * ogmrip_settings_import:
 * @settings: An #OGMRipSettings
 * @filename: A filename to import from
 * @section: The section in which to import
 * @error: Return location for error
 *
 * Imports settings from @filename in @section.
 *
 * Returns: %TRUE if @filename has been imported, %FALSE otherwise
 */
gboolean
ogmrip_settings_import (OGMRipSettings *settings, const gchar *filename, gchar **section, GError **error)
{
  gchar *old_version = NULL;
  xmlChar *base, *new_version;
  xmlDocPtr doc;
  xmlNode *iter;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), FALSE);
  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  doc = xmlParseFile (filename);
  if (!doc)
  {
    g_set_error (error, 0, 0, _("Failed to open '%s'"), filename);
    return FALSE;
  }

  iter = xmlDocGetRootElement (doc);

  if (!iter)
  {
    g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
    xmlFreeDoc (doc);
    return FALSE;
  }

  while (iter != NULL)
  {
    if (iter->type == XML_ELEMENT_NODE)
    {
      if (!g_str_equal((char *)iter->name, "ogmrip"))
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }
      else
        break;
    }

    iter = iter->next;
  }

  if (!iter)
  {
    g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
    xmlFreeDoc (doc);
    return FALSE;
  }

  if (!ogmrip_settings_find_key (settings, "version"))
    ogmrip_settings_install_key (settings,
        g_param_spec_string ("version", NULL, NULL, NULL, G_PARAM_READWRITE));

  iter = iter->children;

  while (iter)
  {
    if (iter->type == XML_ELEMENT_NODE)
    {
      if (!g_str_equal ((char *) iter->name, "profile"))
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }

      base = xmlGetProp (iter, (xmlChar *) "base");
      if (!base)
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }

      if (section)
        *section = g_strdup ((gchar *) base);

      ogmrip_settings_get (settings, (gchar *) base, "version", &old_version, NULL);

      new_version = xmlGetProp (iter, (xmlChar *) "version");

      if (!ogmrip_settings_has_section (settings, (gchar *) base) ||
          ogmrip_settings_compare_versions ((gchar *) new_version, old_version) > 0)
      {
        if (new_version)
          ogmrip_settings_set (settings, (gchar *) base, "version", new_version, NULL);

        ogmrip_settings_parse_section (settings, iter, base);
      }

      if (new_version)
        xmlFree (new_version);
      new_version = NULL;

      if (old_version)
        g_free (old_version);
      old_version = NULL;

      xmlFree (base);
    }
    iter = iter->next;
  }

  xmlFreeDoc (doc);

  return TRUE;
}

static void
ogmrip_settings_foreach (xmlNode *root, OGMRipParseFunc func, gpointer user_data)
{
  xmlNode *iter;

  for (iter = root->children; iter; iter = iter->next)
  {
    if (!(* func) (iter, user_data))
      break;

    if (iter->children)
      ogmrip_settings_foreach (iter, func, user_data);
  }
}

/**
 * ogmrip_settings_parse:
 * @settings: An #OGMRipSettings
 * @filename: A filename to parse
 * @func: The function to call for each entries
 * @user_data: User data passed to the function
 * @error: Return location for error
 *
 * Parses the settings in @filename, calling @func for each entries.
 *
 * Returns: %TRUE on success, %FALSE if an error was set
 *
 */
gboolean
ogmrip_settings_parse (OGMRipSettings *settings, const gchar *filename, OGMRipParseFunc func, gpointer user_data, GError **error)
{
  xmlChar *base;
  xmlDocPtr doc;
  xmlNode *iter;

  g_return_val_if_fail (OGMRIP_IS_SETTINGS (settings), FALSE);
  g_return_val_if_fail (filename != NULL, FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  doc = xmlParseFile (filename);
  if (!doc)
  {
    g_set_error (error, 0, 0, _("Failed to open '%s'"), filename);
    return FALSE;
  }

  iter = xmlDocGetRootElement (doc);

  if (!iter)
  {
    g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
    xmlFreeDoc (doc);
    return FALSE;
  }

  while (iter != NULL)
  {
    if (iter->type == XML_ELEMENT_NODE)
    {
      if (!g_str_equal((char *)iter->name, "ogmrip"))
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }
      else
        break;
    }

    iter = iter->next;
  }

  if (!iter)
  {
    g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
    xmlFreeDoc (doc);
    return FALSE;
  }

  if (!ogmrip_settings_find_key (settings, "version"))
    ogmrip_settings_install_key (settings,
        g_param_spec_string ("version", NULL, NULL, NULL, G_PARAM_READWRITE));

  iter = iter->children;

  while (iter)
  {
    if (iter->type == XML_ELEMENT_NODE)
    {
      if (!g_str_equal ((char *) iter->name, "profile"))
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }

      base = xmlGetProp (iter, (xmlChar *) "base");
      if (!base)
      {
        g_set_error (error, 0, 0, _("'%s' does not contain a valid profile"), filename);
        xmlFreeDoc (doc);
        return FALSE;
      }
      xmlFree (base);

      (* func) (iter, user_data);

      if (iter->children)
        ogmrip_settings_foreach (iter, func, user_data);
    }
    iter = iter->next;
  }

  xmlFreeDoc (doc);

  return TRUE;
}

