/*
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <X11/keysym.h>

#include <glib/gi18n.h>
#include <gdk/gdkx.h>
#include <libgnome/gnome-program.h>

#include "gswitchit-config.h"
#include "gswitchit-config-private.h"

#include <keyboard-config-registry-client.h>

/**
 * GSwitchItConfig
 */
#define KEYBOARD_CONFIG_KEY_PREFIX "/desktop/gnome/peripherals/keyboard"

#define GSWITCHIT_CONFIG_KEY_PREFIX  KEYBOARD_CONFIG_KEY_PREFIX "/general"

const gchar GSWITCHIT_CONFIG_DIR[] = GSWITCHIT_CONFIG_KEY_PREFIX;
const gchar GSWITCHIT_CONFIG_KEY_DEFAULT_GROUP[] =
    GSWITCHIT_CONFIG_KEY_PREFIX "/defaultGroup";
const gchar GSWITCHIT_CONFIG_KEY_GROUP_PER_WINDOW[] =
    GSWITCHIT_CONFIG_KEY_PREFIX "/groupPerWindow";
const gchar GSWITCHIT_CONFIG_KEY_HANDLE_INDICATORS[] =
    GSWITCHIT_CONFIG_KEY_PREFIX "/handleIndicators";
const gchar GSWITCHIT_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES[]
    = GSWITCHIT_CONFIG_KEY_PREFIX "/layoutNamesAsGroupNames";

/**
 * GSwitchItKbdConfig
 */
#define GSWITCHIT_KBD_CONFIG_KEY_PREFIX KEYBOARD_CONFIG_KEY_PREFIX "/kbd"

const gchar GSWITCHIT_KBD_CONFIG_DIR[] = GSWITCHIT_KBD_CONFIG_KEY_PREFIX;
const gchar GSWITCHIT_KBD_CONFIG_KEY_MODEL[] =
    GSWITCHIT_KBD_CONFIG_KEY_PREFIX "/model";
const gchar GSWITCHIT_KBD_CONFIG_KEY_LAYOUTS[] =
    GSWITCHIT_KBD_CONFIG_KEY_PREFIX "/layouts";
const gchar GSWITCHIT_KBD_CONFIG_KEY_OPTIONS[] =
    GSWITCHIT_KBD_CONFIG_KEY_PREFIX "/options";

const gchar *GSWITCHIT_KBD_CONFIG_ACTIVE[] = {
	GSWITCHIT_KBD_CONFIG_KEY_MODEL,
	GSWITCHIT_KBD_CONFIG_KEY_LAYOUTS,
	GSWITCHIT_KBD_CONFIG_KEY_OPTIONS
};

#define GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX KEYBOARD_CONFIG_KEY_PREFIX "/kbd.sysbackup"

const gchar GSWITCHIT_KBD_CONFIG_SYSBACKUP_DIR[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX;
const gchar GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_MODEL[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/model";
const gchar GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_LAYOUTS[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/layouts";
const gchar GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_OPTIONS[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/options";

const gchar *GSWITCHIT_KBD_CONFIG_SYSBACKUP[] = {
	GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_MODEL,
	GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_LAYOUTS,
	GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_OPTIONS
};

/**
 * static common functions
 */
static void
gswitchit_config_string_list_reset (GSList ** plist)
{
	while (*plist != NULL) {
		GSList *p = *plist;
		*plist = (*plist)->next;
		g_free (p->data);
		g_slist_free_1 (p);
	}
}

static gboolean
gswitchit_kbd_config_get_lv_descriptions (XklConfigRegistry *
					  config_registry,
					  const gchar * layout_name,
					  const gchar * variant_name,
					  gchar ** layout_short_descr,
					  gchar ** layout_descr,
					  gchar ** variant_short_descr,
					  gchar ** variant_descr)
{
	static XklConfigItem litem;
	static XklConfigItem vitem;

	layout_name = g_strdup (layout_name);

	g_snprintf (litem.name, sizeof litem.name, "%s", layout_name);
	if (xkl_config_registry_find_layout (config_registry, &litem)) {
		*layout_short_descr = litem.short_description;
		*layout_descr = litem.description;
	} else
		*layout_short_descr = *layout_descr = NULL;

	if (variant_name != NULL) {
		variant_name = g_strdup (variant_name);
		g_snprintf (vitem.name, sizeof vitem.name, "%s",
			    variant_name);
		if (xkl_config_registry_find_variant
		    (config_registry, layout_name, &vitem)) {
			*variant_short_descr = vitem.short_description;
			*variant_descr = vitem.description;
		} else
			*variant_short_descr = *variant_descr = NULL;

		g_free ((char *) variant_name);
	} else
		*variant_descr = NULL;

	g_free ((char *) layout_name);
	return *layout_descr != NULL;
}

static gboolean
gswitchit_config_get_remote_lv_descriptions_utf8 (gchar *** sld,
						  gchar *** lld,
						  gchar *** svd,
						  gchar *** lvd)
{
	DBusGProxy *proxy;
	DBusGConnection *connection;
	GError *error = NULL;

	connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
	if (connection == NULL) {
		g_warning ("Unable to connect to dbus: %s\n",
			   error->message);
		g_error_free (error);
		/* Basically here, there is a problem, since there is no dbus :) */
		return False;
	}

/* This won't trigger activation! */
	proxy = dbus_g_proxy_new_for_name (connection,
					   "org.gnome.KeyboardConfigRegistry",
					   "/org/gnome/KeyboardConfigRegistry",
					   "org.gnome.KeyboardConfigRegistry");

/* The method call will trigger activation, more on that later */
	if (!org_gnome_KeyboardConfigRegistry_get_current_descriptions_as_utf8 (proxy, sld, lld, svd, lvd, &error)) {
		/* Method failed, the GError is set, let's warn everyone */
		g_warning ("Woops remote method failed: %s",
			   error->message);
		g_error_free (error);
		return False;
	}
	return True;
}

void
gswitchit_config_add_listener (GConfClient * conf_client,
			       const gchar * key,
			       GConfClientNotifyFunc func,
			       gpointer user_data, int *pid)
{
	GError *gerror = NULL;
	xkl_debug (150, "Listening to [%s]\n", key);
	*pid = gconf_client_notify_add (conf_client,
					key, func, user_data, NULL,
					&gerror);
	if (0 == *pid) {
		g_warning ("Error listening for configuration: [%s]\n",
			   gerror->message);
		g_error_free (gerror);
	}
}

void
gswitchit_config_remove_listener (GConfClient * conf_client, int *pid)
{
	if (*pid != 0) {
		gconf_client_notify_remove (conf_client, *pid);
		*pid = 0;
	}
}

/**
 * extern common functions
 */
const gchar *
gswitchit_kbd_config_merge_items (const gchar * parent,
				  const gchar * child)
{
	static gchar buffer[XKL_MAX_CI_NAME_LENGTH * 2 - 1];
	*buffer = '\0';
	if (parent != NULL) {
		if (strlen (parent) >= XKL_MAX_CI_NAME_LENGTH)
			return NULL;
		strcat (buffer, parent);
	}
	if (child != NULL) {
		if (strlen (child) >= XKL_MAX_CI_NAME_LENGTH)
			return NULL;
		strcat (buffer, "\t");
		strcat (buffer, child);
	}
	return buffer;
}

gboolean
gswitchit_kbd_config_split_items (const gchar * merged, gchar ** parent,
				  gchar ** child)
{
	static gchar pbuffer[XKL_MAX_CI_NAME_LENGTH];
	static gchar cbuffer[XKL_MAX_CI_NAME_LENGTH];
	int plen, clen;
	const gchar *pos;
	*parent = *child = NULL;

	if (merged == NULL)
		return FALSE;

	pos = strchr (merged, '\t');
	if (pos == NULL) {
		plen = strlen (merged);
		clen = 0;
	} else {
		plen = pos - merged;
		clen = strlen (pos + 1);
		if (clen >= XKL_MAX_CI_NAME_LENGTH)
			return FALSE;
		strcpy (*child = cbuffer, pos + 1);
	}
	if (plen >= XKL_MAX_CI_NAME_LENGTH)
		return FALSE;
	memcpy (*parent = pbuffer, merged, plen);
	pbuffer[plen] = '\0';
	return TRUE;
}

/**
 * static GSwitchItKbdConfig functions
 */
static void
gswitchit_kbd_config_options_add_full (GSwitchItKbdConfig * kbd_config,
				       const gchar * full_option_name)
{
	kbd_config->options =
	    g_slist_append (kbd_config->options,
			    g_strdup (full_option_name));
}

static void
gswitchit_kbd_config_layouts_add_full (GSwitchItKbdConfig * kbd_config,
				       const gchar * full_layout_name)
{
	kbd_config->layouts =
	    g_slist_append (kbd_config->layouts,
			    g_strdup (full_layout_name));
}

static void
gswitchit_kbd_config_copy_from_xkl_config (GSwitchItKbdConfig * kbd_config,
					   XklConfigRec * pdata)
{
	char **p, **p1;
	gswitchit_kbd_config_model_set (kbd_config, pdata->model);
	xkl_debug (150, "Loaded Kbd model: [%s]\n", pdata->model);

	gswitchit_kbd_config_layouts_reset (kbd_config);
	p = pdata->layouts;
	p1 = pdata->variants;
	while (p != NULL && *p != NULL) {
		if (*p1 == NULL || **p1 == '\0') {
			xkl_debug (150, "Loaded Kbd layout: [%s]\n", *p);
			gswitchit_kbd_config_layouts_add_full (kbd_config,
							       *p);
		} else {
			char full_layout[XKL_MAX_CI_NAME_LENGTH * 2];
			g_snprintf (full_layout, sizeof (full_layout),
				    "%s\t%s", *p, *p1);
			xkl_debug (150,
				   "Loaded Kbd layout with variant: [%s]\n",
				   full_layout);
			gswitchit_kbd_config_layouts_add_full (kbd_config,
							       full_layout);
		}
		p++;
		p1++;
	}

	gswitchit_kbd_config_options_reset (kbd_config);
	p = pdata->options;
	while (p != NULL && *p != NULL) {
		char group[XKL_MAX_CI_NAME_LENGTH];
		char *option = *p;
		char *delim =
		    (option != NULL) ? strchr (option, ':') : NULL;
		int len;
		if ((delim != NULL) &&
		    ((len = (delim - option)) < XKL_MAX_CI_NAME_LENGTH)) {
			strncpy (group, option, len);
			group[len] = 0;
			xkl_debug (150, "Loaded Kbd option: [%s][%s]\n",
				   group, option);
			gswitchit_kbd_config_options_add (kbd_config,
							  group, option);
		}
		p++;
	}
}

static void
gswitchit_kbd_config_copy_to_xkl_config (GSwitchItKbdConfig * kbd_config,
					 XklConfigRec * pdata)
{
	int i;
	int num_layouts, num_options;
	pdata->model =
	    (kbd_config->model ==
	     NULL) ? NULL : g_strdup (kbd_config->model);

	num_layouts =
	    (kbd_config->layouts ==
	     NULL) ? 0 : g_slist_length (kbd_config->layouts);
	num_options =
	    (kbd_config->options ==
	     NULL) ? 0 : g_slist_length (kbd_config->options);

	xkl_debug (150, "Taking %d layouts\n", num_layouts);
	if (num_layouts != 0) {
		GSList *the_layout = kbd_config->layouts;
		char **p1 = pdata->layouts =
		    g_new0 (char *, num_layouts + 1);
		char **p2 = pdata->variants =
		    g_new0 (char *, num_layouts + 1);
		for (i = num_layouts; --i >= 0;) {
			char *layout, *variant;
			if (gswitchit_kbd_config_split_items
			    (the_layout->data, &layout, &variant)
			    && variant != NULL) {
				*p1 =
				    (layout ==
				     NULL) ? g_strdup ("") :
				    g_strdup (layout);
				*p2 =
				    (variant ==
				     NULL) ? g_strdup ("") :
				    g_strdup (variant);
			} else {
				*p1 =
				    (the_layout->data ==
				     NULL) ? g_strdup ("") :
				    g_strdup (the_layout->data);
				*p2 = g_strdup ("");
			}
			xkl_debug (150, "Adding [%s]/%p and [%s]/%p\n",
				   *p1 ? *p1 : "(nil)", *p1,
				   *p2 ? *p2 : "(nil)", *p2);
			p1++;
			p2++;
			the_layout = the_layout->next;
		}
	}

	if (num_options != 0) {
		GSList *the_option = kbd_config->options;
		char **p = pdata->options =
		    g_new0 (char *, num_options + 1);
		for (i = num_options; --i >= 0;) {
			char *group, *option;
			if (gswitchit_kbd_config_split_items
			    (the_option->data, &group, &option)
			    && option != NULL)
				*(p++) = g_strdup (option);
			else {
				*(p++) = g_strdup ("");
				xkl_debug (150, "Could not split [%s]\n",
					   the_option->data);
			}
			the_option = the_option->next;
		}
	}
}

static gboolean
gslist_str_equal (GSList * l1, GSList * l2)
{
	if (l1 == l2)
		return TRUE;
	while (l1 != NULL && l2 != NULL) {
		if ((l1->data != l2->data) &&
		    (l1->data != NULL) &&
		    (l2->data != NULL) &&
		    g_ascii_strcasecmp (l1->data, l2->data))
			return False;

		l1 = l1->next;
		l2 = l2->next;
	}
	return (l1 == NULL && l2 == NULL);
}

static void
gswitchit_kbd_config_load_params (GSwitchItKbdConfig * kbd_config,
				  const gchar * param_names[])
{
	GError *gerror = NULL;
	gchar *pc;
	GSList *pl;

	pc = gconf_client_get_string (kbd_config->conf_client,
				      param_names[0], &gerror);
	if (pc == NULL || gerror != NULL) {
		if (gerror != NULL) {
			g_warning ("Error reading configuration:%s\n",
				   gerror->message);
			g_error_free (gerror);
			g_free (pc);
			gerror = NULL;
		}
		gswitchit_kbd_config_model_set (kbd_config, NULL);
	} else {
		gswitchit_kbd_config_model_set (kbd_config, pc);
		g_free (pc);
	}
	xkl_debug (150, "Loaded Kbd model: [%s]\n",
		   kbd_config->model ? kbd_config->model : "(null)");

	gswitchit_kbd_config_layouts_reset (kbd_config);

	pl = gconf_client_get_list (kbd_config->conf_client,
				    param_names[1],
				    GCONF_VALUE_STRING, &gerror);
	if (pl == NULL || gerror != NULL) {
		if (gerror != NULL) {
			g_warning ("Error reading configuration:%s\n",
				   gerror->message);
			g_error_free (gerror);
			gerror = NULL;
		}
	}

	while (pl != NULL) {
		xkl_debug (150, "Loaded Kbd layout: [%s]\n", pl->data);
		gswitchit_kbd_config_layouts_add_full (kbd_config,
						       pl->data);
		pl = pl->next;
	}
	gswitchit_config_string_list_reset (&pl);

	gswitchit_kbd_config_options_reset (kbd_config);

	pl = gconf_client_get_list (kbd_config->conf_client,
				    param_names[2],
				    GCONF_VALUE_STRING, &gerror);
	if (pl == NULL || gerror != NULL) {
		if (gerror != NULL) {
			g_warning ("Error reading configuration:%s\n",
				   gerror->message);
			g_error_free (gerror);
			gerror = NULL;
		}
	}

	while (pl != NULL) {
		xkl_debug (150, "Loaded Kbd option: [%s]\n", pl->data);
		gswitchit_kbd_config_options_add_full (kbd_config,
						       (const gchar *) pl->
						       data);
		pl = pl->next;
	}
	gswitchit_config_string_list_reset (&pl);
}

static void
gswitchit_kbd_config_save_params (GSwitchItKbdConfig * kbd_config,
				  GConfChangeSet * cs,
				  const gchar * param_names[])
{
	GSList *pl;

	if (kbd_config->model)
		gconf_change_set_set_string (cs, param_names[0],
					     kbd_config->model);
	else
		gconf_change_set_unset (cs, param_names[0]);
	xkl_debug (150, "Saved Kbd model: [%s]\n",
		   kbd_config->model ? kbd_config->model : "(null)");

	if (kbd_config->layouts) {
		pl = kbd_config->layouts;
		while (pl != NULL) {
			xkl_debug (150, "Saved Kbd layout: [%s]\n",
				   pl->data);
			pl = pl->next;
		}
		gconf_change_set_set_list (cs,
					   param_names[1],
					   GCONF_VALUE_STRING,
					   kbd_config->layouts);
	} else {
		xkl_debug (150, "Saved Kbd layouts: []\n");
		gconf_change_set_unset (cs, param_names[1]);
	}

	if (kbd_config->options) {
		pl = kbd_config->options;
		while (pl != NULL) {
			xkl_debug (150, "Saved Kbd option: [%s]\n",
				   pl->data);
			pl = pl->next;
		}
		gconf_change_set_set_list (cs,
					   param_names[2],
					   GCONF_VALUE_STRING,
					   kbd_config->options);
	} else {
		xkl_debug (150, "Saved Kbd options: []\n");
		gconf_change_set_unset (cs, param_names[2]);
	}
}

/**
 * extern GSwitchItConfig config functions
 */
void
gswitchit_config_init (GSwitchItConfig * config, GConfClient * conf_client,
		       XklEngine * engine)
{
	GError *gerror = NULL;

	memset (config, 0, sizeof (*config));
	config->conf_client = conf_client;
	config->engine = engine;
	g_object_ref (config->conf_client);

	gconf_client_add_dir (config->conf_client,
			      GSWITCHIT_CONFIG_DIR,
			      GCONF_CLIENT_PRELOAD_NONE, &gerror);
	if (gerror != NULL) {
		g_warning ("err: %s\n", gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}
}

void
gswitchit_config_term (GSwitchItConfig * config)
{
	g_object_unref (config->conf_client);
	config->conf_client = NULL;
}

void
gswitchit_config_load_from_gconf (GSwitchItConfig * config)
{
	GError *gerror = NULL;

	config->group_per_app =
	    gconf_client_get_bool (config->conf_client,
				   GSWITCHIT_CONFIG_KEY_GROUP_PER_WINDOW,
				   &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading configuration:%s\n",
			   gerror->message);
		config->group_per_app = FALSE;
		g_error_free (gerror);
		gerror = NULL;
	}
	xkl_debug (150, "group_per_app: %d\n", config->group_per_app);

	config->handle_indicators =
	    gconf_client_get_bool (config->conf_client,
				   GSWITCHIT_CONFIG_KEY_HANDLE_INDICATORS,
				   &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading configuration:%s\n",
			   gerror->message);
		config->handle_indicators = FALSE;
		g_error_free (gerror);
		gerror = NULL;
	}
	xkl_debug (150, "handle_indicators: %d\n",
		   config->handle_indicators);

	config->layout_names_as_group_names =
	    gconf_client_get_bool (config->conf_client,
				   GSWITCHIT_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES,
				   &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading configuration:%s\n",
			   gerror->message);
		config->layout_names_as_group_names = TRUE;
		g_error_free (gerror);
		gerror = NULL;
	}
	xkl_debug (150, "layout_names_as_group_names: %d\n",
		   config->layout_names_as_group_names);

	config->default_group =
	    gconf_client_get_int (config->conf_client,
				  GSWITCHIT_CONFIG_KEY_DEFAULT_GROUP,
				  &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading configuration:%s\n",
			   gerror->message);
		config->default_group = -1;
		g_error_free (gerror);
		gerror = NULL;
	}

	if (config->default_group < -1
	    || config->default_group >=
	    xkl_engine_get_max_num_groups (config->engine))
		config->default_group = -1;
	xkl_debug (150, "default_group: %d\n", config->default_group);
}

void
gswitchit_config_save_to_gconf (GSwitchItConfig * config)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	gconf_change_set_set_bool (cs,
				   GSWITCHIT_CONFIG_KEY_GROUP_PER_WINDOW,
				   config->group_per_app);
	gconf_change_set_set_bool (cs,
				   GSWITCHIT_CONFIG_KEY_HANDLE_INDICATORS,
				   config->handle_indicators);
	gconf_change_set_set_bool (cs,
				   GSWITCHIT_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES,
				   config->layout_names_as_group_names);
	gconf_change_set_set_int (cs,
				  GSWITCHIT_CONFIG_KEY_DEFAULT_GROUP,
				  config->default_group);

	gconf_client_commit_change_set (config->conf_client, cs, TRUE,
					&gerror);
	if (gerror != NULL) {
		g_warning ("Error saving active configuration: %s\n",
			   gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}
	gconf_change_set_unref (cs);
}

gboolean
gswitchit_config_activate (GSwitchItConfig * config)
{
	gboolean rv = TRUE;

	xkl_engine_set_group_per_toplevel_window (config->engine,
						  config->group_per_app);
	xkl_engine_set_indicators_handling (config->engine,
					    config->handle_indicators);
	xkl_engine_set_default_group (config->engine,
				      config->default_group);

	return rv;
}

void
gswitchit_config_lock_next_group (GSwitchItConfig * config)
{
	int group = xkl_engine_get_next_group (config->engine);
	xkl_engine_lock_group (config->engine, group);
}

void
gswitchit_config_lock_prev_group (GSwitchItConfig * config)
{
	int group = xkl_engine_get_prev_group (config->engine);
	xkl_engine_lock_group (config->engine, group);
}

void
gswitchit_config_restore_group (GSwitchItConfig * config)
{
	int group = xkl_engine_get_current_window_group (config->engine);
	xkl_engine_lock_group (config->engine, group);
}

void
gswitchit_config_start_listen (GSwitchItConfig * config,
			       GConfClientNotifyFunc func,
			       gpointer user_data)
{
	gswitchit_config_add_listener (config->conf_client,
				       GSWITCHIT_CONFIG_DIR, func,
				       user_data,
				       &config->config_listener_id);
}

void
gswitchit_config_stop_listen (GSwitchItConfig * config)
{
	gswitchit_config_remove_listener (config->conf_client,
					  &config->config_listener_id);
}

/**
 * extern GSwitchItKbdConfig config functions
 */
void
gswitchit_kbd_config_init (GSwitchItKbdConfig * kbd_config,
			   GConfClient * conf_client, XklEngine * engine)
{
	GError *gerror = NULL;

	memset (kbd_config, 0, sizeof (*kbd_config));
	kbd_config->conf_client = conf_client;
	kbd_config->engine = engine;
	g_object_ref (kbd_config->conf_client);

	gconf_client_add_dir (kbd_config->conf_client,
			      GSWITCHIT_KBD_CONFIG_DIR,
			      GCONF_CLIENT_PRELOAD_NONE, &gerror);
	if (gerror != NULL) {
		g_warning ("err: %s\n", gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}
}

void
gswitchit_kbd_config_term (GSwitchItKbdConfig * kbd_config)
{
	gswitchit_kbd_config_model_set (kbd_config, NULL);

	gswitchit_kbd_config_layouts_reset (kbd_config);

	g_object_unref (kbd_config->conf_client);
	kbd_config->conf_client = NULL;
}

void
gswitchit_kbd_config_load_from_gconf (GSwitchItKbdConfig * kbd_config,
				      GSwitchItKbdConfig *
				      kbd_config_default)
{
	gswitchit_kbd_config_load_params (kbd_config,
					  GSWITCHIT_KBD_CONFIG_ACTIVE);

	if (kbd_config_default != NULL) {
		GSList *pl;

		if (kbd_config->model == NULL)
			kbd_config->model =
			    g_strdup (kbd_config_default->model);

		if (kbd_config->layouts == NULL) {
			pl = kbd_config_default->layouts;
			while (pl != NULL) {
				kbd_config->layouts =
				    g_slist_append (kbd_config->layouts,
						    g_strdup (pl->data));
				pl = pl->next;
			}
		}

		if (kbd_config->options == NULL) {
			pl = kbd_config_default->options;
			while (pl != NULL) {
				kbd_config->options =
				    g_slist_append (kbd_config->options,
						    g_strdup (pl->data));
				pl = pl->next;
			}
		}
	}
}

void
gswitchit_kbd_config_load_from_gconf_backup (GSwitchItKbdConfig *
					     kbd_config)
{
	gswitchit_kbd_config_load_params (kbd_config,
					  GSWITCHIT_KBD_CONFIG_SYSBACKUP);
}

void
gswitchit_kbd_config_load_from_x_current (GSwitchItKbdConfig * kbd_config)
{
	XklConfigRec *data = xkl_config_rec_new ();
	if (xkl_config_rec_get_from_server (data, kbd_config->engine))
		gswitchit_kbd_config_copy_from_xkl_config (kbd_config,
							   data);
	else
		xkl_debug (150,
			   "Could not load keyboard config from server: [%s]\n",
			   xkl_get_last_error ());
	g_object_unref (G_OBJECT (data));
}

void
gswitchit_kbd_config_load_from_x_initial (GSwitchItKbdConfig * kbd_config)
{
	XklConfigRec *data = xkl_config_rec_new ();
	if (xkl_config_rec_get_from_backup (data, kbd_config->engine))
		gswitchit_kbd_config_copy_from_xkl_config (kbd_config,
							   data);
	else
		xkl_debug (150,
			   "Could not load keyboard config from backup: [%s]\n",
			   xkl_get_last_error ());
	g_object_unref (G_OBJECT (data));
}

gboolean
gswitchit_kbd_config_equals (GSwitchItKbdConfig * kbd_config1,
			     GSwitchItKbdConfig * kbd_config2)
{
	if (kbd_config1 == kbd_config2)
		return True;
	if ((kbd_config1->model != kbd_config2->model) &&
	    (kbd_config1->model != NULL) &&
	    (kbd_config2->model != NULL) &&
	    g_ascii_strcasecmp (kbd_config1->model, kbd_config2->model))
		return False;
	return gslist_str_equal (kbd_config1->layouts,
				 kbd_config2->layouts)
	    && gslist_str_equal (kbd_config1->options,
				 kbd_config2->options);
}

void
gswitchit_kbd_config_save_to_gconf (GSwitchItKbdConfig * kbd_config)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	gswitchit_kbd_config_save_params (kbd_config, cs,
					  GSWITCHIT_KBD_CONFIG_ACTIVE);

	gconf_client_commit_change_set (kbd_config->conf_client, cs, TRUE,
					&gerror);
	if (gerror != NULL) {
		g_warning ("Error saving active configuration: %s\n",
			   gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}
	gconf_change_set_unref (cs);
}

void
gswitchit_kbd_config_save_to_gconf_backup (GSwitchItKbdConfig * kbd_config)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	gswitchit_kbd_config_save_params (kbd_config, cs,
					  GSWITCHIT_KBD_CONFIG_SYSBACKUP);

	gconf_client_commit_change_set (kbd_config->conf_client, cs, TRUE,
					&gerror);
	if (gerror != NULL) {
		g_warning ("Error saving backup configuration: %s\n",
			   gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}
	gconf_change_set_unref (cs);
}

void
gswitchit_kbd_config_model_set (GSwitchItKbdConfig * kbd_config,
				const gchar * model_name)
{
	if (kbd_config->model != NULL)
		g_free (kbd_config->model);
	kbd_config->model =
	    (model_name == NULL
	     || model_name[0] == '\0') ? NULL : g_strdup (model_name);
}

void
gswitchit_kbd_config_layouts_add (GSwitchItKbdConfig * kbd_config,
				  const gchar * layout_name,
				  const gchar * variant_name)
{
	const gchar *merged;
	if (layout_name == NULL)
		return;
	merged =
	    gswitchit_kbd_config_merge_items (layout_name, variant_name);
	if (merged == NULL)
		return;
	gswitchit_kbd_config_layouts_add_full (kbd_config, merged);
}

void
gswitchit_kbd_config_layouts_reset (GSwitchItKbdConfig * kbd_config)
{
	gswitchit_config_string_list_reset (&kbd_config->layouts);
}

void
gswitchit_kbd_config_options_reset (GSwitchItKbdConfig * kbd_config)
{
	gswitchit_config_string_list_reset (&kbd_config->options);
}

void
gswitchit_kbd_config_options_add (GSwitchItKbdConfig * kbd_config,
				  const gchar * group_name,
				  const gchar * option_name)
{
	const gchar *merged;
	if (group_name == NULL || option_name == NULL)
		return;
	merged =
	    gswitchit_kbd_config_merge_items (group_name, option_name);
	if (merged == NULL)
		return;
	gswitchit_kbd_config_options_add_full (kbd_config, merged);
}

gboolean
gswitchit_kbd_config_options_is_set (GSwitchItKbdConfig * kbd_config,
				     const gchar * group_name,
				     const gchar * option_name)
{
	const gchar *merged =
	    gswitchit_kbd_config_merge_items (group_name, option_name);
	if (merged == NULL)
		return FALSE;

	return NULL != g_slist_find_custom (kbd_config->options, (gpointer)
					    merged, (GCompareFunc)
					    g_ascii_strcasecmp);
}

gboolean
gswitchit_kbd_config_activate (GSwitchItKbdConfig * kbd_config)
{
	gboolean rv;
	XklConfigRec *data = xkl_config_rec_new ();

	gswitchit_kbd_config_copy_to_xkl_config (kbd_config, data);
	rv = xkl_config_rec_activate (data, kbd_config->engine);
	g_object_unref (G_OBJECT (data));

	return rv;
}

void
gswitchit_kbd_config_start_listen (GSwitchItKbdConfig * kbd_config,
				   GConfClientNotifyFunc func,
				   gpointer user_data)
{
	gswitchit_config_add_listener (kbd_config->conf_client,
				       GSWITCHIT_KBD_CONFIG_DIR, func,
				       user_data,
				       &kbd_config->config_listener_id);
}

void
gswitchit_kbd_config_stop_listen (GSwitchItKbdConfig * kbd_config)
{
	gswitchit_config_remove_listener (kbd_config->conf_client,
					  &kbd_config->config_listener_id);
}

gboolean
gswitchit_kbd_config_get_descriptions (XklConfigRegistry * config_registry,
				       const gchar * name,
				       gchar ** layout_short_descr,
				       gchar ** layout_descr,
				       gchar ** variant_short_descr,
				       gchar ** variant_descr)
{
	char *layout_name = NULL, *variant_name = NULL;
	if (!gswitchit_kbd_config_split_items
	    (name, &layout_name, &variant_name))
		return FALSE;
	return gswitchit_kbd_config_get_lv_descriptions (config_registry,
							 layout_name,
							 variant_name,
							 layout_short_descr,
							 layout_descr,
							 variant_short_descr,
							 variant_descr);
}

const gchar *
gswitchit_kbd_config_format_full_layout (const gchar * layout_descr,
					 const gchar * variant_descr)
{
	static gchar full_descr[XKL_MAX_CI_DESC_LENGTH * 2];
	if (variant_descr == NULL)
		g_snprintf (full_descr, sizeof (full_descr), "%s",
			    layout_descr);
	else
		g_snprintf (full_descr, sizeof (full_descr), "%s %s",
			    layout_descr, variant_descr);
	return full_descr;
}

gboolean
gswitchit_config_load_remote_group_descriptions_utf8 (GSwitchItConfig *
						      config,
						      gchar ***
						      short_group_names,
						      gchar ***
						      full_group_names)
{
	gchar **sld, **lld, **svd, **lvd;
	gchar **psld, **plld, **plvd;
	gchar **psgn, **pfgn;
	gint total_descriptions;

	if (!gswitchit_config_get_remote_lv_descriptions_utf8
	    (&sld, &lld, &svd, &lvd)) {
		return False;
	}

	total_descriptions = g_strv_length (sld);

	*short_group_names = psgn =
	    g_new0 (gchar *, total_descriptions + 1);
	*full_group_names = pfgn =
	    g_new0 (gchar *, total_descriptions + 1);

	plld = lld;
	psld = sld;
	plvd = lvd;
	while (plld != NULL && *plld != NULL) {
		*psgn++ = g_strdup (*psld++);
		*pfgn++ = g_strdup (gswitchit_kbd_config_format_full_layout
				    (*plld++, *plvd++));
	}
	g_strfreev (sld);
	g_strfreev (lld);
	g_strfreev (svd);
	g_strfreev (lvd);

	return True;
}

gchar **
gswitchit_config_load_group_descriptions_utf8 (GSwitchItConfig * config,
					       XklConfigRegistry *
					       config_registry)
{
	int i;
	const gchar **native_names =
	    xkl_engine_get_groups_names (config->engine);
	guint total_groups = xkl_engine_get_num_groups (config->engine);
	guint total_layouts;
	gchar **rv = g_new0 (char *, total_groups + 1);
	gchar **current_descr = rv;

	if ((xkl_engine_get_features (config->engine) &
	     XKLF_MULTIPLE_LAYOUTS_SUPPORTED)
	    && config->layout_names_as_group_names) {
		XklConfigRec *xkl_config = xkl_config_rec_new ();
		if (xkl_config_rec_get_from_server
		    (xkl_config, config->engine)) {
			char **pl = xkl_config->layouts;
			char **pv = xkl_config->variants;
			i = total_groups;
			while (pl != NULL && *pl != NULL && i >= 0) {
				char *ls_descr;
				char *l_descr;
				char *vs_descr;
				char *v_descr;
				if (gswitchit_kbd_config_get_lv_descriptions (config_registry, *pl++, *pv++, &ls_descr, &l_descr, &vs_descr, &v_descr)) {
					char *name_utf =
					    g_locale_to_utf8
					    (gswitchit_kbd_config_format_full_layout
					     (l_descr, v_descr), -1, NULL,
					     NULL, NULL);
					*current_descr++ = name_utf;
				} else {
					*current_descr++ = g_strdup ("");
				}
			}
		}
		g_object_unref (G_OBJECT (xkl_config));
		/* Worst case - multiple layous - but SOME of them are multigrouped :((( 
		   We cannot do much - just add empty descriptions. 
		   The UI is going to be messy. 
		   Canadian layouts are famous for this sh.t. */
		total_layouts = g_strv_length (rv);
		if (total_layouts != total_groups) {
			xkl_debug (0,
				   "The mismatch between "
				   "the number of groups: %d and number of layouts: %d\n",
				   total_groups, total_layouts);
			current_descr = rv + total_layouts;
			for (i = total_groups - total_layouts; --i >= 0;)
				*current_descr++ = g_strdup ("");
		}
	}
	total_layouts = g_strv_length (rv);
	if (!total_layouts)
		for (i = total_groups; --i >= 0;)
			*current_descr++ = g_strdup (*native_names++);

	return rv;
}

gchar *
gswitchit_kbd_config_to_string (const GSwitchItKbdConfig * config)
{
	gchar *layouts = NULL, *options = NULL;
	GString *buffer = g_string_new (NULL);

	GSList *iter;
	gint count;
	gchar *result;

	if (config->layouts) {
		/* g_slist_length is "expensive", so we determinate the length on the fly */
		for (iter = config->layouts, count = 0; iter;
		     iter = iter->next, ++count) {
			if (buffer->len)
				g_string_append (buffer, " ");

			g_string_append (buffer,
					 (const gchar *) iter->data);
		}

		/* TRANS: The count is related to the number of options. The %s format specifier should not be modified,
		 * left "as is". */
		layouts =
		    g_strdup_printf (ngettext
				     ("layout \"%s\"", "layouts \"%s\"",
				      count), buffer->str);
		g_string_truncate (buffer, 0);
	}
	if (config->options) {
		/* g_slist_length is "expensive", so we determinate the length on the fly */
		for (iter = config->options, count = 0; iter;
		     iter = iter->next, ++count) {
			if (buffer->len)
				g_string_append (buffer, " ");

			g_string_append (buffer,
					 (const gchar *) iter->data);
		}

		/* TRANS: The count is related to the number of options. The %s format specifier should not be modified,
		 * left "as is". */
		options =
		    g_strdup_printf (ngettext
				     ("option \"%s\"", "options \"%s\"",
				      count), buffer->str);
		g_string_truncate (buffer, 0);
	}

	g_string_free (buffer, TRUE);

	result =
	    g_strdup_printf (_("model \"%s\", %s and %s"), config->model,
			     layouts ? layouts : _("no layout"),
			     options ? options : _("no options"));

	g_free (options);
	g_free (layouts);

	return result;
}

