/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "IMLog.hh"
#include "IMLEXMLConf.hh"

#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)

bool
LEInfo::clearhotkey(string &modname)
{
    bool cleared = false;

    for (IMLEHotkeyInfoVec::iterator i = hotkeyvec.begin(); i != hotkeyvec.end(); i++) {
	if (i->modname == modname) {
	    i->hotkeys.clear();
	    cleared = true;
	    break;
	}
    }

    return cleared;
}

bool
LEInfo::appendhotkey(IMKeySpec hotkey, string &modname)
{
    bool stored = false;

    if (modname.empty())
	return false;

    for (IMLEHotkeyInfoVec::iterator i = hotkeyvec.begin(); i != hotkeyvec.end(); i++) {
	if (i->modname == modname) {
	    for (IMKeySpecList::iterator j = i->hotkeys.begin(); j != i->hotkeys.end(); j++) {
		if (j->get_keycode() == hotkey.get_keycode()
		    && j->get_modifier() == hotkey.get_modifier()) {
		    LOG_DEBUG("Hotkey (modname:%s, keycode:%d, modifier:%d) has already been registered.", modname.c_str(), hotkey.get_modifier(), hotkey.get_keycode());
		    return false;
		}
	    }
	    LOG_DEBUG("Adding hotkey (modname:%s, keycode:%d, modifier:%d)", modname.c_str(), hotkey.get_modifier(), hotkey.get_keycode());
	    i->hotkeys.push_back(hotkey);
	    stored = true;
	    break;
	}
    }
    /* if entry for hotkey has not been allocated yet, do it here */
    if (stored == false) {
	LEHotkey *entry = new LEHotkey;

	LOG_DEBUG("Adding hotkey (modname:%s, keycode:%d, modifier:%d)", modname.c_str(), hotkey.get_modifier(), hotkey.get_keycode());
	entry->modname = modname;
	entry->hotkeys.push_back(hotkey);
	hotkeyvec.push_back(*entry);
    }

    return true;
}

bool
LEInfo::removehotkey(IMKeySpec hotkey, string &modname)
{
    bool removed = false;

    if (modname.empty())
	return false;

    for (IMLEHotkeyInfoVec::iterator i = hotkeyvec.begin(); i != hotkeyvec.end(); i++) {
	if (i->modname == modname) {
	    for (IMKeySpecList::iterator j = i->hotkeys.begin(); j != i->hotkeys.end(); j++) {
		if (j->get_keycode() == hotkey.get_keycode()
		    && j->get_modifier() == hotkey.get_modifier()) {
		    LOG_DEBUG("Removing hotkey (modname:%s, keycode:%d, modifier:%d", modname.c_str(), hotkey.get_keycode(), hotkey.get_modifier());
		    i->hotkeys.erase(j);
		    removed = true;
		    break;
		}
	    }
	}
    }

    return removed;
}

bool
LEInfo::appendmodule(string &modulename, string &language)
{
    bool stored = false;

    if (modulename.empty() || language.empty())
	return false;

    for (IMModuleInfoVec::iterator i = levec.begin(); i != levec.end(); i++) {
	if (i->lang == language) {
	    ModuleInfoVec::iterator j = find(i->modules.begin(), i->modules.end(), modulename);
	    if (j != i->modules.end()) {
		LOG_DEBUG("LE (lang:%s, %s) has already been registered.", language.c_str(), modulename.c_str());
		return false;
	    }
	    LOG_DEBUG("Adding LE (lang:%s, %s)", language.c_str(), modulename.c_str());
	    i->modules.push_back(modulename);
	    stored = true;
	    break;
	}
    }
    /* if entry for lang has not been allocated yet, do it here */
    if (stored == false) {
	LEEntry *entry = new LEEntry;

	LOG_DEBUG("Adding LE (lang:%s, %s)", language.c_str(), modulename.c_str());
	entry->lang = language;
	entry->modules.push_back(modulename);
	levec.push_back(*entry);
    }

    return true;
}

bool
LEInfo::prependmodule(string &modulename, string &language)
{
    bool stored = false;

    if (modulename.empty() || language.empty())
	return false;

    for (IMModuleInfoVec::iterator i = levec.begin(); i != levec.end(); i++) {
	if (i->lang == language) {
	    ModuleInfoVec::iterator j = find(i->modules.begin(), i->modules.end(), modulename);
	    if (j != i->modules.end()) {
		LOG_DEBUG("Removing old entry of LE (lang:%s, %s)", language.c_str(), modulename.c_str());
		i->modules.erase(j);
	    }
	    LOG_DEBUG("Prepending LE (lang:%s, %s)", language.c_str(), modulename.c_str());
	    i->modules.insert(i->modules.begin(), modulename);
	    stored = true;
	    break;
	}
    }
    /* if entry for language has not been allocated yet, do it here */
    if (stored == false) {
	LEEntry *entry = new LEEntry;

	LOG_DEBUG("Prepending LE (lang:%s, %s)", language.c_str(), modulename.c_str());
	entry->lang = language;
	entry->modules.push_back(modulename);
	levec.push_back(*entry);
    }

    return true;
}

bool
LEInfo::removemodule(string &modulename, string &language)
{
    bool removed = false;

    if (modulename.empty() || language.empty())
	return false;

    for (IMModuleInfoVec::iterator i = levec.begin(); i != levec.end(); i++) {
	if (i->lang == language) {
	    ModuleInfoVec::iterator j = find(i->modules.begin(), i->modules.end(), modulename);
	    if (j != i->modules.end()) {
		LOG_DEBUG("Removing LE (lang:%s, %s)", language.c_str(), modulename.c_str());
		i->modules.erase(j);
		removed = true;
		break;
	    } else {
		return false;
	    }
	}
    }

    return removed;
}

bool
LEInfo::empty(void)
{
    for (IMModuleInfoVec::iterator i = levec.begin(); i != levec.end(); i++) {
	if (!i->modules.empty())
	    return false;
    }

    return true;
}

LEInfo::LEInfo(void)
{
}

string
IMLEXMLConf::parse_text_node(xmlNodePtr &node)
{
    string retval;

    while (node != NULL) {
	if (xmlStrcmp(node->name, (xmlChar *)"text") == 0) {
	    retval = (const char *)node->content;
	    node = node->next;
	} else if (xmlStrcmp(node->name, (xmlChar *)"comment") == 0) {
	    /* ignore comment node */
	    node = node->next;
	} else {
	    UNKNOWN_NODE_ERROR(node->name, "text");
	    node = node->next;
	}
    }

    return retval;
}

void
IMLEXMLConf::parse_key_node(xmlNodePtr &node, string &modname)
{
    xmlChar *mod;
    string modifier, key, s;
    char *ptop, *pend;
    IMKeySpec *hotkey;
    IMKeyParser *keyparser;
    IMKeySpecList keylist;

    mod = xmlGetProp(node, (xmlChar *)"modifier");
    ptop = (char *)mod;
    while (ptop) {
	if ((pend = strchr(ptop, ',')) != NULL) {
	    s.assign(ptop, pend - ptop);
	    ptop = pend + 1;
	} else {
	    s.assign(ptop);
	    ptop = NULL;
	}
	modifier += string("<") + s + string(">");
    }

    key = parse_text_node(node->xmlChildrenNode);
    if (key.empty()) {
	EMPTY_NODE_ERROR("key");
	goto ensure;
    }
    keyparser = new IMKeyParser((modifier + key).c_str());
    keylist = keyparser->get_parsedkeys();
    for (IMKeySpecList::iterator i = keylist.begin(); i != keylist.end(); i++) {
	le_list.appendhotkey(*i, modname);
    }
    delete keyparser;
  ensure:
    if (mod != NULL)
	xmlFree(mod);
}

void
IMLEXMLConf::parse_module_node(xmlNodePtr &node, string &language)
{
    xmlNodePtr topnode = NULL;
    xmlChar *p;
    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());
    le_list.appendmodule(path, language);

    node = node->xmlChildrenNode;

    while (node != NULL) {
	if (xmlStrcmp(node->name, (xmlChar *)"key") == 0) {
	    topnode = node;
	    parse_key_node(node, path);
	    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, "module");
	    node = node->next;
	}
    }
}

void
IMLEXMLConf::parse_le_node(xmlNodePtr &node)
{
    xmlNodePtr topnode = NULL;
    xmlChar *l;
    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(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;
	}
    }
}

void
IMLEXMLConf::parse_default_node(xmlNodePtr &node)
{
    xmlNodePtr topnode = NULL;
    string s = "global-hotkey";

    while (node != NULL) {
	if (xmlStrcmp(node->name, (xmlChar *)"key") == 0) {
	    topnode = node;
	    parse_key_node(node, s);
	    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, "default");
	    node = node->next;
	}
    }
}

void
IMLEXMLConf::parse_les_node(xmlNodePtr &node)
{
    xmlNodePtr topnode = NULL;

    while (node != NULL) {
	if (xmlStrcmp(node->name, (xmlChar *)"LanguageEngine") == 0) {
	    topnode = node;
	    parse_le_node(node);
	    node = topnode->next;
	    topnode = NULL;
	} else if (xmlStrcmp(node->name, (xmlChar *)"default") == 0) {
	    topnode = node;
	    parse_default_node(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;
	}
    }
}

bool
IMLEXMLConf::load(void)
{
#ifdef HAVE_XMLCTXTREAD
    xmlParserCtxtPtr parser = NULL;
#endif
    xmlDocPtr doc = NULL;
    xmlNodePtr root = NULL, node, topnode = NULL;
    bool retval = false;

    if (filename.empty())
	return false;
#ifdef HAVE_XMLCTXTREAD
    parser = xmlNewParserCtxt();
    if ((doc = xmlCtxtReadFile(parser, filename.c_str(), "UTF-8", 0)) == NULL) {
#else
    if ((doc = xmlParseFile(filename.c_str())) == NULL) {
#endif
	goto ensure;
    }
    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(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 = true;

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

    return retval;
}

IMLEXMLConf::IMLEXMLConf(const char *conffile)
{
    filename = conffile;
}

#if 0
int
main(int argc, char **argv)
{
    IMLEXMLConf conf((const char *)argv[1]);
    IMModuleInfoVec *lev;

    conf.parse();
    lev = conf.get_leinfo();
    for (IMModuleInfoVec::iterator i = lev->begin(); i != lev->end(); i++) {
	printf("%s:\n", i->lang.c_str());
	for (ModuleInfoVec::iterator j = i->modules.begin(); j != i->modules.end(); j++) {
	    printf("- %s\n", j->c_str());
	}
    }

    return 0;
}
#endif
