/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "lexmlconf.h"
#include "SunIM.h"
#include "IMArg.h"
#include "keysyms.h"

typedef struct _LangList {
    char *language;
    struct _LangList *next;
} LangList;

typedef enum _ActionType {
    ACT_NONE = 0,
    ACT_ARG,
    ACT_INSTALL,
    ACT_REMOVE,
    ACT_DEFAULT,
    ACT_LANG,
    ACT_HOTKEY,
    ACT_ADDKEY,
    ACT_DELKEY,
    ACT_APPENDMOD = 100,
    ACT_PREPENDMOD,
    ACT_REMOVEMOD,
    ACT_MODLIST,
    ACT_APPENDKEY,
    ACT_REMOVEKEY
} ActionType;

struct _Action {
    ActionType  type;
    char       *filename;
    LangList   *langlist;
    int         verbose;
    int         globalmode;
    HotKeyList *addhotkey;
    HotKeyList *removehotkey;
    int         clearhotkey;
};

typedef struct _Action Action;

static LangList *
lang_list_new(const char *lang)
{
    LangList *l;

    l = (LangList *)malloc(sizeof (LangList) * 1);
    l->language = strdup(lang);
    l->next = NULL;

    return l;
}

static LangList *
lang_list_add(LangList *list, const char *lang)
{
    LangList *l, *ll;

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

    return ll;
}

static void
lang_list_free(LangList *list)
{
    LangList *last;

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

static void
read_supported_lang(Action     *act,
		    const char *modname)
{
    if (act->langlist == NULL) {
	/* need to get lang info from module */
	void *h = NULL;
	void (*f)(IMArgList, int);
	IMArgList a;
	IMLocale *l;

	if ((h = dlopen(act->filename, RTLD_LAZY)) == NULL) {
	    fprintf(stderr, "Cannot open `%s'\n", act->filename);
	    exit(1);
	}
	if ((f = (void (*) (IMArgList, int)) dlsym(h, "if_GetIfInfo")) == NULL) {
	    fprintf(stderr, "`%s' is not valid module.\n", act->filename);
	    exit(1);
	}
	a = (IMArg *)malloc(sizeof (IMArg) * 2);
	a->id = 4;
	f(a, 1);
	for (l = (IMLocale *)a->value; l->id != NULL; l++) {
	    act->langlist = lang_list_add(act->langlist, l->id);
	}
	free(a);
	dlclose(h);
    }
}

void
usage(const char *name)
{
    fprintf(stderr,
	    "Usage: %s [-v][-g][--default][--lang LANG][--add-hotkey KEY][--remove-hotkey KEY]<--install MODULE|--remove MODULE>\n"
	    "Options:\n"
	    "  -v			display more information\n"
	    "  -g			work for the global configuration\n"
	    "  --default		register MODULE as default.\n"
	    "			must use --install together\n"
	    "  --lang LANG		specify LANG for the action.\n"
	    "  --add-hotkey KEY	add the hotkey for LANG\n"
	    "			e.g. --add-hotkey '<Shift>space'\n"
	    "  --remove-hotkey KEY	remove the hotkey for LANG\n"
	    "  --clear-hotkey	clean up the hotkey configuration\n"
	    "			must use --install or --lang together\n"
	    "\n"
	    "Actions:\n"
	    "  --install MODULE	install MODULE to the configuration file\n"
	    "  --remove MODULE	remove MODULE from the configuration file\n"
	    "  --list		shows the available LEs\n"
	    "			must use --lang together.\n"
	    , name);
}

#ifndef HAVE_STRNDUP
static char *
strndup(const char *s, size_t n)
{
    size_t slen;
    char *dst;
    size_t copied = 0;

    if (s != NULL) {
	slen = strlen(s);
	if (slen >= n)
	    copied = n;
	else
	    copied = slen;
    }

    dst = (char*)malloc (copied + 1);
    if (dst == NULL)
	return NULL;

    dst[copied] = '\0';
    return (char *) memcpy (dst, s, copied);
}
#endif

static HotKeyStruct *
parse_hotkey(char *keys)
{
    HotKeyStruct *hstruct = NULL;
    size_t len = strlen(keys);
    long i, j, l, mask = 0;
    char *mod, *key = NULL, *tmp;
    const char *p;
    static struct {
	const char *symbol;
	int mask;
    } modifiers[] = {
	{"shift", 1},
	{"control", 2},
	{"meta", 4},
	{"alt", 8},
	{"altgr", 16},
	{NULL, 0},
    };

    for (i = 0; i < len; i++) {
	if (keys[i] == '<') {
	    tmp = strchr(&keys[i+1], '>');
	    if (tmp != NULL) {
		p = &keys[i+1];
		l = tmp - p;
		mod = strndup(p, l);
		for (j = 0; modifiers[j].symbol != NULL; j++) {
		    if (!strcasecmp(modifiers[j].symbol, mod)) {
			mask |= modifiers[j].mask;
			p = NULL;
			i += (l + 1);
			break;
		    }
		}
		if (p != NULL)
		    fprintf(stderr, "Unknown modifier symbol: %s\n", mod);
		if (mod)
		    free(mod);
	    }
	} else {
	    if (key != NULL) {
		fprintf(stderr, "Specified a key twice. ignoring previous key %s...\n", key);
		free(key);
	    }
	    tmp = strchr(&keys[i], '<');
	    if (tmp != NULL) {
		p = &keys[i];
		key = strndup(p, tmp - p);
		i += (tmp - p - 1);
	    } else {
		key = strdup(&keys[i]);
		i = len;
	    }
	}
    }
    if (key != NULL) {
	char buffer[1024];
	size_t n = 1024, pos = 0;
	int i, found = 0;

	for (i = 0; keysymtable[i].keyname != NULL; i++) {
	    if (!strcasecmp(key, keysymtable[i].keyname)) {
		found = 1;
		break;
	    }
	}
	if (found == 0) {
	    fprintf(stderr, "Unknown key: %s\n", key);
	    free(key);

	    return NULL;
	}
	buffer[0] = 0;
	for (i = 0; mask != 0; i++) {
	    if ((mask & modifiers[i].mask) != 0) {
		size_t len = strlen(modifiers[i].symbol);

		n -= len;
		if (n > 0) {
		    if (pos > 0)
			strcat(buffer, " ");
		    strcat(buffer, modifiers[i].symbol);
		    pos += len;
		} else {
		    fprintf(stderr, "Buffer overflow.\n");
		    free(key);

		    return NULL;
		}
		mask ^= modifiers[i].mask;
	    }
	}
	hstruct = iiim_le_hotkey_struct_new(key, buffer);
	free(key);
    } else {
	fprintf(stderr, "No key specified.");

	return NULL;
    }

    return hstruct;
}

static Action *
parse_arg(int argc, char **argv)
{
    Action *act = (Action *)malloc(sizeof (Action) * 1);
    ActionType cur = ACT_NONE, next = ACT_NONE;
    int defaultflag = 0;
    int i;
    HotKeyStruct *hstruct;

    act->type = ACT_NONE;
    act->filename = NULL;
    act->verbose = 0;
    act->globalmode = 0;
    act->langlist = NULL;
    act->clearhotkey = 0;
    act->addhotkey = NULL;
    act->removehotkey = NULL;
    for (i = 1; i < argc; i++) {
	if (next == ACT_ARG && strncmp(argv[i], "-", 1) == 0) {
	    fprintf(stderr, "missing argument\n");
	    exit(1);
	}
	if (strcmp(argv[i], "--install") == 0) {
	    cur = ACT_INSTALL;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--remove") == 0) {
	    cur = ACT_REMOVE;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--lang") == 0) {
	    cur = ACT_LANG;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--default") == 0) {
	    cur = ACT_DEFAULT;
	    next = ACT_NONE;
	    defaultflag = 1;
	    if (act->type == ACT_APPENDMOD)
		act->type = ACT_PREPENDMOD;
	} else if (strcmp(argv[i], "--clear-hotkey") == 0) {
	    act->clearhotkey = 1;
	    next = ACT_NONE;
	} else if (strcmp(argv[i], "--list") == 0) {
	    act->type = ACT_MODLIST;
	    next = ACT_NONE;
	} else if (strcmp(argv[i], "--add-hotkey") == 0) {
	    cur = ACT_ADDKEY;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--remove-hotkey") == 0) {
	    cur = ACT_DELKEY;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--help") == 0) {
	    usage(argv[0]);
	    exit(1);
	} else if (strcmp(argv[i], "-v") == 0) {
	    act->verbose = 1;
	    next = ACT_NONE;
	} else if (strcmp(argv[i], "-g") == 0) {
	    act->globalmode = 1;
	    next = ACT_NONE;
	} else if (strncmp(argv[i], "-", 1) == 0) {
	    fprintf(stderr, "unknown option `%s'\n", argv[i]);
	    exit(1);
	} else {
	    switch (cur) {
		case ACT_INSTALL:
		    if (defaultflag)
			act->type = ACT_PREPENDMOD;
		    else
			act->type = ACT_APPENDMOD;
		    if (act->filename)
			free(act->filename);
		    act->filename = strdup(argv[i]);
		    break;
		case ACT_REMOVE:
		    act->type = ACT_REMOVEMOD;
		    if (act->filename)
			free(act->filename);
		    act->filename = strdup(argv[i]);
		    break;
		case ACT_LANG:
		    act->langlist = lang_list_add(act->langlist, argv[i]);
		    break;
		case ACT_ADDKEY:
		    hstruct = parse_hotkey(argv[i]);
		    if (hstruct != NULL)
			act->addhotkey = iiim_le_hotkey_list_add(act->addhotkey, hstruct);
		    break;
		case ACT_DELKEY:
		    hstruct = parse_hotkey(argv[i]);
		    if (hstruct != NULL)
			act->removehotkey = iiim_le_hotkey_list_add(act->removehotkey, hstruct);
		    break;
		default:
		    fprintf(stderr, "unknown parameter `%s'\n", argv[i]);
		    exit(1);
	    }
	    next = ACT_NONE;
	}
    }

    return act;
}

int
main (int argc, char **argv)
{
    char *conffile = NULL;
    Action *act;
    IIIMLEXMLConf *conf = NULL;
    struct stat s;
    int notfoundconf = 0, i, need_store = 0;
    LangList *ll;
    IIIMLEInfoList *modlist, *m;
    IIIMLELanguageList *langlist, *lelang;
    HotKeyList *hlist;

    if (argc < 2) {
	usage(argv[0]);
	exit(1);
    }
    act = parse_arg(argc, argv);
    if (act->globalmode) {
	conffile = strdup(XMLCONFDIR "/le.xml.conf");
    } else {
	struct passwd *pass;
	size_t len;
	uid_t uid;

	uid = getuid();
	pass = getpwuid(uid);
	len = strlen(pass->pw_dir);
	conffile = (char *)malloc(sizeof (char) * (len + 19));
	sprintf(conffile, "%s/.iiim/le.xml.conf", pass->pw_dir);
	endpwent();
    }
    conf = iiim_le_xmlconf_new(conffile);
    if (act->verbose) {
	iiim_log_debug_mode();
    }
    if (stat(conffile, &s) >= 0) {
	/* if conffile exists, parse it */
	if (act->verbose)
	    fprintf(stderr, "Loading %s...", conffile);
	iiim_le_xmlconf_load_file(conf);
    } else {
	notfoundconf = 1;
    }
    switch (act->type) {
	case ACT_MODLIST:
	    langlist = iiim_le_xmlconf_get_lang_list(conf);
	    for (lelang = langlist; lelang != NULL; lelang = lelang->next) {
		printf("Language: %s\n", lelang->language);
		modlist = iiim_le_xmlconf_get_le_info_list(conf, lelang->language);
		for (m = modlist, i = 1; m != NULL; m = m->next, i++) {
		    IIIMLEInfo *info = m->data;

		    printf(" %d. %s\n", i, info->lename);
		}
	    }
	    break;
	case ACT_APPENDMOD:
	    need_store = 1;
	    read_supported_lang(act, act->filename);
	    for (ll = act->langlist; ll != NULL; ll = ll->next) {
		if (act->clearhotkey)
		    iiim_le_xmlconf_clear_hotkey(conf, ll->language);
		iiim_le_xmlconf_append_module(conf, act->filename, ll->language);
		for (hlist = act->removehotkey; hlist != NULL; hlist = hlist->next) {
		    iiim_le_xmlconf_remove_hotkey(conf, hlist->hotkey, ll->language);
		}
		for (hlist = act->addhotkey; hlist != NULL; hlist = hlist->next) {
		    iiim_le_xmlconf_append_hotkey(conf, hlist->hotkey, ll->language);
		}
	    }
	    break;
	case ACT_PREPENDMOD:
	    need_store = 1;
	    read_supported_lang(act, act->filename);
	    for (ll = act->langlist; ll != NULL; ll = ll->next) {
		if (act->clearhotkey)
		    iiim_le_xmlconf_clear_hotkey(conf, ll->language);
		iiim_le_xmlconf_prepend_module(conf, act->filename, ll->language);
		for (hlist = act->removehotkey; hlist != NULL; hlist = hlist->next) {
		    iiim_le_xmlconf_remove_hotkey(conf, hlist->hotkey, ll->language);
		}
		for (hlist = act->addhotkey; hlist != NULL; hlist = hlist->next) {
		    iiim_le_xmlconf_append_hotkey(conf, hlist->hotkey, ll->language);
		}
	    }
	    break;
	case ACT_REMOVEMOD:
	    need_store = 1;
	    read_supported_lang(act, act->filename);
	    for (ll = act->langlist; ll != NULL; ll = ll->next) {
		iiim_le_xmlconf_remove_module(conf, act->filename, ll->language);
		if (iiim_le_xmlconf_get_le_info_list(conf, ll->language) == NULL)
		    iiim_le_xmlconf_clear_hotkey(conf, ll->language);
	    }
	    break;
	default:
	    if (act->langlist != NULL &&
		(act->addhotkey != NULL || act->removehotkey != NULL)) {
		/* Add/Remove the hotkey only */
		need_store = 1;
		for (ll = act->langlist; ll != NULL; ll = ll->next) {
		    for (hlist = act->removehotkey; hlist != NULL; hlist = hlist->next) {
			iiim_le_xmlconf_remove_hotkey(conf, hlist->hotkey, ll->language);
		    }
		    for (hlist = act->addhotkey; hlist != NULL; hlist = hlist->next) {
			iiim_le_xmlconf_append_hotkey(conf, hlist->hotkey, ll->language);
		    }
		}
	    } else if (act->langlist != NULL && act->clearhotkey) {
		need_store = 1;
		for (ll = act->langlist; ll != NULL; ll = ll->next) {
		    /* clean up the hotkey only */
		    iiim_le_xmlconf_clear_hotkey(conf, ll->language);
		}
	    } else {
		fprintf(stderr, "unkown action: %d\n", act->type);
		exit(1);
	    }
    }
    if (need_store) {
	if (iiim_le_xmlconf_is_empty_module(conf) &&
	    iiim_le_xmlconf_is_empty_hotkey(conf)) {
	    if (!notfoundconf) {
		/* remove empty conf file. */
		if (unlink(conffile) == -1) {
		    fprintf(stderr, "Cannot remove `%s' :%s\n", conffile, strerror(errno));
		    exit(1);
		}
	    }
	} else {
	    if (act->verbose)
		fprintf(stderr, "Storing %s...", conffile);
	    iiim_le_xmlconf_save_file(conf);
	    if (act->verbose)
		fprintf(stderr, "done\n");
	}
    }
    if (conffile)
	free(conffile);
    if (act->filename)
	free(act->filename);
    if (act->langlist)
	lang_list_free(act->langlist);
    if (act->removehotkey)
	iiim_le_hotkey_list_free(act->removehotkey);
    if (act)
	free(act);
    if (conf)
	iiim_le_xmlconf_free(conf);

    return 0;
}
