/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* eel-gconf-extensions.c - Stuff to make GConf easier to use.

   Copyright (C) 2000, 2001 Eazel, Inc.

   The Gnome 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.

   The Gnome 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 the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors: Ramiro Estrugo <ramiro@eazel.com>

   $Id$
*/

#include "config.h"

#include "eel-gconf-extensions.h"

#include <glib/gi18n.h>

#include <gconf/gconf-client.h>
#include <gconf/gconf.h>
#include <string.h>

static GConfClient *global_gconf_client = NULL;

static void
global_client_free (void)
{
	if (global_gconf_client == NULL) {
		return;
	}
	
	g_object_unref (global_gconf_client);
	global_gconf_client = NULL;
}

/* Public */
GConfClient *
eel_gconf_client_get_global (void)
{
	if (global_gconf_client == NULL) {
		global_gconf_client = gconf_client_get_default ();
		g_atexit (global_client_free);
	}
	
	return global_gconf_client;
}

gboolean
eel_gconf_handle_error (GError **error)
{
	g_return_val_if_fail (error != NULL, FALSE);

	if (*error != NULL) {
		g_warning (_("GConf error:\n  %s"), (*error)->message);
		g_error_free (*error);
		*error = NULL;

		return TRUE;
	}

	return FALSE;
}

void
eel_gconf_set_boolean (const char *key,
			    gboolean boolean_value)
{
	GConfClient *client;
	GError *error = NULL;
	
	g_return_if_fail (key != NULL);

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);
	
	gconf_client_set_bool (client, key, boolean_value, &error);
	eel_gconf_handle_error (&error);
}

gboolean
eel_gconf_get_boolean (const char *key)
{
	gboolean result;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, FALSE);
	
	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, FALSE);
	
	result = gconf_client_get_bool (client, key, &error);
	
	if (eel_gconf_handle_error (&error)) {
		result = FALSE;
	}
	
	return result;
}

void
eel_gconf_set_integer (const char *key,
			    int int_value)
{
	GConfClient *client;
	GError *error = NULL;

	g_return_if_fail (key != NULL);

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);

	gconf_client_set_int (client, key, int_value, &error);
	eel_gconf_handle_error (&error);
}

int
eel_gconf_get_integer (const char *key)
{
	int result;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, 0);
	
	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, 0);
	
	result = gconf_client_get_int (client, key, &error);

	if (eel_gconf_handle_error (&error)) {
		result = 0;
	}

	return result;
}

void
eel_gconf_set_string (const char *key,
			   const char *string_value)
{
	GConfClient *client;
	GError *error = NULL;

	g_return_if_fail (key != NULL);

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);
	
	gconf_client_set_string (client, key, string_value, &error);
	eel_gconf_handle_error (&error);
}

char *
eel_gconf_get_string (const char *key)
{
	char *result;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, NULL);
	
	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, NULL);
	
	result = gconf_client_get_string (client, key, &error);
	
	if (eel_gconf_handle_error (&error)) {
		result = g_strdup ("");
	}
	
	return result;
}

void
eel_gconf_set_string_list (const char *key,
				const GSList *slist)
{
	GConfClient *client;
	GError *error;

	g_return_if_fail (key != NULL);

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);

	error = NULL;
	gconf_client_set_list (client, key, GCONF_VALUE_STRING,
			       /* Need cast cause of GConf api bug */
			       (GSList *) slist,
			       &error);
	eel_gconf_handle_error (&error);
}

GSList *
eel_gconf_get_string_list (const char *key)
{
	GSList *slist;
	GConfClient *client;
	GError *error;
	
	g_return_val_if_fail (key != NULL, NULL);
	
	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, NULL);
	
	error = NULL;
	slist = gconf_client_get_list (client, key, GCONF_VALUE_STRING, &error);
	if (eel_gconf_handle_error (&error)) {
		slist = NULL;
	}

	return slist;
}

gboolean
eel_gconf_is_default (const char *key)
{
	gboolean result;
	GConfValue *value;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, FALSE);
	
	value = gconf_client_get_without_default  (eel_gconf_client_get_global (), key, &error);

	if (eel_gconf_handle_error (&error)) {
		if (value != NULL) {
			gconf_value_free (value);
		}
		return FALSE;
	}

	result = (value == NULL);
	eel_gconf_value_free (value);
	return result;
}

gboolean
eel_gconf_key_is_writable (const char *key)
{
	gboolean result;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, FALSE);
	
	result = gconf_client_key_is_writable  (eel_gconf_client_get_global (), key, &error);

	if (eel_gconf_handle_error (&error)) {
		return result;
	}

	return result;
}

gboolean
eel_gconf_monitor_add (const char *directory)
{
	GError *error = NULL;
	GConfClient *client;

	g_return_val_if_fail (directory != NULL, FALSE);

	client = gconf_client_get_default ();
	g_return_val_if_fail (client != NULL, FALSE);

	gconf_client_add_dir (client,
			      directory,
			      GCONF_CLIENT_PRELOAD_NONE,
			      &error);
	
	if (eel_gconf_handle_error (&error)) {
		return FALSE;
	}

	return TRUE;
}

gboolean
eel_gconf_monitor_remove (const char *directory)
{
	GError *error = NULL;
	GConfClient *client;

	if (directory == NULL) {
		return FALSE;
	}

	client = gconf_client_get_default ();
	g_return_val_if_fail (client != NULL, FALSE);
	
	gconf_client_remove_dir (client,
				 directory,
				 &error);
	
	if (eel_gconf_handle_error (&error)) {
		return FALSE;
	}
	
	return TRUE;
}

void
eel_gconf_preload_cache (const char             *directory,
			 GConfClientPreloadType  preload_type)
{
	GError *error = NULL;
	GConfClient *client;

	if (directory == NULL) {
		return;
	}

	client = gconf_client_get_default ();
	g_return_if_fail (client != NULL);
	
	gconf_client_preload (client,
			      directory,
			      preload_type,
			      &error);
	
	eel_gconf_handle_error (&error);
}

void
eel_gconf_suggest_sync (void)
{
	GConfClient *client;
	GError *error = NULL;

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);
	
	gconf_client_suggest_sync (client, &error);
	eel_gconf_handle_error (&error);
}

GConfValue*
eel_gconf_get_value (const char *key)
{
	GConfValue *value = NULL;
	GConfClient *client;
	GError *error = NULL;

	g_return_val_if_fail (key != NULL, NULL);

	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, NULL);

	value = gconf_client_get (client, key, &error);
	
	if (eel_gconf_handle_error (&error)) {
		if (value != NULL) {
			gconf_value_free (value);
			value = NULL;
		}
	}

	return value;
}

GConfValue*
eel_gconf_get_default_value (const char *key)
{
	GConfValue *value = NULL;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, NULL);

	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, NULL);

	value = gconf_client_get_default_from_schema (client, key, &error);
	
	if (eel_gconf_handle_error (&error)) {
		if (value != NULL) {
			gconf_value_free (value);
			value = NULL;
		}
	}

	return value;
}

static int
eel_strcmp (const char *string_a, const char *string_b)
{
	/* FIXME bugzilla.eazel.com 5450: Maybe we need to make this
	 * treat 'NULL < ""', or have a flavor that does that. If we
	 * didn't have code that already relies on 'NULL == ""', I
	 * would change it right now.
	 */
	return strcmp (string_a == NULL ? "" : string_a,
		       string_b == NULL ? "" : string_b);
}

static gboolean
eel_str_is_equal (const char *string_a, const char *string_b)
{
	/* FIXME bugzilla.eazel.com 5450: Maybe we need to make this
	 * treat 'NULL != ""', or have a flavor that does that. If we
	 * didn't have code that already relies on 'NULL == ""', I
	 * would change it right now.
	 */
	return eel_strcmp (string_a, string_b) == 0;
}

static gboolean
simple_value_is_equal (const GConfValue *a,
		       const GConfValue *b)
{
	g_return_val_if_fail (a != NULL, FALSE);
	g_return_val_if_fail (b != NULL, FALSE);

	switch (a->type) {
	case GCONF_VALUE_STRING:
		return eel_str_is_equal (gconf_value_get_string (a),
					 gconf_value_get_string (b));
		break;

	case GCONF_VALUE_INT:
		return gconf_value_get_int (a) ==
			gconf_value_get_int (b);
		break;

	case GCONF_VALUE_FLOAT:
		return gconf_value_get_float (a) ==
			gconf_value_get_float (b);
		break;

	case GCONF_VALUE_BOOL:
		return gconf_value_get_bool (a) ==
			gconf_value_get_bool (b);
		break;
	default:
		g_assert_not_reached ();
	}
	
	return FALSE;
}

gboolean
eel_gconf_value_is_equal (const GConfValue *a,
			       const GConfValue *b)
{
	GSList *node_a;
	GSList *node_b;

	if (a == NULL && b == NULL) {
		return TRUE;
	}

	if (a == NULL || b == NULL) {
		return FALSE;
	}

	if (a->type != b->type) {
		return FALSE;
	}

	switch (a->type) {
	case GCONF_VALUE_STRING:
	case GCONF_VALUE_INT:
	case GCONF_VALUE_FLOAT:
	case GCONF_VALUE_BOOL:
		return simple_value_is_equal (a, b);
		break;
		
	case GCONF_VALUE_LIST:
		if (gconf_value_get_list_type (a) !=
		    gconf_value_get_list_type (b)) {
			return FALSE;
		}

		node_a = gconf_value_get_list (a);
		node_b = gconf_value_get_list (b);
		
		if (node_a == NULL && node_b == NULL) {
			return TRUE;
		}

		if (g_slist_length (node_a) !=
		    g_slist_length (node_b)) {
			return FALSE;
		}
		
		for (;
		     node_a != NULL && node_b != NULL;
		     node_a = node_a->next, node_b = node_b->next) {
			g_assert (node_a->data != NULL);
			g_assert (node_b->data != NULL);
			if (!simple_value_is_equal (node_a->data, node_b->data)) {
				return FALSE;
			}
		}
		
		return TRUE;
	default:
		/* FIXME: pair ? */
		g_assert (0);
	}
	
	g_assert_not_reached ();
	return FALSE;
}

void
eel_gconf_value_free (GConfValue *value)
{
	if (value == NULL) {
		return;
	}
	
	gconf_value_free (value);
}

guint
eel_gconf_notification_add (const char *key,
				 GConfClientNotifyFunc notification_callback,
				 gpointer callback_data)
{
	guint notification_id;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
	g_return_val_if_fail (notification_callback != NULL, EEL_GCONF_UNDEFINED_CONNECTION);

	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, EEL_GCONF_UNDEFINED_CONNECTION);
	
	notification_id = gconf_client_notify_add (client,
						   key,
						   notification_callback,
						   callback_data,
						   NULL,
						   &error);
	
	if (eel_gconf_handle_error (&error)) {
		if (notification_id != EEL_GCONF_UNDEFINED_CONNECTION) {
			gconf_client_notify_remove (client, notification_id);
			notification_id = EEL_GCONF_UNDEFINED_CONNECTION;
		}
	}
	
	return notification_id;
}

void
eel_gconf_notification_remove (guint notification_id)
{
	GConfClient *client;

	if (notification_id == EEL_GCONF_UNDEFINED_CONNECTION) {
		return;
	}
	
	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);

	gconf_client_notify_remove (client, notification_id);
}

GSList *
eel_gconf_value_get_string_list (const GConfValue *value)
{
 	GSList *result;
 	const GSList *slist;
 	const GSList *node;
	const char *string;
	const GConfValue *next_value;

	if (value == NULL) {
		return NULL;
	}

	g_return_val_if_fail (value->type == GCONF_VALUE_LIST, NULL);
	g_return_val_if_fail (gconf_value_get_list_type (value) == GCONF_VALUE_STRING, NULL);

	slist = gconf_value_get_list (value);
	result = NULL;
	for (node = slist; node != NULL; node = node->next) {
		next_value = node->data;
		g_return_val_if_fail (next_value != NULL, NULL);
		g_return_val_if_fail (next_value->type == GCONF_VALUE_STRING, NULL);
		string = gconf_value_get_string (next_value);
		result = g_slist_append (result, g_strdup (string));
	}
	return result;
}

void
eel_gconf_value_set_string_list (GConfValue *value,
				 const GSList *string_list)
{
 	const GSList *node;
	GConfValue *next_value;
 	GSList *value_list;

	g_return_if_fail (value->type == GCONF_VALUE_LIST);
	g_return_if_fail (gconf_value_get_list_type (value) == GCONF_VALUE_STRING);

	value_list = NULL;
	for (node = string_list; node != NULL; node = node->next) {
		next_value = gconf_value_new (GCONF_VALUE_STRING);
		gconf_value_set_string (next_value, node->data);
		value_list = g_slist_append (value_list, next_value);
	}

	gconf_value_set_list (value, value_list);

	for (node = value_list; node != NULL; node = node->next) {
		gconf_value_free (node->data);
	}
	g_slist_free (value_list);
}

void
eel_gconf_set_float (const char *key,
			    gfloat float_value)
{
	GConfClient *client;
	GError *error = NULL;

	g_return_if_fail (key != NULL);

	client = eel_gconf_client_get_global ();
	g_return_if_fail (client != NULL);

	gconf_client_set_float (client, key, float_value, &error);
	eel_gconf_handle_error (&error);
}

gfloat
eel_gconf_get_float (const char *key)
{
	gfloat result;
	GConfClient *client;
	GError *error = NULL;
	
	g_return_val_if_fail (key != NULL, 0);
	
	client = eel_gconf_client_get_global ();
	g_return_val_if_fail (client != NULL, 0);
	
	result = gconf_client_get_float (client, key, &error);

	if (eel_gconf_handle_error (&error)) {
		result = 0;
	}

	return result;
}

static char *
tilde_compress (const char *path)
{
	const char *home_dir = g_get_home_dir();
	int         home_dir_l = strlen (home_dir);
	int         ntilde = 0;
	const char *scan;
	int         path_l, result_l;
	char       *result, *scan2;

	if (path == NULL)
		return NULL;

	path_l = strlen (path);
	for (scan = path; scan != NULL; scan++) {
		if (path_l - (scan - path) < home_dir_l)
			break;
		if (strncmp (scan, home_dir, home_dir_l) == 0)
			ntilde++;
	}
	
	if (ntilde == 0)
		return g_strdup (path);
	
	result_l = strlen (path) + ntilde - (ntilde * home_dir_l);
	result = g_new (char, result_l + 1);

	for (scan = path, scan2 = result; scan != NULL; scan2++) {
		if (path_l - (scan - path) < home_dir_l) {
			strcpy (scan2, scan);
			scan2 += strlen (scan);
			break;
		}
		if (strncmp (scan, home_dir, home_dir_l) == 0) {
			*scan2 = '~';
			scan += home_dir_l;
		} else {
			*scan2 = *scan;
			scan++;
		} 
	}
	*scan2 = 0;

	return result;
}

void
eel_gconf_set_path (const char *key,
		    const char *value)
{
	char *tilde_path;
	char *converted;
	
	tilde_path = tilde_compress (value);
	converted = g_filename_to_utf8 (tilde_path, -1, NULL, NULL, NULL);
	eel_gconf_set_string (key, converted);

	g_free (tilde_path);
	g_free (converted);
}