/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "SunIM.h"
#include "IMArg.h"
#include "IMLog.hh"
#include "IMBasicObject.hh"
#include "IMXMLUtil.hh"

typedef vector<string> LangVec;

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_APPENDKEY,
    ACT_REMOVEKEY,
} ActionType;

struct _Action {
    ActionType type;
    string filename;
    LangVec lang;
    IMKeySpecList keylist;
    bool verbose;
};

typedef struct _Action Action;

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

Action *
parse_arg(int argc, char **argv)
{
    Action *act = new Action;
    ActionType cur, next = ACT_NONE;
    bool defaultflag = false;
    string keys;

    act->type = ACT_NONE;
    for (int 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 = true;
	    if (act->type == ACT_APPENDMOD)
		act->type = ACT_PREPENDMOD;
	} else if (strcmp(argv[i], "--add-key") == 0) {
	    cur = ACT_ADDKEY;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--remove-key") == 0) {
	    cur = ACT_DELKEY;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--hotkey") == 0) {
	    cur = ACT_HOTKEY;
	    next = ACT_ARG;
	} else if (strcmp(argv[i], "--help") == 0) {
	    usage(argv[0]);
	    exit(1);
	} else if (strcmp(argv[i], "-v") == 0) {
	    act->verbose = true;
	    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;
		    act->filename = argv[i];
		    break;
		case ACT_REMOVE:
		    act->type = ACT_REMOVEMOD;
		    act->filename = argv[i];
		    break;
		case ACT_LANG:
		    act->lang.push_back(string(argv[i]));
		    break;
		case ACT_ADDKEY:
		    act->type = ACT_APPENDKEY;
		    act->filename = argv[i];
		    break;
		case ACT_DELKEY:
		    act->type = ACT_REMOVEKEY;
		    act->filename = argv[i];
		    break;
		case ACT_HOTKEY:
		    if (!keys.empty())
			keys += string(",");
		    keys += string(argv[i]);
		    break;
		default:
		    fprintf(stderr, "unknown parameter `%s'\n", argv[i]);
		    exit(1);
	    }
	    next = ACT_NONE;
	}
    }
    if (!keys.empty()) {
	IMKeyParser keyparser = IMKeyParser(keys.c_str());

	act->keylist = keyparser.get_parsedkeys();
    }

    return act;
}

int
main (int argc, char **argv)
{
    string lang, modulename;
    string conffile(XMLCONFDIR "/le.xml.conf");
    Action *act;
    IMXMLModifier conf(conffile.c_str());
    struct stat s;
    bool notfoundconf = false;
    IMLog *pimlog = IMLog::construct(argv[0]);

    if (argc < 2) {
	usage(argv[0]);
	exit(1);
    }
    act = parse_arg(argc, argv);
    if (act->lang.empty()) {
	/* need to get lang info from module */
	void *h = NULL;
	void (*f)(IMArgList, int);
	IMArgList a;
	IMLocale *l;

	if ((h = dlopen(act->filename.c_str(), RTLD_LAZY)) == NULL) {
	    fprintf(stderr, "Cannot open `%s'\n", act->filename.c_str());
	    exit(1);
	}
	if ((f = (void (*) (IMArgList, int)) dlsym(h, "if_GetIfInfo")) == NULL) {
	    fprintf(stderr, "`%s' is not valid module.\n", act->filename.c_str());
	    exit(1);
	}
	a = new IMArg[2];
	a->id = 4;
	f(a, 1);
	for (l = (IMLocale *)a->value; l->id != NULL; l++) {
	    act->lang.push_back(string(l->id));
	}
	dlclose(h);
    }
    if (stat(conffile.c_str(), &s) >= 0) {
	/* if conffile exists, parse it */
	LOG_DEBUG("Loading %s...", conffile.c_str());
	conf.load();
    } else {
	notfoundconf = true;
    }
    if (act->verbose) {
	IMLog::get_instance()->set_log_level(IMLog::DEBUGLOG);
	IMLog::get_instance()->set_default_destination(IMLog::IMLOG_STDERR);
    }
    switch (act->type) {
	case ACT_APPENDMOD:
	    for (LangVec::iterator i = act->lang.begin(); i != act->lang.end(); i++) {
		conf.appendmodule(act->filename, *i);
	    }
	    conf.clearhotkey(act->filename);
	    for (IMKeySpecList::iterator i = act->keylist.begin(); i != act->keylist.end(); i++) {
		conf.appendhotkey(*i, act->filename);
	    }
	    break;
	case ACT_PREPENDMOD:
	    for (LangVec::iterator i = act->lang.begin(); i != act->lang.end(); i++) {
		conf.prependmodule(act->filename, *i);
	    }
	    conf.clearhotkey(act->filename);
	    for (IMKeySpecList::iterator i = act->keylist.begin(); i != act->keylist.end(); i++) {
		conf.appendhotkey(*i, act->filename);
	    }
	    break;
	case ACT_REMOVEMOD:
	    for (LangVec::iterator i = act->lang.begin(); i != act->lang.end(); i++) {
		conf.removemodule(act->filename, *i);
	    }
	    conf.clearhotkey(act->filename);
	    break;
	case ACT_APPENDKEY:
	    for (IMKeySpecList::iterator i = act->keylist.begin(); i != act->keylist.end(); i++) {
		conf.appendhotkey(*i, act->filename);
	    }
	    break;
	case ACT_REMOVEKEY:
	    for (IMKeySpecList::iterator i = act->keylist.begin(); i != act->keylist.end(); i++) {
		conf.removehotkey(*i, act->filename);
	    }
	    break;
	default:
	    fprintf(stderr, "unkown action: %d\n", act->type);
	    exit(1);
    }
    if (conf.empty()) {
	if (!notfoundconf) {
	    /* remove empty conf file. */
	    if (unlink(conffile.c_str()) == -1) {
		fprintf(stderr, "Cannot remove `%s' :%s\n", conffile.c_str(), strerror(errno));
		exit(1);
	    }
	}
    } else {
	LOG_DEBUG("Storing %s...", conffile.c_str());
	conf.dumpconf(conffile);
    }

    return 0;
}
