/*
 *  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 <X11/keysym.h>

#include <gdk/gdkx.h>
#include <gnome.h>
#include <libgnome/gnome-util.h>

#include <libxklavier/xklavier_config.h>

#include "gswitchit_config.h"
#include "gswitchit_config_private.h"

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

#define GSWITCHIT_CONFIG_KEY_PREFIX  KEYBOARD_CONFIG_KEY_PREFIX "/general"

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

/**
 * GSwitchItAppletConfig
 */
#define GSWITCHIT_APPLET_CONFIG_KEY_PREFIX  "/apps/gswitchit/Applet"

const char GSWITCHIT_APPLET_CONFIG_DIR[] = GSWITCHIT_APPLET_CONFIG_KEY_PREFIX;
const char GSWITCHIT_APPLET_CONFIG_KEY_SHOW_FLAGS[] =
    GSWITCHIT_APPLET_CONFIG_KEY_PREFIX "/showFlags";
const char GSWITCHIT_APPLET_CONFIG_KEY_ENABLED_PLUGINS[] =
    GSWITCHIT_APPLET_CONFIG_KEY_PREFIX "/enabledPlugins";
const char GSWITCHIT_APPLET_CONFIG_KEY_SECONDARIES[] =
    GSWITCHIT_APPLET_CONFIG_KEY_PREFIX "/secondary";

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

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

const char *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 char GSWITCHIT_KBD_CONFIG_SYSBACKUP_DIR[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX;
const char GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_MODEL[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/model";
const char GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_LAYOUTS[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/layouts";
const char GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_OPTIONS[] =
    GSWITCHIT_KBD_CONFIG_SYSBACKUP_KEY_PREFIX "/options";

const char *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
_GSwitchItConfigStringListReset (GSList ** plist)
{
	while (*plist != NULL) {
		GSList *p = *plist;
		*plist = (*plist)->next;
		g_free (p->data);
		g_slist_free_1 (p);
	}
}

static Bool
_GSwitchItKbdConfigGetDescriptions (const char *layoutName,
				 const char *variantName,
				 char **layoutShortDescr,
				 char **layoutDescr,
				 char **variantShortDescr,
				 char **variantDescr)
{
	static XklConfigItem litem;
	static XklConfigItem vitem;

	layoutName = g_strdup (layoutName);

	g_snprintf (litem.name, sizeof litem.name, "%s", layoutName);
	if (XklConfigFindLayout (&litem)) {
		*layoutShortDescr = litem.shortDescription;
		*layoutDescr = litem.description;
	} else
		*layoutShortDescr = *layoutDescr = NULL;

	if (variantName != NULL) {
		variantName = g_strdup (variantName);
		g_snprintf (vitem.name, sizeof vitem.name, "%s",
			    variantName);
		if (XklConfigFindVariant (layoutName, &vitem)) {
			*variantShortDescr = vitem.shortDescription;
			*variantDescr = vitem.description;
		} else
			*variantShortDescr = *variantDescr = NULL;

		g_free ((char *) variantName);
	} else
		*variantDescr = NULL;

	g_free ((char *) layoutName);
	return *layoutDescr != NULL;
}

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

static void
_GSwitchItConfigRemoveListener (GConfClient * confClient, int *pid)
{
	if (*pid != 0) {
		gconf_client_notify_remove (confClient, *pid);
		*pid = 0;
	}
}

/**
 * extern common functions
 */
const char *
GSwitchItKbdConfigMergeItems (const char *parent, const char *child)
{
	static char 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
GSwitchItKbdConfigSplitItems (const char *merged, char **parent, char **child)
{
	static char pbuffer[XKL_MAX_CI_NAME_LENGTH];
	static char cbuffer[XKL_MAX_CI_NAME_LENGTH];
	int plen, clen;
	const char *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 applet config functions
 */
static void
_GSwitchItAppletConfigFreeEnabledPlugins (GSwitchItAppletConfig *
					  appletConfig)
{
	GSList *pluginNode = appletConfig->enabledPlugins;
	if (pluginNode != NULL) {
		do {
			if (pluginNode->data != NULL) {
				g_free (pluginNode->data);
				pluginNode->data = NULL;
			}
			pluginNode = g_slist_next (pluginNode);
		} while (pluginNode != NULL);
		g_slist_free (appletConfig->enabledPlugins);
		appletConfig->enabledPlugins = NULL;
	}
}

/**
 * static GSwitchItKbdConfig functions
 */
static void
_GSwitchItKbdConfigOptionsAdd (GSwitchItKbdConfig * kbdConfig,
			       const gchar * fullOptionName)
{
	kbdConfig->options =
	    g_slist_append (kbdConfig->options, g_strdup (fullOptionName));
}

static void
_GSwitchItKbdConfigLayoutsAdd (GSwitchItKbdConfig * kbdConfig,
			       const gchar * fullLayoutName)
{
	kbdConfig->layouts =
	    g_slist_append (kbdConfig->layouts, g_strdup (fullLayoutName));
}

static void
_GSwitchItKbdConfigCopyFromXklConfig (GSwitchItKbdConfig * kbdConfig,
				      XklConfigRec * pdata)
{
	int i;
	char **p, **p1;
	GSwitchItKbdConfigModelSet (kbdConfig, pdata->model);
	XklDebug (150, "Loaded Kbd model: [%s]\n", pdata->model);

	GSwitchItKbdConfigLayoutsReset (kbdConfig);
	p = pdata->layouts;
	p1 = pdata->variants;
	for (i = pdata->numLayouts; --i >= 0;) {
		if (*p1 == NULL || **p1 == '\0') {
			XklDebug (150, "Loaded Kbd layout: [%s]\n", *p);
			_GSwitchItKbdConfigLayoutsAdd (kbdConfig, *p);
		} else {
			char fullLayout[XKL_MAX_CI_NAME_LENGTH * 2];
			g_snprintf (fullLayout, sizeof (fullLayout),
				    "%s\t%s", *p, *p1);
			XklDebug (150,
				  "Loaded Kbd layout with variant: [%s]\n",
				  fullLayout);
			_GSwitchItKbdConfigLayoutsAdd (kbdConfig,
						       fullLayout);
		}
		p++;
		p1++;
	}

	GSwitchItKbdConfigOptionsReset (kbdConfig);
	p = pdata->options;
	for (i = pdata->numOptions; --i >= 0;) {
		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;
			XklDebug (150, "Loaded Kbd option: [%s][%s]\n",
				  group, option);
			GSwitchItKbdConfigOptionsAdd (kbdConfig, group,
						      option);
		}
		p++;
	}
}

static void
_GSwitchItKbdConfigCopyToXklConfig (GSwitchItKbdConfig * kbdConfig,
				    XklConfigRec * pdata)
{
	int i;
	pdata->model =
	    (kbdConfig->model == NULL) ? NULL : strdup (kbdConfig->model);

	pdata->numLayouts = pdata->numVariants =
	    (kbdConfig->layouts ==
	     NULL) ? 0 : g_slist_length (kbdConfig->layouts);
	pdata->numOptions =
	    (kbdConfig->options ==
	     NULL) ? 0 : g_slist_length (kbdConfig->options);

	XklDebug (150, "Taking %d layouts\n", pdata->numLayouts);
	if (pdata->numLayouts != 0) {
		GSList *theLayout = kbdConfig->layouts;
		char **p1 = pdata->layouts =
		    calloc ((sizeof (char *)) * pdata->numLayouts, 1);
		char **p2 = pdata->variants =
		    calloc ((sizeof (char *)) * pdata->numVariants, 1);
		for (i = pdata->numLayouts; --i >= 0;) {
			char *layout, *variant;
			if (GSwitchItKbdConfigSplitItems
			    (theLayout->data, &layout, &variant)
			    && variant != NULL) {
				*p1 =
				    (layout ==
				     NULL) ? NULL : strdup (layout);
				*p2 =
				    (variant ==
				     NULL) ? NULL : strdup (variant);
			} else {
				*p1 =
				    (theLayout->data ==
				     NULL) ? NULL : strdup (theLayout->
							    data);
				*p2 = NULL;
			}
			XklDebug (150, "Adding [%s]/%p and [%s]/%p\n",
				  *p1 ? *p1 : "(nil)", *p1,
				  *p2 ? *p2 : "(nil)", *p2);
			p1++;
			p2++;
			theLayout = theLayout->next;
		}
	}

	if (pdata->numOptions != 0) {
		GSList *theOption = kbdConfig->options;
		char **p = pdata->options =
		    calloc ((sizeof (char *)) * pdata->numOptions, 1);
		for (i = pdata->numOptions; --i >= 0;) {
			char *group, *option;
			if (GSwitchItKbdConfigSplitItems
			    (theOption->data, &group, &option)
			    && option != NULL)
				*(p++) = strdup (option);
			else
				XklDebug (150, "Could not split [%s]\n",
					  theOption->data);
			theOption = theOption->next;
		}
	}
}

static gboolean
_GSListStrEqual (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
_GSwitchItKbdConfigLoadParams (GSwitchItKbdConfig * kbdConfig,
			       const char *paramNames[])
{
	GError *gerror = NULL;
	gchar *pc;
	GSList *pl;

	pc = gconf_client_get_string (kbdConfig->confClient,
				      paramNames[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;
		}
		GSwitchItKbdConfigModelSet (kbdConfig, NULL);
	} else {
		GSwitchItKbdConfigModelSet (kbdConfig, pc);
		g_free (pc);
	}
	XklDebug (150, "Loaded Kbd model: [%s]\n", kbdConfig->model ? kbdConfig->model : "(null)");

	GSwitchItKbdConfigLayoutsReset (kbdConfig);

	pl = gconf_client_get_list (kbdConfig->confClient,
				    paramNames[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) {
		XklDebug (150, "Loaded Kbd layout: [%s]\n", pl->data);
		_GSwitchItKbdConfigLayoutsAdd (kbdConfig, pl->data);
		pl = pl->next;
	}
	_GSwitchItConfigStringListReset(&pl);

	GSwitchItKbdConfigOptionsReset (kbdConfig);

	pl = gconf_client_get_list (kbdConfig->confClient,
				    paramNames[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) {
		XklDebug (150, "Loaded Kbd option: [%s]\n", pl->data);
		_GSwitchItKbdConfigOptionsAdd (kbdConfig,
					       (const char *) pl->data);
		pl = pl->next;
	}
	_GSwitchItConfigStringListReset(&pl);
}

static void
_GSwitchItKbdConfigSaveParams (GSwitchItKbdConfig * kbdConfig,
			       GConfChangeSet * cs,
			       const char *paramNames[])
{
	GSList *pl;

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

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

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

/**
 * extern GSwitchItConfig config functions
 */
void
GSwitchItConfigInit (GSwitchItConfig * config,
		     GConfClient * confClient)
{
	GError *gerror = NULL;

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

	gconf_client_add_dir (config->confClient,
			      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
GSwitchItConfigTerm (GSwitchItConfig * config)
{
	g_object_unref (config->confClient);
	config->confClient = NULL;
}

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

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

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

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

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

	if (config->defaultGroup < -1
	    || config->defaultGroup >= XklGetMaxNumGroups ())
		config->defaultGroup = -1;
	XklDebug (150, "defaultGroup: %d\n", config->defaultGroup);
}

void
GSwitchItConfigSaveToGConf (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->groupPerApp);
	gconf_change_set_set_bool (cs,
				   GSWITCHIT_CONFIG_KEY_HANDLE_INDICATORS,
				   config->handleIndicators);
	gconf_change_set_set_bool (cs,
				   GSWITCHIT_CONFIG_KEY_LAYOUT_NAMES_AS_GROUP_NAMES,
				   config->layoutNamesAsGroupNames);
	gconf_change_set_set_int (cs,
				  GSWITCHIT_CONFIG_KEY_DEFAULT_GROUP,
				  config->defaultGroup);

	gconf_client_commit_change_set (config->confClient, 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
GSwitchItConfigActivate (GSwitchItConfig * config)
{
	gboolean rv = TRUE;

	XklSetGroupPerApp (config->groupPerApp);
	XklSetIndicatorsHandling (config->handleIndicators);
	XklSetDefaultGroup (config->defaultGroup);

	return rv;
}

void
GSwitchItConfigLockNextGroup (void)
{
	int group = XklGetNextGroup ();
	XklLockGroup (group);
}

void
GSwitchItConfigLockPrevGroup (void)
{
	int group = XklGetPrevGroup ();
	XklLockGroup (group);
}

void
GSwitchItConfigRestoreGroup (void)
{
	int group = XklGetRestoreGroup ();
	XklLockGroup (group);
}

void
GSwitchItConfigStartListen (GSwitchItConfig * config,
			    GConfClientNotifyFunc func,
			    gpointer userData)
{
	_GSwitchItConfigAddListener (config->confClient,
				     GSWITCHIT_CONFIG_DIR, func,
				     userData, &config->configListenerId);
}

void
GSwitchItConfigStopListen (GSwitchItConfig * config)
{
	_GSwitchItConfigRemoveListener (config->confClient,
					&config->configListenerId);
}
/**
 * extern GSwitchItKbdConfig config functions
 */
void
GSwitchItKbdConfigInit (GSwitchItKbdConfig * kbdConfig,
			GConfClient * confClient)
{
	GError *gerror = NULL;

	memset (kbdConfig, 0, sizeof (*kbdConfig));
	kbdConfig->confClient = confClient;
	g_object_ref (kbdConfig->confClient);

	gconf_client_add_dir (kbdConfig->confClient,
			      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
GSwitchItKbdConfigTerm (GSwitchItKbdConfig * kbdConfig)
{
	GSwitchItKbdConfigModelSet (kbdConfig, NULL);

	GSwitchItKbdConfigLayoutsReset (kbdConfig);

	g_object_unref (kbdConfig->confClient);
	kbdConfig->confClient = NULL;
}

void
GSwitchItKbdConfigLoadFromGConf (GSwitchItKbdConfig * kbdConfig,
				 GSwitchItKbdConfig * kbdConfigDefault)
{
	_GSwitchItKbdConfigLoadParams (kbdConfig,
				       GSWITCHIT_KBD_CONFIG_ACTIVE);

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

		if (kbdConfig->model == NULL)
			kbdConfig->model = g_strdup (kbdConfigDefault->model);

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

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

void
GSwitchItKbdConfigLoadFromGConfBackup (GSwitchItKbdConfig * kbdConfig)
{
	_GSwitchItKbdConfigLoadParams (kbdConfig,
				       GSWITCHIT_KBD_CONFIG_SYSBACKUP);
}

void
GSwitchItKbdConfigLoadFromXCurrent (GSwitchItKbdConfig * kbdConfig)
{
	XklConfigRec data;
	XklConfigRecInit (&data);
	if (XklConfigGetFromServer (&data))
		_GSwitchItKbdConfigCopyFromXklConfig (kbdConfig, &data);
	else
		XklDebug (150,
			  "Could not load keyboard config from server: [%s]\n",
			  XklGetLastError ());
	XklConfigRecDestroy (&data);
}

void
GSwitchItKbdConfigLoadFromXInitial (GSwitchItKbdConfig * kbdConfig)
{
	XklConfigRec data;
	XklConfigRecInit (&data);
	if (XklConfigGetFromBackup (&data))
		_GSwitchItKbdConfigCopyFromXklConfig (kbdConfig, &data);
	else
		XklDebug (150,
			  "Could not load keyboard config from backup: [%s]\n",
			  XklGetLastError ());
	XklConfigRecDestroy (&data);
}

gboolean
GSwitchItKbdConfigEquals (GSwitchItKbdConfig * kbdConfig1,
			  GSwitchItKbdConfig * kbdConfig2)
{
	if (kbdConfig1 == kbdConfig2)
		return True;
	if ((kbdConfig1->model != kbdConfig2->model) &&
	    (kbdConfig1->model != NULL) &&
	    (kbdConfig2->model != NULL) &&
	    g_ascii_strcasecmp (kbdConfig1->model, kbdConfig2->model))
		return False;
	return _GSListStrEqual (kbdConfig1->layouts, kbdConfig2->layouts)
	    && _GSListStrEqual (kbdConfig1->options, kbdConfig2->options);
}

void
GSwitchItKbdConfigSaveToGConf (GSwitchItKbdConfig * kbdConfig)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	_GSwitchItKbdConfigSaveParams (kbdConfig, cs,
				       GSWITCHIT_KBD_CONFIG_ACTIVE);

	gconf_client_commit_change_set (kbdConfig->confClient, 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
GSwitchItKbdConfigSaveToGConfBackup (GSwitchItKbdConfig * kbdConfig)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	_GSwitchItKbdConfigSaveParams (kbdConfig, cs,
				       GSWITCHIT_KBD_CONFIG_SYSBACKUP);

	gconf_client_commit_change_set (kbdConfig->confClient, 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
GSwitchItKbdConfigModelSet (GSwitchItKbdConfig * kbdConfig,
			    const gchar * modelName)
{
	if (kbdConfig->model != NULL)
		g_free (kbdConfig->model);
	kbdConfig->model =
	    (modelName == NULL || modelName[0] == '\0') ? NULL : g_strdup (modelName);
}

void
GSwitchItKbdConfigLayoutsAdd (GSwitchItKbdConfig * kbdConfig,
			      const gchar * layoutName,
			      const gchar * variantName)
{
	const char *merged;
	if (layoutName == NULL)
		return;
	merged = GSwitchItKbdConfigMergeItems (layoutName, variantName);
	if (merged == NULL)
		return;
	_GSwitchItKbdConfigLayoutsAdd (kbdConfig, merged);
}

void
GSwitchItKbdConfigLayoutsReset (GSwitchItKbdConfig * kbdConfig)
{
	_GSwitchItConfigStringListReset (&kbdConfig->layouts);
}

void
GSwitchItKbdConfigOptionsReset (GSwitchItKbdConfig * kbdConfig)
{
	_GSwitchItConfigStringListReset (&kbdConfig->options);
}

void
GSwitchItKbdConfigOptionsAdd (GSwitchItKbdConfig * kbdConfig,
			      const gchar * groupName,
			      const gchar * optionName)
{
	const char *merged;
	if (groupName == NULL || optionName == NULL)
		return;
	merged = GSwitchItKbdConfigMergeItems (groupName, optionName);
	if (merged == NULL)
		return;
	_GSwitchItKbdConfigOptionsAdd (kbdConfig, merged);
}

gboolean
GSwitchItKbdConfigOptionsIsSet (GSwitchItKbdConfig * kbdConfig,
				const gchar * groupName,
				const gchar * optionName)
{
	const char *merged =
	    GSwitchItKbdConfigMergeItems (groupName, optionName);
	if (merged == NULL)
		return FALSE;

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

gboolean
GSwitchItKbdConfigActivate (GSwitchItKbdConfig * kbdConfig)
{
	gboolean rv;

	XklConfigRec data;
	XklConfigRecInit (&data);

	_GSwitchItKbdConfigCopyToXklConfig (kbdConfig, &data);

	rv = XklConfigActivate (&data);
	XklConfigRecDestroy (&data);

	return rv;
}

void
GSwitchItKbdConfigStartListen (GSwitchItKbdConfig * kbdConfig,
			       GConfClientNotifyFunc func,
			       gpointer userData)
{
	_GSwitchItConfigAddListener (kbdConfig->confClient,
				     GSWITCHIT_KBD_CONFIG_DIR, func,
				     userData, &kbdConfig->configListenerId);
}

void
GSwitchItKbdConfigStopListen (GSwitchItKbdConfig * kbdConfig)
{
	_GSwitchItConfigRemoveListener (kbdConfig->confClient,
					&kbdConfig->configListenerId);
}

/**
 * extern applet kbdConfig functions
 */
void
GSwitchItAppletConfigFreeImages (GSwitchItAppletConfig * appletConfig)
{
	GdkPixbuf *pi;
	GSList* imgNode;
	while ((imgNode = appletConfig->images) != NULL)
	{
		pi = GDK_PIXBUF (imgNode->data);
		/* It can be NULL - some images may be missing */
		if (pi != NULL) {
			gdk_pixbuf_unref (pi);
		}
		appletConfig->images = g_slist_remove_link (appletConfig->images, imgNode);
		g_slist_free_1 (imgNode);
	}
}

char *
GSwitchItAppletConfigGetImagesFile (GSwitchItAppletConfig *
				    appletConfig,
				    GSwitchItKbdConfig *
				    kbdConfig, int group)
{
	char *imageFile = NULL;
	GtkIconInfo *iconInfo = NULL;

	if (!appletConfig->showFlags)
		return NULL;

	if ((kbdConfig->layouts != NULL) &&
	    (g_slist_length (kbdConfig->layouts) > group)) {
		char *fullLayoutName =
		    (char *) g_slist_nth_data (kbdConfig->layouts, group);

		if (fullLayoutName != NULL) {
			char *l, *v;
			GSwitchItKbdConfigSplitItems (fullLayoutName, &l, &v);
			if (l != NULL) {
				// probably there is something in theme?
				iconInfo = gtk_icon_theme_lookup_icon
				    (appletConfig->iconTheme, l, 48, 0);
			}
		}
	}
	// fallback to the default value
	if (iconInfo == NULL) {
		iconInfo = gtk_icon_theme_lookup_icon
		    (appletConfig->iconTheme, "applet-error", 48, 0);
	}
	if (iconInfo != NULL) {
		imageFile =
		    g_strdup (gtk_icon_info_get_filename (iconInfo));
		gtk_icon_info_free (iconInfo);
	}

	return imageFile;
}

void
GSwitchItAppletConfigLoadImages (GSwitchItAppletConfig * appletConfig,
				 GSwitchItKbdConfig * kbdConfig)
{
	int i;
	appletConfig->images = NULL;

	if (!appletConfig->showFlags)
		return;

	for (i = XklGetMaxNumGroups (); --i >= 0;) {
		GdkPixbuf *image = NULL;
		char *imageFile =
		    GSwitchItAppletConfigGetImagesFile (appletConfig,
							kbdConfig, i);

		if (imageFile != NULL) {
			GError *err = NULL;
			image =
			    gdk_pixbuf_new_from_file (imageFile, &err);
			if (image == NULL) {
				GtkWidget *dialog = gtk_message_dialog_new (NULL, 
									    GTK_DIALOG_DESTROY_WITH_PARENT,
									    GTK_MESSAGE_ERROR,
									    GTK_BUTTONS_OK,
									    _("There was an error loading an image: %s"),
									    err->message);
				g_signal_connect (G_OBJECT (dialog),
						  "response",
						  G_CALLBACK (gtk_widget_destroy), NULL);

				gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

				gtk_widget_show (dialog);
				g_error_free (err);
			}
			XklDebug (150,
				  "Image %d[%s] loaded -> %p[%dx%d]\n",
				  i, imageFile, image,
				  gdk_pixbuf_get_width (image),
				  gdk_pixbuf_get_height (image));
			g_free (imageFile);
		}
		/* We append the image anyway - even if it is NULL! */
		appletConfig->images = g_slist_prepend (appletConfig->images, image);
	}
}

void
GSwitchItAppletConfigInit (GSwitchItAppletConfig * appletConfig,
			   GConfClient * confClient)
{
	GError *gerror = NULL;
	gchar *sp, *datadir;

	memset (appletConfig, 0, sizeof (*appletConfig));
	appletConfig->confClient = confClient;
	g_object_ref (appletConfig->confClient);

	gconf_client_add_dir (appletConfig->confClient,
			      GSWITCHIT_APPLET_CONFIG_DIR,
			      GCONF_CLIENT_PRELOAD_NONE, &gerror);
	if (gerror != NULL) {
		g_warning ("err1:%s\n", gerror->message);
		g_error_free (gerror);
		gerror = NULL;
	}

	appletConfig->iconTheme = gtk_icon_theme_get_default ();

	datadir =
	    gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_APP_DATADIR,
				       "", FALSE, NULL);
	gtk_icon_theme_append_search_path (appletConfig->iconTheme, sp =
					   g_build_filename (g_get_home_dir
							     (),
							     ".icons/flags",
							     NULL));
	g_free (sp);

	gtk_icon_theme_append_search_path (appletConfig->iconTheme,
					   sp =
					   g_build_filename (datadir,
							     "pixmaps/flags",
							     NULL));
	g_free (sp);

	gtk_icon_theme_append_search_path (appletConfig->iconTheme,
					   sp =
					   g_build_filename (datadir,
							     "icons/flags",
							     NULL));
	g_free (sp);
	g_free (datadir);
}

void
GSwitchItAppletConfigTerm (GSwitchItAppletConfig * appletConfig)
{
#if 0
	g_object_unref (G_OBJECT (appletConfig->iconTheme));
#endif
	appletConfig->iconTheme = NULL;

	GSwitchItAppletConfigFreeImages (appletConfig);

	_GSwitchItAppletConfigFreeEnabledPlugins (appletConfig);
	g_object_unref (appletConfig->confClient);
	appletConfig->confClient = NULL;
}

void
GSwitchItAppletConfigUpdateImages (GSwitchItAppletConfig * appletConfig,
				   GSwitchItKbdConfig * kbdConfig)
{
	GSwitchItAppletConfigFreeImages (appletConfig);
	GSwitchItAppletConfigLoadImages (appletConfig, kbdConfig);
}

void
GSwitchItAppletConfigLoadFromGConf (GSwitchItAppletConfig * appletConfig)
{
	GError *gerror = NULL;

	appletConfig->secondaryGroupsMask =
	    gconf_client_get_int (appletConfig->confClient,
				  GSWITCHIT_APPLET_CONFIG_KEY_SECONDARIES,
				  &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading configuration:%s\n",
			   gerror->message);
		appletConfig->secondaryGroupsMask = 0;
		g_error_free (gerror);
		gerror = NULL;
	}

	appletConfig->showFlags =
	    gconf_client_get_bool (appletConfig->confClient,
				   GSWITCHIT_APPLET_CONFIG_KEY_SHOW_FLAGS,
				   &gerror);
	if (gerror != NULL) {
		g_warning ("Error reading kbdConfiguration:%s\n",
			   gerror->message);
		appletConfig->showFlags = FALSE;
		g_error_free (gerror);
		gerror = NULL;
	}

	_GSwitchItAppletConfigFreeEnabledPlugins (appletConfig);
	appletConfig->enabledPlugins =
	    gconf_client_get_list (appletConfig->confClient,
				   GSWITCHIT_APPLET_CONFIG_KEY_ENABLED_PLUGINS,
				   GCONF_VALUE_STRING, &gerror);

	if (gerror != NULL) {
		g_warning ("Error reading kbdConfiguration:%s\n",
			   gerror->message);
		appletConfig->enabledPlugins = NULL;
		g_error_free (gerror);
		gerror = NULL;
	}
}

void
GSwitchItAppletConfigSaveToGConf (GSwitchItAppletConfig * appletConfig)
{
	GConfChangeSet *cs;
	GError *gerror = NULL;

	cs = gconf_change_set_new ();

	gconf_change_set_set_int (cs,
				  GSWITCHIT_APPLET_CONFIG_KEY_SECONDARIES,
				  appletConfig->secondaryGroupsMask);
	gconf_change_set_set_bool (cs,
				   GSWITCHIT_APPLET_CONFIG_KEY_SHOW_FLAGS,
				   appletConfig->showFlags);
	gconf_change_set_set_list (cs,
				   GSWITCHIT_APPLET_CONFIG_KEY_ENABLED_PLUGINS,
				   GCONF_VALUE_STRING,
				   appletConfig->enabledPlugins);

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

void
GSwitchItAppletConfigActivate (GSwitchItAppletConfig * appletConfig)
{
	XklSetSecondaryGroupsMask (appletConfig->secondaryGroupsMask);
}

void
GSwitchItAppletConfigStartListen (GSwitchItAppletConfig * appletConfig,
				  GConfClientNotifyFunc func,
				  gpointer userData)
{
	_GSwitchItConfigAddListener (appletConfig->confClient,
				     GSWITCHIT_APPLET_CONFIG_DIR, func,
				     userData, &appletConfig->configListenerId);
}

void
GSwitchItAppletConfigStopListen (GSwitchItAppletConfig * appletConfig)
{
	_GSwitchItConfigRemoveListener (appletConfig->confClient,
					&appletConfig->configListenerId);
}

Bool
GSwitchItKbdConfigGetDescriptions (const char *name,
				char **layoutShortDescr,
				char **layoutDescr,
				char **variantShortDescr,
				char **variantDescr)
{
	char *layoutName = NULL, *variantName = NULL;
	if (!GSwitchItKbdConfigSplitItems (name, &layoutName, &variantName))
		return FALSE;
	return _GSwitchItKbdConfigGetDescriptions (layoutName, variantName,
						layoutShortDescr,
						layoutDescr,
						variantShortDescr,
						variantDescr);
}

const char *
GSwitchItKbdConfigFormatFullLayout (const char *layoutDescr,
				 const char *variantDescr)
{
	static char fullDescr[XKL_MAX_CI_DESC_LENGTH * 2];
	if (variantDescr == NULL)
		g_snprintf (fullDescr, sizeof (fullDescr), "%s",
			    layoutDescr);
	else
		g_snprintf (fullDescr, sizeof (fullDescr), "%s %s",
			    layoutDescr, variantDescr);
	return fullDescr;
}

GSList *
GSwitchItConfigLoadGroupDescriptionsUtf8 (GSwitchItConfig * config)
{
	int i;
	const char **pNativeNames = XklGetGroupNames ();
	GSList *rv = NULL;
	if ((XklGetBackendFeatures() & XKLF_MULTIPLE_LAYOUTS_SUPPORTED) && 
	    config->layoutNamesAsGroupNames) {
		XklConfigRec xklConfig;
		XklConfigRecInit (&xklConfig);
		if (XklConfigGetFromServer (&xklConfig)) {
			char **pl = xklConfig.layouts;
			char **pv = xklConfig.variants;
			for (i = xklConfig.numLayouts; --i >= 0;) {
				char *lSDescr;
				char *lDescr;
				char *vSDescr;
				char *vDescr;
				if (_GSwitchItKbdConfigGetDescriptions
				    (*pl++, *pv++, &lSDescr, &lDescr,
				     &vSDescr, &vDescr)) {
					char *nameUtf =
					    g_locale_to_utf8
					    (GSwitchItKbdConfigFormatFullLayout
					     (lDescr, vDescr), -1, NULL,
					     NULL, NULL);
					rv = g_slist_append (rv, nameUtf);
				} else {
					rv = g_slist_append (rv, g_strdup(""));
				}
			}
		}
		XklConfigRecDestroy (&xklConfig);
		/* 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. */
		if (g_slist_length (rv) != XklGetNumGroups())
		{
			XklDebug (0, "The mismatch between "
				  "the number of groups: %d and number of layouts: %d\n",
				  XklGetNumGroups(), g_slist_length (rv));
			for (i = XklGetNumGroups() - g_slist_length (rv); --i>=0;)
				rv = g_slist_append (rv, g_strdup(""));
		}
	}
	if (rv == NULL)
		for (i = XklGetNumGroups (); --i >= 0;)
			rv = g_slist_append (rv, g_strdup (*pNativeNames++));

	return rv;
}
