#include <stdio.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include "ztypes.h"
#include "xio.h"

#define NUMCOMMANDS (768) /* 3*256 */

cmdentry *keycmds[NUMCOMMANDS];
char *keycmdargs[NUMCOMMANDS];

static cmdentry mastertable[] = {
    {xted_insert, -1, 0, "insert-self"},
    {xted_enter, op_Enter, 0, "enter"},

    {xted_movecursor, op_ForeChar, 0, "forward-char"},
    {xted_movecursor, op_BackChar, 0, "backward-char"},
    {xted_movecursor, op_ForeWord, 0, "forward-word"},
    {xted_movecursor, op_BackWord, 0, "backward-word"},
    {xted_movecursor, op_ForeLine, 0, "forward-line"},
    {xted_movecursor, op_BackLine, 0, "backward-line"},
    {xted_movecursor, op_BeginLine, 0, "beginning-of-line"},
    {xted_movecursor, op_EndLine, 0, "end-of-line"},

    {xted_scroll, op_DownPage, 0, "scroll-down"},
    {xted_scroll, op_UpPage, 0, "scroll-up"},
    {xted_scroll, op_DownLine, 0, "scroll-down-line"},
    {xted_scroll, op_UpLine, 0, "scroll-up-line"},
    {xted_scroll, op_ToBottom, 0, "scroll-to-bottom"},
    {xted_scroll, op_ToTop, 0, "scroll-to-top"},

    {xted_delete, op_ForeChar, 0, "delete-next-char"},
    {xted_delete, op_BackChar, 0, "delete-char"},
    {xted_delete, op_ForeWord, 0, "delete-next-word"},
    {xted_delete, op_BackWord, 0, "delete-word"},

    {xted_cutbuf, op_Yank, 0, "yank"},
    {xted_cutbuf, op_Wipe, 0, "kill-region"},
    {xted_cutbuf, op_Copy, 0, "copy-region"},
    {xted_cutbuf, op_YankReplace, 0, "yank-pop"},
    {xted_cutbuf, op_Kill, 0, "kill-line"},
    {xted_cutbuf, op_Untype, 0, "kill-input"},

    {xted_history, op_BackLine, 0, "backward-history"},
    {xted_history, op_ForeLine, 0, "forward-history"},

    {xted_macro, -1, 0, "macro"},

    {xtexted_meta, op_Cancel, 1, "cancel"},
    {xtexted_meta, op_Escape, 1, "escape"},
    {xtexted_meta, op_ExplainKey, 0, "explain-key"},
    {xtexted_meta, op_DefineMacro, 0, "define-macro"},
    {xtexted_redraw, op_Screen, 0, "redraw-screen"},
    {xtexted_redraw, op_Status, 0, "redraw-status"},
    {xtexted_redraw, op_AllWindows, 0, "redraw-all-windows"},
    {xstat_reset_window_size, op_Zoom, 0, "zoom-status"},
    {xstat_reset_window_size, op_Shrink, 0, "shrink-status"},
    {xstat_reset_window_size, op_Clear, 0, "clear-status"},

    {xted_noop, -1, 0, "no-op"},

    {NULL, 0, 0, NULL}
};

typedef struct binding_t {
    unsigned int key; /* or keysym */
    int which; /* keytype_main, keytype_meta, keytype_sym */
    char *name;
} binding;

static binding defaultbindings[] = {
    {XK_Left, keytype_sym, "backward-char"},
    {XK_KP_Left, keytype_sym, "backward-char"},
    {XK_Right, keytype_sym, "forward-char"},
    {XK_KP_Right, keytype_sym, "forward-char"},
    {XK_Up, keytype_sym, "backward-history"},
    {XK_KP_Up, keytype_sym, "backward-history"},
    {XK_Down, keytype_sym, "forward-history"},
    {XK_KP_Down, keytype_sym, "forward-history"},
    {XK_Begin, keytype_sym, "beginning-of-line"},
    {XK_KP_Begin, keytype_sym, "beginning-of-line"},
    {XK_End, keytype_sym, "end-of-line"},
    {XK_KP_End, keytype_sym, "end-of-line"},
    {XK_Prior, keytype_sym, "scroll-up"},
    {XK_KP_Prior, keytype_sym, "scroll-up"},
    {XK_Next, keytype_sym, "scroll-down"},
    {XK_KP_Next, keytype_sym, "scroll-down"},
    {XK_Delete, keytype_sym, "delete-char"},
    {XK_KP_Delete, keytype_sym, "delete-char"},
    {XK_Help, keytype_sym, "explain-key"},

    {'w', keytype_meta, "copy-region"},
    {'y', keytype_meta, "yank-pop"},
    {'b', keytype_meta, "backward-word"},
    {'f', keytype_meta, "forward-word"},
    {'v', keytype_meta, "scroll-up"},
    {'d', keytype_meta, "delete-next-word"},
    {'`', keytype_meta, "forward-history"},
    {'=', keytype_meta, "backward-history"},
    {'z', keytype_meta, "zoom-status"},
    {'s', keytype_meta, "shrink-status"},
    {'c', keytype_meta, "clear-status"},
    {'r', keytype_meta, "define-macro"},
    {'0', keytype_meta, "macro"},
    {'1', keytype_meta, "macro"},
    {'2', keytype_meta, "macro"},
    {'3', keytype_meta, "macro"},
    {'4', keytype_meta, "macro"},
    {'5', keytype_meta, "macro"},
    {'6', keytype_meta, "macro"},
    {'7', keytype_meta, "macro"},
    {'8', keytype_meta, "macro"},
    {'9', keytype_meta, "macro"},

    {'\177', keytype_meta, "delete-word"},
    {'\010', keytype_meta, "delete-word"},

    {'\007' /* ctrl-G */, keytype_main, "cancel"},
    {'\033' /* Escape */, keytype_main, "escape"},
    {'\007' /* ctrl-G */, keytype_meta, "cancel"},
    {'\037' /* ctrl-_ */, keytype_main, "explain-key"},
#ifdef TESTING
    {'\021' /* ctrl-Q */, keytype_main, "scroll-up-line"},
    {'\032' /* ctrl-Z */, keytype_main, "scroll-down-line"},
#endif
    {'\001' /* ctrl-A */, keytype_main, "beginning-of-line"},
    {'\005' /* ctrl-E */, keytype_main, "end-of-line"},
    {'\002' /* ctrl-B */, keytype_main, "backward-char"},
    {'\006' /* ctrl-F */, keytype_main, "forward-char"},
    {'\014' /* ctrl-L */, keytype_main, "redraw-all-windows"},
    {'\031' /* ctrl-Y */, keytype_main, "yank"},
    {'\027' /* ctrl-W */, keytype_main, "kill-region"},
    {'\013' /* ctrl-K */, keytype_main, "kill-line"},
    {'\025' /* ctrl-U */, keytype_main, "kill-input"},
    {'\026' /* ctrl-V */, keytype_main, "scroll-down"},
    {'\016' /* ctrl-N */, keytype_main, "forward-line"},
    {'\020' /* ctrl-P */, keytype_main, "backward-line"},
    {'\177' /* del */,    keytype_main, "delete-char"},
    {'\010' /* del */,    keytype_main, "delete-char"},
    {'\004' /* ctrl-D */, keytype_main, "delete-next-char"},
    {'\n' /* newline */,  keytype_main, "enter"},
    {'\r' /* return */,   keytype_main, "enter"},

    {0, 0, NULL}
};

#ifdef __STDC__
static void parse_one_binding(char *key, char *proc, char *marg);
#else
static void parse_one_binding();
#endif

/* initialize key tables */
#ifdef __STDC__
void xkey_init()
#else
void xkey_init()
#endif
{
    int ix, keynum;
    cmdentry *cmd;
    binding *bx;

    for (ix=0; ix<NUMCOMMANDS; ix++) {
	keycmds[ix] = NULL;
	keycmdargs[ix] = NULL;
    }

    cmd = xkey_find_cmd_by_name("insert-self");
    for (ix=' '; ix<='~'; ix++) {
	keycmds[ix | keytype_main] = cmd;
    }

    for (bx=defaultbindings; bx->name; bx++) {
	ix = (bx->key & 255);
	cmd = xkey_find_cmd_by_name(bx->name);
	if (!cmd) {
	    fprintf(stderr, "%s: unknown function <%s> in default bindings\n", PROGRAMNAME, bx->name);
	}
	else {
	    keynum = ix | bx->which;
	    if (keycmds[keynum]) {
		fprintf(stderr, "%s: key <%s> (%d) defined twice in default bindings\n", PROGRAMNAME,
			xkey_get_key_name(keynum), ix);
	    }
	    keycmds[keynum] = cmd;
	}
    }
}

#ifdef __STDC__
char *xkey_get_key_name(int key)
#else
char *xkey_get_key_name(key)
int key;
#endif
{
    static char buf[32];
    char *prefix, *name;
    int which;

    which = key & keytype_Mask;
    key = key & (~keytype_Mask);

    if (which==keytype_sym) {
	KeySym ksym = (KeySym)((XK_Home & (~255)) | key); /* I bet this is a stupid thing to do */
	name = XKeysymToString(ksym);
	if (!name)
	    name = "Unknown key";
	strcpy(buf, name);
	return buf;
    }

    if (which==keytype_meta)
	prefix = "meta-";
    else
	prefix = "";

    if (iscntrl(key)) {
	sprintf(buf, "%sctrl-%c", prefix, key+'A'-1);	
    }
    else {
	sprintf(buf, "%s%c", prefix, key);
    }

    return buf;
}

/* parse stuff of the form key=proc-name;key=proc-name;...
 Spaces are optional, c-X and m-X are abbreviations, /123 codes are accepted, and I'm getting a headache. */
#ifdef __STDC__
void xkey_parse_bindings(char *str)
#else
void xkey_parse_bindings(str)
char *str;
#endif
{
    char *cx, *cx2, *key, *proc, *marg;
    char buf[64], nbuf[256], margbuf[256];
    int escaped;
    int keylen, proclen;
    int hasmarg;

    cx = str;
    for (; *cx && isspace(*cx); cx++);
    while (*cx) {

	key = cx;

	escaped = FALSE;
	for (; *cx && *cx != '=' && !isspace(*cx); cx++) {
	    if (*cx == '\\' && *(cx+1) != '\0') {
		cx++;
		if (isdigit(*cx)) {
		    cx++;
		    if (isdigit(*cx)) {
			cx++;
		    }
		}
	    }
	}
	keylen = cx - key;
	if (keylen >= sizeof(buf)) {
	    keylen = sizeof(buf)-1;
	}
	strncpy(buf, key, keylen);
	buf[keylen] = '\0';
	if (*cx == '\0') {
	    fprintf(stderr, "%s: binding for <%s> has no procedure\n", PROGRAMNAME, buf);
	    break;
	}

	for (; *cx && isspace(*cx); cx++);
	if (*cx != '=') {
	    fprintf(stderr, "%s: unexpected '%c' instead of '=' after <%s>\n", PROGRAMNAME, *cx, buf);
	    break;
	}
	cx++; /* skip equals */
	for (; *cx && isspace(*cx); cx++);
	proc = cx;
	for (; *cx && (*cx=='-' || isalnum(*cx)); cx++);
	proclen = cx - proc;
	if (proclen >= sizeof(nbuf)) {
	    proclen = sizeof(nbuf)-1;
	}
	strncpy(nbuf, proc, proclen);
	nbuf[proclen] = '\0';

	for (; *cx && isspace(*cx); cx++);
	hasmarg = FALSE;
	if (*cx == ',') {
	    cx++; /* skip comma */
	    for (; *cx && isspace(*cx); cx++);
	    if (*cx != '"') {
		fprintf(stderr, "%s: unexpected '%c' instead of '\"' after ','\n", PROGRAMNAME, *cx);
		break;
	    }
	    cx++; /* skip open-quote */
	    marg = cx;
	    for (; *cx && *cx!='\"'; cx++);
	    if (*cx == '\0') {
		fprintf(stderr, "%s: unexpected end of string after '\"'\n", PROGRAMNAME);
		break;
	    }
	    proclen = cx - marg;
	    if (proclen >= sizeof(margbuf)) {
		proclen = sizeof(margbuf)-1;
	    }
	    strncpy(margbuf, marg, proclen);
	    margbuf[proclen] = '\0';
	    hasmarg = TRUE;

	    cx++; /* skip close-quote */
	    for (; *cx && isspace(*cx); cx++);
	}

	parse_one_binding(buf, nbuf, hasmarg ? margbuf : NULL);

	if (*cx == '\0')
	    break;
	if (*cx != ';') {
	    fprintf(stderr, "%s: unexpected '%c' instead of ';' after <%s>\n", PROGRAMNAME, *cx, nbuf);
	    break;
	}
	cx++; /* skip semicolon */
	for (; *cx && isspace(*cx); cx++);
    }

}

#ifdef __STDC__
static void parse_one_binding(char *key, char *proc, char *marg)
#else
static void parse_one_binding(key, proc, marg)
char *key;
char *proc;
char *marg;
#endif
{
    cmdentry *cmd;
    int ismeta = FALSE;
    int isctrl = FALSE;
    int keynum;
    KeySym ksym;

    cmd = xkey_find_cmd_by_name(proc);
    if (!cmd) {
	fprintf(stderr, "%s: unknown procedure <%s>\n", PROGRAMNAME, proc);
	return;
    }

    while ((*key=='c' || *key=='C' || *key=='m' || *key=='M') && *(key+1)=='-') {
	if (*key=='c' || *key=='C')
	    isctrl = TRUE;
	else
	    ismeta = TRUE;
	key += 2;
    }
    if (strlen(key)==1) {
	keynum = (*key);
	if (isctrl) {
	    if (islower(keynum))
		keynum = toupper(keynum);
	    if (keynum >= 'A'-1)
		keynum -= ('A'-1);
	}
	if (ismeta)
	    keynum |= keytype_meta;
    }
    else {
	ksym = XStringToKeysym(key);
	if (ksym==NoSymbol) {
	    fprintf(stderr, "%s: unknown key <%s>\n", PROGRAMNAME, key);
	    return;
	}
	if ((ksym & (~255)) == (XK_Home & (~255))) {
	    keynum = keytype_sym | (ksym & (255));
	}
	else if ((ksym & (~255)) == (XK_A & (~255))) {
	    keynum = keytype_main | (ksym & (255));
	    if (ismeta)
		keynum |= keytype_meta;
	}
	else {
	    fprintf(stderr, "%s: unknown key <%s>\n", PROGRAMNAME, key);
	    return;
	}
    }

    keycmds[keynum] = cmd;

    if (keycmdargs[keynum]) {
	free(keycmdargs[keynum]);
	keycmdargs[keynum] = NULL;
    }

    if (marg) {
	keycmdargs[keynum] = (char *)malloc(sizeof(char) * (1+strlen(marg)));
	strcpy(keycmdargs[keynum], marg);
    }
}

#ifdef __STDC__
cmdentry *xkey_find_cmd_by_name(char *str)
#else
cmdentry *xkey_find_cmd_by_name(str)
char *str;
#endif
{
    cmdentry *retval;

    for (retval = mastertable; retval->func; retval++) {
	if (!strcmp(str, retval->name))
	    return retval;
    }
    return NULL;
}
