/* 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
 */

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

/**
 * SECTION:ogmrip-gconf-settings
 * @title: OGMRipGConfSettings
 * @include: ogmrip-gconf-settings.h
 * @short_description: GConf settings manager
 */

#include "ogmrip-gconf-settings.h"

#include <gconf/gconf-client.h>

#include <string.h>

#define OGMRIP_GCONF_SETTINGS_GET_PRIVATE(o) \
    (G_TYPE_INSTANCE_GET_PRIVATE ((o), OGMRIP_TYPE_GCONF_SETTINGS, OGMRipGConfSettingsPriv))

struct _OGMRipGConfSettingsPriv
{
  GConfClient *client;
  gchar *basedir;
};

static void ogmrip_settings_init     (OGMRipSettingsIface *iface);
static void ogmrip_settings_dispose  (GObject             *gobject);
static void ogmrip_settings_finalize (GObject             *gobject);

gchar*        
my_gconf_concat_dir_and_key (const gchar* dir, const gchar* key)
{
  guint dirlen;
  guint keylen;
  gchar* retval;

  g_return_val_if_fail(dir != NULL, NULL);
  g_return_val_if_fail(key != NULL, NULL);

  dirlen = strlen (dir);
  keylen = strlen (key);

  retval = g_malloc0 (dirlen + keylen + 3); /* auto-null-terminate */

  strcpy (retval, dir);

  if (dir[dirlen-1] == '/')
  {
    /* dir ends in slash, strip key slash if needed */
    if (*key == '/')
      ++key;

    strcpy (retval + dirlen, key);
  }
  else 
  {
    /* Dir doesn't end in slash, add slash if key lacks one. */
    gchar* dest = retval + dirlen;

    if (*key != '/')
    {
      *dest = '/';
      ++dest;
    }
      
    strcpy (dest, key);
  }
  
  return retval;
}

G_DEFINE_TYPE_WITH_CODE (OGMRipGConfSettings, ogmrip_gconf_settings, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (OGMRIP_TYPE_SETTINGS, ogmrip_settings_init))

static void
ogmrip_gconf_settings_class_init (OGMRipGConfSettingsClass *klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->dispose = ogmrip_settings_dispose;
  gobject_class->finalize = ogmrip_settings_finalize;

  g_type_class_add_private (klass, sizeof (OGMRipGConfSettingsPriv));
}

static void
ogmrip_settings_dispose (GObject *gobject)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (gobject);

  if (gconf->priv->client)
  {
    g_object_unref (gconf->priv->client);
    gconf->priv->client = NULL;
  }

  (*G_OBJECT_CLASS (ogmrip_gconf_settings_parent_class)->dispose) (gobject);
}

static void
ogmrip_settings_finalize (GObject *gobject)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (gobject);

  if (gconf->priv->basedir)
  {
    g_free (gconf->priv->basedir);
    gconf->priv->basedir = NULL;
  }

  (*G_OBJECT_CLASS (ogmrip_gconf_settings_parent_class)->finalize) (gobject);
}

static void
ogmrip_gconf_settings_set_value (OGMRipSettings *settings, const gchar *section, const gchar *key, const GValue *value)
{
  OGMRipGConfSettings *gconf;
  gchar *real_key;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  real_key = gconf_concat_dir_and_key (section, key);

  switch (G_VALUE_TYPE (value))
  {
    case G_TYPE_INT:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_int (value), NULL);
      break;
    case G_TYPE_UINT:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_uint (value), NULL);
      break;
    case G_TYPE_LONG:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_long (value), NULL);
      break;
    case G_TYPE_ULONG:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_ulong (value), NULL);
      break;
    case G_TYPE_INT64:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_int64 (value), NULL);
      break;
    case G_TYPE_UINT64:
      gconf_client_set_int (gconf->priv->client, real_key, g_value_get_uint64 (value), NULL);
      break;
    case G_TYPE_BOOLEAN:
      gconf_client_set_bool (gconf->priv->client, real_key, g_value_get_boolean (value), NULL);
      break;
    case G_TYPE_FLOAT:
      gconf_client_set_float (gconf->priv->client, real_key, g_value_get_float (value), NULL);
      break;
    case G_TYPE_DOUBLE:
      gconf_client_set_float (gconf->priv->client, real_key, g_value_get_double (value), NULL);
      break;
    case G_TYPE_STRING:
      gconf_client_set_string (gconf->priv->client, real_key, g_value_get_string (value), NULL);
      break;
    default:
      g_warning ("Cannot set key '%s': invalid type", key);
      break;
  }

  g_free (real_key);
}

static void
ogmrip_gconf_settings_get_value (OGMRipSettings *settings, const gchar *section, const gchar *key, GValue *value)
{
  OGMRipGConfSettings *gconf;
  GConfValue *gconf_value;
  GType type;

  gchar *real_key;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  real_key = gconf_concat_dir_and_key (section, key);
  gconf_value = gconf_client_get (gconf->priv->client, real_key, NULL);
  g_free (real_key);

  if (gconf_value)
  {
    type = ogmrip_settings_get_key_type (settings, section, key);
    if (type != G_TYPE_NONE)
    {
      g_value_init (value, type);

      switch (type)
      {
        case G_TYPE_INT:
          g_value_set_int (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_UINT:
          g_value_set_uint (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_LONG:
          g_value_set_long (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_ULONG:
          g_value_set_ulong (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_INT64:
          g_value_set_int64 (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_UINT64:
          g_value_set_uint64 (value, gconf_value_get_int (gconf_value));
          break;
        case G_TYPE_BOOLEAN:
          g_value_set_boolean (value, gconf_value_get_bool (gconf_value));
          break;
        case G_TYPE_FLOAT:
          g_value_set_float (value, gconf_value_get_float (gconf_value));
          break;
        case G_TYPE_DOUBLE:
          g_value_set_double (value, gconf_value_get_float (gconf_value));
          break;
        case G_TYPE_STRING:
          g_value_set_string (value, gconf_value_get_string (gconf_value));
          break;
        default:
          g_warning ("Cannot get key '%s': invalid type", key);
          break;
      }
    }
    gconf_value_free (gconf_value);
  }
}

static GSList *
ogmrip_gconf_settings_get_subsections (OGMRipSettings *settings, const gchar *section)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  return gconf_client_all_dirs (gconf->priv->client, section, NULL);
}

GSList *
ogmrip_gconf_settings_get_keys_internal (OGMRipGConfSettings *settings, GSList *keys, const gchar *base_section, const gchar *current_section, gboolean recursive)
{
  GSList *list, *link;
  GConfEntry *entry;
  gchar *name;
  gint len;

  len = strlen (base_section);

  list = gconf_client_all_entries (settings->priv->client, current_section, NULL);
  for (link = list; link; link = link->next)
  {
    entry = link->data;

    name = entry->key + len;
    if (*name == '/')
      name ++;

    keys = g_slist_append (keys, g_strdup (name));

    gconf_entry_unref (entry);
  }

  g_slist_free (list);

  if (recursive)
  {
    list = gconf_client_all_dirs (settings->priv->client, current_section, NULL);
    for (link = list; link; link = link->next)
      keys = ogmrip_gconf_settings_get_keys_internal (settings, keys, base_section, link->data, TRUE);
  }

  return keys;
}

static GSList *
ogmrip_gconf_settings_get_keys (OGMRipSettings *settings, const gchar *section, gboolean recursive)
{
  return ogmrip_gconf_settings_get_keys_internal (OGMRIP_GCONF_SETTINGS (settings), NULL, section, section, recursive);
}

static void
ogmrip_gconf_settings_remove_key (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipGConfSettings *gconf;
  const gchar *real_key;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  real_key = gconf_concat_dir_and_key (section, key);
  gconf_client_unset (gconf->priv->client, real_key, NULL);
  gconf_client_suggest_sync (gconf->priv->client, NULL);
}

static void
ogmrip_gconf_settings_remove_section (OGMRipSettings *settings, const gchar *section)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  gconf_client_recursive_unset (gconf->priv->client, section, 0, NULL);
  gconf_client_suggest_sync (gconf->priv->client, NULL);
}

typedef struct
{
  GConfClient *client;
  OGMRipSettings *settings;
  OGMRipNotifyFunc func;
  guint handler_id;
  gchar *section;
  gchar *key;
  gpointer data;
} OGMRipNotification;

static gboolean
ogmrip_gconf_settings_has_key (OGMRipSettings *settings, const gchar *section, const gchar *key)
{
  OGMRipGConfSettings *gconf;
  GSList *list, *link;
  GConfEntry *entry;
  gchar *full_key;
  gboolean found;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  full_key = gconf_concat_dir_and_key (section, key);

  list = gconf_client_all_entries (gconf->priv->client, section, NULL);
  for (found = FALSE, link = list; link; link = link->next)
  {
    entry = link->data;

    if (g_str_equal (entry->key, full_key))
      found = TRUE;

    gconf_entry_unref (entry);
  }
  g_slist_free (list);

  return found;
}

static gboolean
ogmrip_gconf_settings_has_section (OGMRipSettings *settings, const gchar *section)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  return gconf_client_dir_exists (gconf->priv->client, section, NULL);
}

static void
ogmrip_gconf_settings_notify_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, OGMRipNotification *notification)
{
  if (entry->value)
  {
    GValue value = {0};

    switch (entry->value->type)
    {
      case GCONF_VALUE_STRING:
        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, gconf_value_get_string (entry->value));
        break;
      case GCONF_VALUE_INT:
        g_value_init (&value, G_TYPE_INT);
        g_value_set_int (&value, gconf_value_get_int (entry->value));
        break;
      case GCONF_VALUE_FLOAT:
        g_value_init (&value, G_TYPE_DOUBLE);
        g_value_set_double (&value, gconf_value_get_float (entry->value));
        break;
      case GCONF_VALUE_BOOL:
        g_value_init (&value, G_TYPE_BOOLEAN);
        g_value_set_boolean (&value, gconf_value_get_bool (entry->value));
        break;
      default:
        g_warning ("Cannot set key '%s': invalid type", entry->key);
        break;
    }

    (* notification->func) (notification->settings, notification->section,
        entry->key + strlen (notification->section) + 1, &value, notification->data);

    g_value_unset (&value);
  }
}

static void
ogmrip_gconf_settings_free_notification (OGMRipNotification *notification)
{
/*
  if (notification->handler_id)
    gconf_client_notify_remove (notification->client, notification->handler_id);
*/
  g_object_unref (notification->client);

  g_free (notification->section);
  g_free (notification->key);
  g_free (notification);
}

static gulong
ogmrip_gconf_settings_add_notify (OGMRipSettings *settings, const gchar *section, const gchar *key, OGMRipNotifyFunc func, gpointer data)
{
  OGMRipGConfSettings *gconf;
  OGMRipNotification *notification;
  gchar *real_key;

  gconf = OGMRIP_GCONF_SETTINGS (settings);

  notification = g_new0 (OGMRipNotification, 1);

  notification->client = g_object_ref (gconf->priv->client);
  notification->settings = settings;
  notification->func = func;
  notification->data = data;

  notification->section = g_strdup (section);
  notification->key = g_strdup (key);

  real_key = gconf_concat_dir_and_key (section, key);
  notification->handler_id = gconf_client_notify_add (gconf->priv->client, real_key,
      (GConfClientNotifyFunc) ogmrip_gconf_settings_notify_cb, notification,
      (GFreeFunc) ogmrip_gconf_settings_free_notification, NULL);
  g_free (real_key);

  if (!notification->handler_id)
    ogmrip_gconf_settings_free_notification (notification);

  return notification->handler_id;
}

static void
ogmrip_gconf_settings_remove_notify (OGMRipSettings *settings, gulong handler_id)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (settings);
  gconf_client_notify_remove  (gconf->priv->client, handler_id);
}

static gchar *
ogmrip_gconf_settings_build_section (OGMRipSettings *settings, const gchar *element, va_list var_args)
{
  gchar *str, *section = NULL;

  while (element)
  {
    if (section)
    {
      str = my_gconf_concat_dir_and_key (section, element);
      g_free (section);
      section = str;
    }
    else
      section = g_strdup (element);

    element = va_arg (var_args, gchar *);
  }

  return section;
}

static const gchar *
ogmrip_gconf_settings_get_section_name (OGMRipSettings *settings, const gchar *section)
{
  gchar *name;

  name = strrchr (section, '/');
  if (!name)
    return section;

  return name + 1;
}

static void
ogmrip_gconf_settings_sync (OGMRipSettings *settings)
{
  OGMRipGConfSettings *gconf;

  gconf = OGMRIP_GCONF_SETTINGS (settings);
  gconf_client_suggest_sync (gconf->priv->client, NULL);
}

static void
ogmrip_settings_init (OGMRipSettingsIface *iface)
{
  iface->set_value = ogmrip_gconf_settings_set_value;
  iface->get_value = ogmrip_gconf_settings_get_value;

  iface->get_subsections = ogmrip_gconf_settings_get_subsections;
  iface->get_keys = ogmrip_gconf_settings_get_keys;

  iface->has_key = ogmrip_gconf_settings_has_key;
  iface->has_section = ogmrip_gconf_settings_has_section;
  iface->remove_key = ogmrip_gconf_settings_remove_key;
  iface->remove_section = ogmrip_gconf_settings_remove_section;

  iface->add_notify    = ogmrip_gconf_settings_add_notify;
  iface->remove_notify = ogmrip_gconf_settings_remove_notify;

  iface->build_section = ogmrip_gconf_settings_build_section;
  iface->get_section_name = ogmrip_gconf_settings_get_section_name;

  iface->sync = ogmrip_gconf_settings_sync;
}

static void
ogmrip_gconf_settings_init (OGMRipGConfSettings *settings)
{
  settings->priv = OGMRIP_GCONF_SETTINGS_GET_PRIVATE (settings);

  settings->priv->client = gconf_client_get_default ();
}

/**
 * ogmrip_gconf_settings_new:
 * @basedir: the directory to watch
 *
 * Creates a new #OGMRipGConfSettings.
 *
 * Returns: The newly created #OGMRipGConfSettings
 */
OGMRipSettings *
ogmrip_gconf_settings_new (const gchar *basedir)
{
  OGMRipGConfSettings *settings;

  g_return_val_if_fail (basedir != NULL, NULL);

  settings = g_object_new (OGMRIP_TYPE_GCONF_SETTINGS, NULL);

  settings->priv->basedir = g_strdup (basedir);
  gconf_client_add_dir (settings->priv->client, basedir, GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);

  return OGMRIP_SETTINGS (settings);
}

