/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial
portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP OR SUN MICROSYSTEMS, INC. BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <string>
#include "lexmlconf.h"
#include "IMLog.hh"
#include "IMKeyUtils.hh"
#include "keysyms.h"

#define	EMPTY_NODE_ERROR(n)		LOG_ERROR("<%s> is an empty node.", n)
#define	DUPLICATED_NODE_ERROR(n, p)	LOG_ERROR("<%s> had the duplicated node `<%s>'", p, n)
#define	UNKNOWN_NODE_ERROR(n, p)	LOG_WARNING("<%s> had an unknown node `<%s>'", p, n)
#define	INVALID_NODE_ERROR(n, v)	LOG_ERROR("<%s> is an invalid node. [%s]", n, v)
#define	MISSING_NODE_ERROR(n, p)	LOG_ERROR("<%s> is missing in <%s>", n, p)


struct _LEEntry {
	char *language;
	IIIMLEInfoList *list;
	struct _LEEntry *next;
};
struct _HotKeyEntry {
	char *language;
	HotKeyList *list;
	struct _HotKeyEntry *next;
};
struct _IIIMLEXMLConfPrivate {
	struct _LEEntry     *entries;
	IIIMLELanguageList  *lang_list;
	struct _HotKeyEntry *hotkeys;
};

static IIIMLEInfoList     *iiim_le_info_list_new     (IIIMLEInfo         *leinfo);
static IIIMLEInfoList     *iiim_le_info_list_add     (IIIMLEInfoList     *list,
						      IIIMLEInfo         *leinfo);
static IIIMLEInfo         *iiim_le_info_list_find    (IIIMLEInfoList     *list,
						      IIIMLEInfo         *leinfo);
static void                iiim_le_info_list_free    (IIIMLEInfoList     *list);
static IIIMLEInfo         *iiim_le_info_new          (const char         *language,
						      const char         *modulename);
static void                iiim_le_info_free         (IIIMLEInfo         *leinfo);
static IIIMLELanguageList *iiim_le_lang_list_new     (const char         *langugae);
static IIIMLELanguageList *iiim_le_lang_list_add     (IIIMLELanguageList *list,
						      const char         *language);
static IIIMLELanguageList *iiim_le_lang_list_remove  (IIIMLELanguageList *list,
						      const char         *language);
static void                iiim_le_lang_list_free    (IIIMLELanguageList *list);
static void                iiim_le_xmlconf_remove_hotkeys(IIIMLEXMLConf *conf);

static void        parse_key_node    (IIIMLEXMLConf *conf,
				      xmlNodePtr    &node,
				      std::string   &modname);
static void        parse_module_node (IIIMLEXMLConf *conf,
				      xmlNodePtr    &node,
				      std::string   &language);
static void        parse_le_node     (IIIMLEXMLConf *conf,
				      xmlNodePtr    &node);
static void        parse_les_node    (IIIMLEXMLConf *conf,
				      xmlNodePtr    &node);

IMLog *__imlog = NULL;


static IIIMLEInfoList *
iiim_le_info_list_new(IIIMLEInfo *leinfo)
{
	IIIMLEInfoList *list;

	list = (IIIMLEInfoList *)malloc(sizeof (IIIMLEInfoList) * 1);
	list->data = leinfo;
	list->next = NULL;

	return list;
}

static IIIMLEInfoList *
iiim_le_info_list_add(IIIMLEInfoList *list,
		      IIIMLEInfo     *leinfo)
{
	IIIMLEInfoList *l, *ll;

	l = iiim_le_info_list_new(leinfo);
	if (list != NULL) {
		for (ll = list; ll->next != NULL; ll = ll->next);
		ll->next = l;
		ll = list;
	} else {
		ll = l;
	}

	return ll;
}

static IIIMLEInfoList *
iiim_le_info_list_prepend(IIIMLEInfoList *list,
			  IIIMLEInfo     *leinfo)
{
	IIIMLEInfoList *l;

	l = iiim_le_info_list_new(leinfo);
	if (list != NULL) {
		l->next = list;
	}

	return l;
}

static IIIMLEInfoList *
iiim_le_info_list_remove(IIIMLEInfoList *list,
			 IIIMLEInfo     *leinfo)
{
	IIIMLEInfoList *tmp, *prev = NULL;

	tmp = list;
	while (tmp) {
		if (tmp->data == leinfo) {
			if (prev)
				prev->next = tmp->next;
			else
				list = tmp->next;
			iiim_le_info_free(tmp->data);
			free(tmp);

			break;
		}
		prev = tmp;
		tmp = prev->next;
	}

	return list;
}

static IIIMLEInfo *
iiim_le_info_list_find(IIIMLEInfoList *list,
		       IIIMLEInfo     *leinfo)
{
	IIIMLEInfoList *l;

	if (leinfo == NULL || leinfo->language == NULL || leinfo->lename == NULL)
		return NULL;

	for (l = list; l != NULL; l = l->next) {
		IIIMLEInfo *ll = l->data;

		if (!strcmp(ll->language, leinfo->language) &&
		    !strcmp(ll->lename, leinfo->lename)) {
			return ll;
		}
	}

	return NULL;
}

static void
iiim_le_info_list_free(IIIMLEInfoList *list)
{
	IIIMLEInfoList *last;

	while (list) {
		last = list;
		list = last->next;
		if (last->data)
			iiim_le_info_free(last->data);
		free(last);
	}
}

static IIIMLEInfo *
iiim_le_info_new(const char *language,
		 const char *modulename)
{
	IIIMLEInfo *leinfo;

	if (language == NULL || modulename == NULL)
		return NULL;

	leinfo = (IIIMLEInfo *)malloc(sizeof (IIIMLEInfo) * 1);
	leinfo->language = strdup(language);
	leinfo->lename = strdup(modulename);

	return leinfo;
}

static void
iiim_le_info_free(IIIMLEInfo *leinfo)
{
	if (leinfo == NULL)
		return;

	if (leinfo->language)
		free(leinfo->language);
	if (leinfo->lename)
		free(leinfo->lename);
	free(leinfo);
}

static IIIMLELanguageList *
iiim_le_lang_list_new(const char *language)
{
	IIIMLELanguageList *list;

	list = (IIIMLELanguageList *)malloc(sizeof (IIIMLELanguageList) * 1);
	list->language = strdup(language);
	list->next = NULL;

	return list;
}

static IIIMLELanguageList *
iiim_le_lang_list_add(IIIMLELanguageList *list,
		      const char         *language)
{
	IIIMLELanguageList *ll, *prev;

	if (list != NULL) {
		for (ll = list, prev = list; ll != NULL; prev = ll, ll = prev->next) {
			if (!strcmp(ll->language, language)) {
				return list;
			}
		}
		prev->next = iiim_le_lang_list_new(language);
		ll = list;
	} else {
		ll = iiim_le_lang_list_new(language);
	}

	return ll;
}

static IIIMLELanguageList *
iiim_le_lang_list_remove(IIIMLELanguageList *list,
			 const char         *language)
{
	IIIMLELanguageList *tmp, *prev = NULL;

	tmp = list;
	while (tmp) {
		if (!strcmp(tmp->language, language)) {
			if (prev)
				prev->next = tmp->next;
			else
				list = tmp->next;
			if (tmp->language)
				free(tmp->language);
			free(tmp);

			break;
		}
		prev = tmp;
		tmp = prev->next;
	}

	return list;
}

static void
iiim_le_lang_list_free(IIIMLELanguageList *list)
{
	IIIMLELanguageList *last;

	while (list) {
		last = list;
		list = last->next;
		if (last->language)
			free(last->language);
		free(last);
	}
}

static int
_symbol2mask(const char *mod)
{
	struct _modlist {
		char *symbol;
		int id;
	} modlist[] = {
		{"Shift", 1},
		{"Control", 2},
		{"Meta", 4},
		{"Alt", 8},
		{"AltGr", 16},
		{NULL, 0},
	};
	char *m, *p, *pp, *next;
	int retval = 0, i;

	if (mod == NULL)
		return 0;

	m = p = strdup(mod);
	while (p) {
		pp = strchr(p, ' ');
		if (pp != NULL) {
			*pp = 0;
			next = pp + 1;
		} else {
			next = NULL;
		}
		for (i = 0; modlist[i].symbol != NULL; i++) {
			if (!strcasecmp(modlist[i].symbol, p)) {
				retval |= modlist[i].id;
				break;
			}
		}
		p = next;
	}
	free(m);

	return retval;
}

static int
_compare_modifiers(const char *mod1, const char *mod2)
{
	int mask1, mask2;

	mask1 = _symbol2mask(mod1);
	mask2 = _symbol2mask(mod2);

	return mask1 - mask2;
}

static void
parse_key_node(IIIMLEXMLConf *conf,
	       xmlNodePtr    &node,
	       std::string   &language)
{
	xmlChar *mod, *k;
	std::string modifier, key;
	HotKeyStruct *hotkey;

	mod = xmlGetProp(node, (xmlChar *)"modifier");
	k = xmlGetProp(node, (xmlChar *)"name");
	modifier = (const char *)mod;
	key = (const char *)k;
	if (mod != NULL)
		xmlFree(mod);
	if (k != NULL)
		xmlFree(k);
	if (key.empty()) {
		LOG_DEBUG("<key> needs 'name' attribute.");
		return;
	}

	LOG_DEBUG("<key modifier=\"%s\" name=\"%s\">", modifier.c_str(), key.c_str());

	hotkey = iiim_le_hotkey_struct_new(key.c_str(), modifier.c_str());
	iiim_le_xmlconf_append_hotkey(conf, hotkey, language.c_str());

	node = node->xmlChildrenNode;

	while (node != NULL) {
		if (xmlStrcmp(node->name, (xmlChar *)"text") == 0 ||
		    xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
			/* ignore text and comment node */
			node = node->next;
		} else {
			/* ignore the unknown nodes */
			UNKNOWN_NODE_ERROR(node->name, "key");
			node = node->next;
		}
	}
}

static void
parse_hotkey_node(IIIMLEXMLConf *conf,
		  xmlNodePtr    &node,
		  std::string   &language)
{
	xmlNodePtr topnode = NULL;
	std::string path;

	LOG_DEBUG("<hotkey>");

	node = node->xmlChildrenNode;

	while (node != NULL) {
		if (xmlStrcmp(node->name, (xmlChar *)"key") == 0) {
			topnode = node;
			parse_key_node(conf, node, language);
			node = topnode->next;
		} else if (xmlStrcmp(node->name, (xmlChar *)"text") == 0
			   || xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
			/* ignore text and comment node */
			node = node->next;
		} else {
			/* ignore the unknown nodes */
			UNKNOWN_NODE_ERROR(node->name, "hotkey");
			node = node->next;
		}
	}
}

static void
parse_module_node(IIIMLEXMLConf *conf,
		  xmlNodePtr    &node,
		  std::string   &language)
{
	xmlChar *p;
	std::string path;

	p = xmlGetProp(node, (xmlChar *)"path");
	path = (const char *)p;
	if (p != NULL)
		xmlFree(p);
	if (path.empty()) {
		LOG_DEBUG("<module> needs 'path' attribute.");
		return;
	}

	LOG_DEBUG("<module path=\"%s\">", path.c_str());
	iiim_le_xmlconf_append_module(conf, path.c_str(), language.c_str());

	node = node->xmlChildrenNode;

	while (node != NULL) {
		if (xmlStrcmp(node->name, (xmlChar *)"text") == 0
			   || xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
			/* ignore text and comment node */
			node = node->next;
		} else {
			/* ignore the unknown nodes */
			UNKNOWN_NODE_ERROR(node->name, "module");
			node = node->next;
		}
	}
}

static void
parse_le_node(IIIMLEXMLConf *conf, xmlNodePtr &node)
{
	xmlNodePtr topnode = NULL;
	xmlChar *l;
	std::string lang;

	l = xmlGetProp(node, (xmlChar *)"lang");
	lang = (const char *)l;

	if (l != NULL)
		xmlFree(l);
	if (lang.empty()) {
		LOG_DEBUG("<LanguageEngine> needs 'lang' attribute.");
		return;
	}

	node = node->xmlChildrenNode;

	while (node != NULL) {
		if (xmlStrcmp(node->name, (xmlChar *)"module") == 0) {
			topnode = node;
			parse_module_node(conf, node, lang);
			node = topnode->next;
		} else if (xmlStrcmp(node->name, (xmlChar *)"hotkey") == 0) {
			topnode = node;
			parse_hotkey_node(conf, node, lang);
			node = topnode->next;
		} else if (xmlStrcmp(node->name, (xmlChar *)"text") == 0 ||
			   xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
			/* ignore text and comment node */
			node = node->next;
		} else {
			/* ignore the unknown nodes */
			UNKNOWN_NODE_ERROR(node->name, "LanguageEngine");
			node = node->next;
		}
	}
}

static void
parse_les_node(IIIMLEXMLConf *conf, xmlNodePtr &node)
{
	xmlNodePtr topnode = NULL;

	while (node != NULL) {
		if (xmlStrcmp(node->name, (xmlChar *)"LanguageEngine") == 0) {
			topnode = node;
			parse_le_node(conf, node);
			node = topnode->next;
			topnode = NULL;
		} else if (xmlStrcmp(node->name, (xmlChar *)"text") == 0
			   || xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
			/* ignore text and comment node */
			node = node->next;
		} else {
			/* ignore the unknown nodes */
			UNKNOWN_NODE_ERROR(node->name, "LanguageEngines");
			node = node->next;
		}
	}
}

void
iiim_log_init(const char *name)
{
	if (__imlog == NULL)
		__imlog = IMLog::construct(name);
}

void
iiim_log_debug_mode(void)
{
	IMLog::get_instance()->set_log_level(IMLog::DEBUGLOG);
	IMLog::get_instance()->set_default_destination(IMLog::IMLOG_STDERR);
}

IIIMLEXMLConf *
iiim_le_xmlconf_new(const char *filename)
{
	IIIMLEXMLConf *conf;

	/* need to initialize IMLog instance here to use LOG_* macros. */
	iiim_log_init(__FILE__);

	if (filename == NULL)
		return NULL;

	conf = (IIIMLEXMLConf *) malloc(sizeof (IIIMLEXMLConf) * 1);
	conf->filename = strdup(filename);
	conf->priv = (IIIMLEXMLConfPrivate *)malloc(sizeof (IIIMLEXMLConfPrivate) * 1);
	conf->priv->entries = NULL;
	conf->priv->lang_list = NULL;
	conf->priv->hotkeys = NULL;

	return conf;
}

static void
iiim_le_xmlconf_remove_modules(IIIMLEXMLConf *conf)
{
	struct _LEEntry *e, *tmp;

	e = conf->priv->entries;
	while (e) {
		tmp = e;
		e = tmp->next;
		free(tmp->language);
		iiim_le_info_list_free(tmp->list);
		free(tmp);
	}
	conf->priv->entries = NULL;
	if (conf->priv->lang_list)
		iiim_le_lang_list_free(conf->priv->lang_list);
	conf->priv->lang_list = NULL;
}

void
iiim_le_xmlconf_free(IIIMLEXMLConf *conf)
{
	if (conf == NULL)
		return;

	if (conf->filename)
		free(conf->filename);
	iiim_le_xmlconf_remove_modules(conf);
	iiim_le_xmlconf_remove_hotkeys(conf);
	free(conf->priv);

	free(conf);
}

static int
_parse_xml(IIIMLEXMLConf *conf, xmlDocPtr doc)
{
    xmlNodePtr root = NULL, node, topnode = NULL;
    int retval = 0;

    if ((root = xmlDocGetRootElement(doc)) == NULL)
	goto ensure;
    if (xmlStrcmp(root->name, (xmlChar *)"iiimf") != 0)
	goto ensure;

    node = root->xmlChildrenNode;
    while (node != NULL) {
	if (xmlStrcmp(node->name, (xmlChar *)"LanguageEngines") == 0) {
	    topnode = node;
	    node = node->xmlChildrenNode;
	    parse_les_node(conf, node);
	    node = topnode->next;
	    topnode = NULL;
	} else if (xmlStrcmp(node->name, (xmlChar *)"text") == 0
		   || xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
	    /* ignore text and comment node */
	    node = node->next;
	} else {
	    /* ignore the unknown nodes */
	    UNKNOWN_NODE_ERROR(node->name, "iiimf");
	    node = node->next;
	}
    }
    retval = 1;

  ensure:
    if (doc != NULL)
	xmlFreeDoc(doc);

    return retval;
}

int
iiim_le_xmlconf_load_file(IIIMLEXMLConf *conf)
{
#ifdef HAVE_XMLCTXTREAD
    xmlParserCtxtPtr parser = NULL;
#endif
    xmlDocPtr doc = NULL;
    int retval = 0;

    if (conf == NULL)
	    return 0;
    if (conf->filename == NULL)
	    return 0;

    /* make sure that there is no older data */
    if (!iiim_le_xmlconf_is_empty_module(conf)) {
	    iiim_le_xmlconf_remove_modules(conf);
	    iiim_le_xmlconf_remove_hotkeys(conf);
    }

#ifdef HAVE_XMLCTXTREAD
    parser = xmlNewParserCtxt();
    if ((doc = xmlCtxtReadFile(parser, conf->filename, "UTF-8", 0)) == NULL) {
#else
    if ((doc = xmlParseFile(conf->filename)) == NULL) {
#endif
	goto ensure;
#ifdef HAVE_XMLCTXTREAD
    }
#else
    }
#endif

    retval = _parse_xml(conf, doc);

  ensure:
#ifdef HAVE_XMLCTXTREAD
    if (parser != NULL)
	xmlFreeParserCtxt(parser);
#endif

    return retval;
}

int
iiim_le_xmlconf_load_with_nsio(IIIMLEXMLConf *conf,
			       iml_desktop_t *iml_desktop)
{
    xmlDocPtr doc = NULL;
    int retval = 0;
    iml_nsc_create_t nsc_create = (iml_nsc_create_t)(iml_desktop->If->nsc_get_function("_nsc_create"));
    iml_nsc_free_t nsc_free = (iml_nsc_free_t)(iml_desktop->If->nsc_get_function("_nsc_free"));
    iml_nsc_open_t nsc_open = (iml_nsc_open_t)(iml_desktop->If->nsc_get_function("open"));
    iml_nsc_close_t nsc_close = (iml_nsc_close_t)(iml_desktop->If->nsc_get_function("close"));
    iml_nsc_read_t nsc_read = (iml_nsc_read_t)(iml_desktop->If->nsc_get_function("read"));
    iml_nsc_lstat_t nsc_lstat = (iml_nsc_lstat_t)(iml_desktop->If->nsc_get_function("lstat"));
    iml_nsc_t nsc = NULL;
    int fd = 0, size;
    size_t len;
    char *buffer = NULL;
    struct stat st;

    if (conf == NULL)
	    return 0;
    if (conf->filename == NULL)
	    return 0;

    /* make sure that there is no older data */
    if (!iiim_le_xmlconf_is_empty_module(conf)) {
	    iiim_le_xmlconf_remove_modules(conf);
	    iiim_le_xmlconf_remove_hotkeys(conf);
    }

    /* FIXME: read the conf here */
    nsc = (iml_nsc_t)nsc_create("IIIMLEXMLConf", IML_NSC_TYPE_DESKTOP, iml_desktop);
    if (nsc_lstat(nsc, conf->filename, &st) == -1) {
	    LOG_DEBUG("Failed to stat %s via the namespace I/O: %s", conf->filename, strerror(errno));
	    goto ensure;
    }
    size = st.st_size;
    buffer = (char *)malloc(sizeof (char) * (size + 1));
    if ((fd = nsc_open(nsc, conf->filename, O_RDONLY)) == -1) {
	    LOG_DEBUG("Failed to open %s via the namespace I/O: %s", conf->filename, strerror(errno));
	    goto ensure;
    }
    if ((len = nsc_read(nsc, fd, buffer, size)) == -1) {
	    LOG_DEBUG("Failed to read %s via the namespace I/O: %s", conf->filename, strerror(errno));
	    goto ensure;
    }
    nsc_close(nsc, fd);
    fd = 0;

    if ((doc = xmlParseMemory(buffer, size)) == NULL) {
	    goto ensure;
    }

    retval = _parse_xml(conf, doc);

  ensure:
    if (fd > 0)
	    nsc_close(nsc, fd);
    if (buffer)
	    free(buffer);
    if (nsc)
	    nsc_free(nsc);

    return retval;
}

int
iiim_le_xmlconf_save_file(IIIMLEXMLConf *conf)
{
	xmlDocPtr doc = NULL;
	xmlNodePtr root, les, le, node, key;
	xmlAttrPtr attr;
	int retval = 1;
	struct _LEEntry *ent;

	if (conf == NULL)
		return 0;

	doc = xmlNewDoc((xmlChar *)"1.0");
	doc->encoding = xmlStrdup((xmlChar *)"UTF-8");
	root = xmlNewDocNode(doc, NULL, (xmlChar *)"iiimf", NULL);
	xmlDocSetRootElement(doc, root);
	les = xmlNewTextChild(root, NULL, (xmlChar *)"LanguageEngines", NULL);
	xmlSaveFile(conf->filename, doc);

	if (conf->priv->entries != NULL) {
		for (ent = conf->priv->entries; ent != NULL; ent = ent->next) {
			IIIMLEInfoList *l;
			struct _HotKeyEntry *hent;
			HotKeyList *hlist, *h;

			le = xmlNewTextChild(les, NULL, (xmlChar *)"LanguageEngine", NULL);
			attr = xmlNewProp(le, (xmlChar *)"lang", (xmlChar *)ent->language);
			for (l = ent->list; l != NULL; l = l->next) {
				IIIMLEInfo *ll = l->data;

				node = xmlNewTextChild(le, NULL, (xmlChar *)"module", NULL);
				attr = xmlNewProp(node, (xmlChar *)"path", (xmlChar *)ll->lename);
			}
			for (hent = conf->priv->hotkeys; hent != NULL; hent = hent->next) {
				if (!strcmp(hent->language, ent->language)) {
					node = xmlNewTextChild(le, NULL, (xmlChar *)"hotkey", NULL);
					hlist = hent->list;
					for (h = hlist; h != NULL; h = h->next) {
						key = xmlNewTextChild(node, NULL, (xmlChar *)"key", NULL);
						if (h->hotkey->modifiers != NULL)
							attr = xmlNewProp(key, (xmlChar *)"modifier", (xmlChar *)h->hotkey->modifiers);
						attr = xmlNewProp(key, (xmlChar *)"name", (xmlChar *)h->hotkey->key);
					}
				}
			}
		}
	} else {
		struct _HotKeyEntry *hent;
		HotKeyList *hlist, *h;

		/* try to store the hotkeys only */
		for (hent = conf->priv->hotkeys; hent != NULL; hent = hent->next) {
			le = xmlNewTextChild(les, NULL, (xmlChar *)"LanguageEngine", NULL);
			attr = xmlNewProp(le, (xmlChar *)"lang", (xmlChar *)hent->language);

			node = xmlNewTextChild(le, NULL, (xmlChar *)"hotkey", NULL);
			hlist = hent->list;
			for (h = hlist; h != NULL; h = h->next) {
				key = xmlNewTextChild(node, NULL, (xmlChar *)"key", NULL);
				if (h->hotkey->modifiers != NULL)
					attr = xmlNewProp(key, (xmlChar *)"modifier", (xmlChar *)h->hotkey->modifiers);
				attr = xmlNewProp(key, (xmlChar *)"name", (xmlChar *)h->hotkey->key);
			}
		}
	}
	if (xmlSaveFile(conf->filename, doc) > 0)
		retval = true;

	if (doc != NULL)
		xmlFreeDoc(doc);

	return 1;
}

int
iiim_le_xmlconf_is_empty_module(IIIMLEXMLConf *conf)
{
	if (conf == NULL)
		return 0;
	return conf->priv->entries == NULL;
}

int
iiim_le_xmlconf_append_module(IIIMLEXMLConf *conf,
			      const char    *modulename,
			      const char    *language)
{
	IIIMLEInfo *leinfo, *le;
	struct _LEEntry *ent;
	int stored = 0;

	if (conf == NULL)
		return 0;

	leinfo = iiim_le_info_new(language, modulename);
	if (leinfo == NULL)
		return 0;

	for (ent = conf->priv->entries; ent != NULL; ent = ent->next) {
		if (!strcmp(ent->language, language)) {
			le = iiim_le_info_list_find(ent->list, leinfo);
			if (le != NULL) {
				LOG_DEBUG("LE (lang:%s, %s) has already been registered.", language, le->lename);
				iiim_le_info_free(leinfo);

				return 0;
			}
			LOG_DEBUG("Adding LE (lang:%s, %s)", language, modulename);
			ent->list = iiim_le_info_list_add(ent->list, leinfo);
			stored = 1;
			break;
		}
	}
	if (!stored) {
		struct _LEEntry *entry, *tmp;

		entry = (struct _LEEntry *)malloc(sizeof (struct _LEEntry) * 1);

		LOG_DEBUG("Adding LE (lang:%s, %s)", language, modulename);
		entry->language = strdup(language);
		entry->list = iiim_le_info_list_new(leinfo);
		entry->next = NULL;
		if (conf->priv->entries != NULL) {
			for (tmp = conf->priv->entries; tmp->next != NULL; tmp = tmp->next);
			tmp->next = entry;
		} else {
			conf->priv->entries = entry;
		}
	}
	conf->priv->lang_list = iiim_le_lang_list_add(conf->priv->lang_list,
						      language);

	return 1;
}

int
iiim_le_xmlconf_prepend_module(IIIMLEXMLConf *conf,
			       const char    *modulename,
			       const char    *language)
{
	IIIMLEInfo *leinfo, *le;
	struct _LEEntry *ent;
	int stored = 0;

	if (conf == NULL)
		return 0;
	leinfo = iiim_le_info_new(language, modulename);
	if (leinfo == NULL)
		return 0;

	for (ent = conf->priv->entries; ent != NULL; ent = ent->next) {
		if (!strcmp(ent->language, language)) {
			le = iiim_le_info_list_find(ent->list, leinfo);
			if (le != NULL) {
				LOG_DEBUG("Removing old entry of LE (lang:%s, %s)", language, le->lename);
				ent->list = iiim_le_info_list_remove(ent->list, le);
			}
			LOG_DEBUG("Prepending LE (lang:%s, %s)", language, modulename);
			ent->list = iiim_le_info_list_prepend(ent->list, leinfo);
			stored = 1;
			break;
		}
	}
	if (!stored) {
		struct _LEEntry *entry, *tmp;
		IIIMLEInfo *info;

		entry = (struct _LEEntry *)malloc(sizeof (struct _LEEntry) * 1);
		info = iiim_le_info_new(language, modulename);

		LOG_DEBUG("Prepending LE (lang:%s, %s)", language, modulename);
		entry->language = strdup(language);
		entry->list = iiim_le_info_list_new(info);
		entry->next = NULL;
		if (conf->priv->entries != NULL) {
			for (tmp = conf->priv->entries; tmp->next != NULL; tmp = tmp->next);
			tmp->next = entry;
		} else {
			conf->priv->entries = entry;
		}
	}
	conf->priv->lang_list = iiim_le_lang_list_add(conf->priv->lang_list,
						      language);

	return 1;
}

int
iiim_le_xmlconf_remove_module(IIIMLEXMLConf *conf,
			      const char    *modulename,
			      const char    *language)
{
	IIIMLEInfo *leinfo, *le;
	struct _LEEntry *ent, *prev;
	int removed = 0;

	if (conf == NULL || modulename == NULL || language == NULL)
		return 0;

	leinfo = iiim_le_info_new(language, modulename);

	for (ent = conf->priv->entries, prev = NULL; ent != NULL; prev = ent, ent = prev->next) {
		if (!strcmp(ent->language, language)) {
			le = iiim_le_info_list_find(ent->list, leinfo);
			if (le != NULL) {
				LOG_DEBUG("Removing LE (lang:%s, %s)", language, le->lename);
				ent->list = iiim_le_info_list_remove(ent->list, le);
				if (ent->list == NULL) {
					free(ent->language);
					conf->priv->lang_list = iiim_le_lang_list_remove(conf->priv->lang_list,
											 language);
					if (prev != NULL) {
						prev->next = ent->next;
					} else {
						conf->priv->entries = NULL;
					}
					free(ent);
				}
				removed = 1;
				break;
			} else {
				break;
			}
		}
	}

	iiim_le_info_free(leinfo);

	return removed;
}

IIIMLEInfoList *
iiim_le_xmlconf_get_le_info_list(IIIMLEXMLConf *conf,
				 const char    *language)
{
	struct _LEEntry *ent;
	IIIMLEInfoList *retval = NULL;

	if (conf == NULL || language == NULL || language[0] == 0)
		return NULL;

	for (ent = conf->priv->entries; ent != NULL; ent = ent->next) {
		if (!strcmp(ent->language, language)) {
			retval = ent->list;
			break;
		}
	}

	return retval;
}

IIIMLELanguageList *
iiim_le_xmlconf_get_lang_list(IIIMLEXMLConf *conf)
{
	return conf->priv->lang_list;
}

int
iiim_le_xmlconf_is_empty_hotkey(IIIMLEXMLConf *conf)
{
	if (conf == NULL)
		return 0;
	return conf->priv->hotkeys == NULL;
}

int
iiim_le_xmlconf_append_hotkey(IIIMLEXMLConf *conf,
			      HotKeyStruct  *hotkey,
			      const char    *language)
{
	HotKeyList *list;
	struct _HotKeyEntry *ent;
	int stored = 0;

	if (conf == NULL || hotkey == NULL || language == NULL)
		return 0;

	for (ent = conf->priv->hotkeys; ent != NULL; ent = ent->next) {
		if (!strcmp(ent->language, language)) {
			list = iiim_le_hotkey_list_find(ent->list, hotkey);
			if (list != NULL) {
				LOG_DEBUG("Hotkey (lang:%s, key:%s, modifier:%s) has already been registered.", language, hotkey->key, hotkey->modifiers);
				return 0;
			}
			LOG_DEBUG("Adding Hotkey (lang:%s, key:%s, modifier:%s)", language, hotkey->key, hotkey->modifiers);
			ent->list = iiim_le_hotkey_list_add(ent->list, hotkey);
			stored = 1;
			break;
		}
	}
	if (!stored) {
		struct _HotKeyEntry *entry, *tmp;

		entry = (struct _HotKeyEntry *)malloc(sizeof (struct _HotKeyEntry) * 1);

		LOG_DEBUG("Adding Hotkey (lang:%s, key:%s, modifier:%s)", language, hotkey->key, hotkey->modifiers);
		entry->language = strdup(language);
		entry->list = iiim_le_hotkey_list_new(hotkey);
		entry->next = NULL;
		if (conf->priv->hotkeys != NULL) {
			for (tmp = conf->priv->hotkeys; tmp->next != NULL; tmp = tmp->next);
			tmp->next = entry;
		} else {
			conf->priv->hotkeys = entry;
		}
	}

	return 1;
}

int
iiim_le_xmlconf_remove_hotkey(IIIMLEXMLConf *conf,
			      HotKeyStruct  *hotkey,
			      const char    *language)
{
	HotKeyList *list;
	struct _HotKeyEntry *ent, *prev;
	int removed = 0;

	if (conf == NULL || hotkey == NULL || language == NULL)
		return 0;

	for (ent = conf->priv->hotkeys, prev = NULL; ent != NULL; prev = ent, ent = prev->next) {
		if (!strcmp(ent->language, language)) {
			list = iiim_le_hotkey_list_find(ent->list, hotkey);
			if (list != NULL) {
				LOG_DEBUG("Removing Hotkey (lang:%s, key:%s, modifiers:%s)", language, hotkey->key, hotkey->modifiers);
				ent->list = iiim_le_hotkey_list_remove(ent->list, hotkey);
				if (ent->list == NULL) {
					if (prev != NULL) {
						prev->next = ent->next;
					} else {
						conf->priv->hotkeys = NULL;
					}
				}
				removed = 1;
				break;
			} else {
				break;
			}
		}
	}

	return removed;
}

static void
iiim_le_xmlconf_remove_hotkeys(IIIMLEXMLConf *conf)
{
	struct _HotKeyEntry *ent, *tmp;

	if (conf == NULL)
		return;

	ent = conf->priv->hotkeys;
	while (ent) {
		tmp = ent;
		ent = tmp->next;
		free(tmp->language);
		iiim_le_hotkey_list_free(tmp->list);
		free(tmp);
	}
	conf->priv->hotkeys = NULL;
}

int
iiim_le_xmlconf_clear_hotkey(IIIMLEXMLConf *conf,
			     const char    *language)
{
	struct _HotKeyEntry *ent, *prev;
	int removed = 0;

	if (conf == NULL || language == NULL)
		return 0;

	for (ent = conf->priv->hotkeys, prev = NULL; ent != NULL; prev = ent, ent = prev->next) {
		if (!strcmp(ent->language, language)) {
			LOG_DEBUG("Clearing Hotkey (lang:%s)", language);
			iiim_le_hotkey_list_free(ent->list);
			if (prev != NULL) {
				prev->next = ent->next;
			} else {
				conf->priv->hotkeys = NULL;
			}
			free(ent->language);
			free(ent);
			removed = 1;
			break;
		}
	}

	return removed;
}

HotKeyList *
iiim_le_xmlconf_get_hotkey_list (IIIMLEXMLConf *conf,
				 const char    *language)
{
	struct _HotKeyEntry *ent;

	if (conf == NULL || language == NULL)
		return NULL;

	for (ent = conf->priv->hotkeys; ent != NULL; ent = ent->next) {
		if (!strcmp(ent->language, language)) {
			return ent->list;
		}
	}

	return NULL;
}

HotKeyStruct *
iiim_le_hotkey_struct_new(const char *name,
			  const char *modifiers)
{
	HotKeyStruct *retval;
	int i;

	retval = (HotKeyStruct *)malloc(sizeof (HotKeyStruct) * 1);
	if (modifiers) {
		retval->modifiers = strdup(modifiers);
		retval->modmask = _symbol2mask(modifiers);
	} else {
		retval->modifiers = NULL;
		retval->modmask = 0;
	}
	retval->key = strdup(name);
	for (i = 0; keysymtable[i].keyname != NULL; i++) {
		if (!strcasecmp(name, keysymtable[i].keyname)) {
			retval->keycode = keysymtable[i].keysym;
			break;
		}
	}

	return retval;
}

void
iiim_le_hotkey_struct_free(HotKeyStruct *hotkey)
{
	if (hotkey == NULL)
		return;

	if (hotkey->modifiers)
		free(hotkey->modifiers);
	if (hotkey->key)
		free(hotkey->key);
	free(hotkey);
}

HotKeyList *
iiim_le_hotkey_list_new(HotKeyStruct *hotkey)
{
	HotKeyList *list;

	list = (HotKeyList *)malloc(sizeof (HotKeyList) * 1);
	list->hotkey = hotkey;
	list->next = NULL;

	return list;
}

HotKeyList *
iiim_le_hotkey_list_add(HotKeyList   *list,
			HotKeyStruct *hotkey)
{
	HotKeyList *retval, *l, *prev;

	if (list != NULL) {
		for (l = list, prev = list; l != NULL; prev = l, l = prev->next) {
			if (iiim_le_hotkey_list_find(list, hotkey) != NULL) {
				return list;
			}
		}
		prev->next = iiim_le_hotkey_list_new(hotkey);
		retval = list;
	} else {
		retval = iiim_le_hotkey_list_new(hotkey);
	}

	return retval;
}

HotKeyList *
iiim_le_hotkey_list_remove(HotKeyList *list,
			   HotKeyStruct *hotkey)
{
	HotKeyList *tmp, *prev = NULL;

	tmp = list;
	while (tmp) {
		if (!strcasecmp(tmp->hotkey->key, hotkey->key) &&
		    !_compare_modifiers(tmp->hotkey->modifiers, hotkey->modifiers)) {
			if (prev)
				prev->next = tmp->next;
			else
				list = tmp->next;
			iiim_le_hotkey_struct_free(tmp->hotkey);
			free(tmp);

			break;
		}
		prev = tmp;
		tmp = prev->next;
	}

	return list;
}

HotKeyList *
iiim_le_hotkey_list_find(HotKeyList   *list,
			 HotKeyStruct *hotkey)
{
	HotKeyList *l;

	if (hotkey == NULL)
		return NULL;

	for (l = list; l != NULL; l = l->next) {
		if (!strcasecmp(l->hotkey->key, hotkey->key) &&
		    !_compare_modifiers(l->hotkey->modifiers, hotkey->modifiers)) {
			return l;
		}
	}

	return NULL;
}

void
iiim_le_hotkey_list_free(HotKeyList *list)
{
	HotKeyList *last;

	while (list) {
		last = list;
		list = last->next;
		if (last->hotkey)
			iiim_le_hotkey_struct_free(last->hotkey);
		free(last);
	}
}
