/* 
 * w3m func.c
 */

#include <stdarg.h>
#include <stdio.h>

#include "fm.h"
#include "func.h"
#include "myctype.h"

#include "funcname.c"

btri_string_tab_t w3mFuncTab[] = {
#include "funcname.h"
};

KeyTabList *w3mFuncKeyTabList = &w3mDefaultFuncKeyTabList;
KeyTabList *w3mEditKeyTabList = &w3mDefaultEditKeyTabList;
#ifdef USE_MENU
KeyTabList *w3mMenuKeyTabList = &w3mDefaultMenuKeyTabList;
#endif

static btri_uint_tab_t *KeyData;
static char keymap_initialized;
static struct stat current_keymap_file;

void
addToKeyTab(KeyTabList *kl, int key, int cmd)
{
    NEW_OBJV1(&kl->size, kl->nitem, &kl->key_table, sizeof(KeyTabItem), 1);
    kl->key_table[(kl->nitem)++] = K_SET_FUNC(key, cmd);
}

int
cmpKeyTabItem(const void *x, const void *y)
{
    return K_CMP(*(int *)x, -, *(int *)y);
}

KeyTabList *
copyKeyTabList(KeyTabList *kl)
{
    if (kl->key_table) {
	KeyTabList *new;

	qsort(kl->key_table, kl->nitem, sizeof(KeyTabItem), cmpKeyTabItem);
	new = GC_MALLOC(sizeof(KeyTabList));
	*new = *kl;
	return new;
    }
    else
	return kl->next;
}

void
setKeymap(char *p, int lineno, int verbose,
	  KeyTabList *func_tab, KeyTabList *edit_tab
#ifdef USE_MENU
	  , KeyTabList *menu_tab
#endif
	  )
{
    char *s, *emsg;
    int c;
    KeyTabList *tab;
    FuncList *fl;

    s = getQWord(&p);
    c = getKey(s);

    if (c < 0) {		/* error */
	if (lineno > 0)
	    emsg = Sprintf("line %d: unknown key '%s'", lineno, s)->ptr;
	else
	    emsg = Sprintf("defkey: unknown key '%s'", s)->ptr;

	record_err_message(emsg);

	if (verbose)
	    disp_message_nsec(emsg, FALSE, 0, FALSE, TRUE);

	return;
    }

    s = getWord(&p);

    if (!strncasecmp(s, "E:", sizeof("E:") - 1))
	tab = edit_tab;
#ifdef USE_MENU
    else if (!strncasecmp(s, "M:", sizeof("M:") - 1)) {
	tab = menu_tab;

#ifdef USE_MENU_BUFFER
	if (s[sizeof("M:") - 1] == ':')
	    s += sizeof("M::") - 1;
#endif
    }
#endif
    else
	tab = func_tab;

    if (btri_fast_ci_search_str(s, w3mFuncTab, (void **)&fl) == bt_failure) {
	if (lineno > 0)
	    emsg = Sprintf("line %d: invalid command '%s'", lineno, s)->ptr;
	else
	    emsg = Sprintf("defkey: invalid command '%s'", s)->ptr;

	record_err_message(emsg);

	if (verbose)
	    disp_message_nsec(emsg, FALSE, 0, FALSE, TRUE);

	return;
    }

    if (lineno <= 0) {
	if (w3mFuncKeyTabList != &w3mDefaultFuncKeyTabList)
	    *func_tab = *w3mFuncKeyTabList;

	if (w3mEditKeyTabList != &w3mDefaultEditKeyTabList)
	    *edit_tab = *w3mEditKeyTabList;

#ifdef USE_MENU
	if (w3mMenuKeyTabList != &w3mDefaultMenuKeyTabList)
	    *menu_tab = *w3mMenuKeyTabList;
#endif
    }

    addToKeyTab(tab, c, fl - w3mFuncList);

    if (tab == func_tab) {
	s = getQWord(&p);

	if (*s) {
	    void *value;

	    if (!KeyData)
		KeyData = btri_new_node(&btri_uint_tab_desc);

	    value = s;
	    btri_search_uint(&btri_uint_tab_desc, BTRI_OP_ADD | BTRI_OP_WR, c, KeyData, &value);
	}
    }

    if (lineno <= 0) {
	w3mFuncKeyTabList = copyKeyTabList(func_tab);
	w3mEditKeyTabList = copyKeyTabList(edit_tab);
#ifdef USE_MENU
	w3mMenuKeyTabList = copyKeyTabList(menu_tab);
#endif
    }
}

void
initKeymap(int force)
{
    FILE *kf;
    Str line;
    char *p, *s, *emsg;
    int lineno;
    int verbose = 1;
    KeyTabList func_tab = {NULL, NULL, 0, 0}, edit_tab = {NULL, NULL, 0, 0};
#ifdef USE_MENU
    KeyTabList menu_tab = {NULL, NULL, 0, 0};
#endif
    int kfd;
    struct stat kstat;

    if (!force && !keymap_initialized)
	return;

    if ((kf = fopen(rcFile(keymap_file), "rt")) == NULL ||
	(kfd = fileno(kf)) < 0 || fstat(kfd, &kstat) ||
	(!force && keymap_initialized &&
	 kstat.st_mtime == current_keymap_file.st_mtime &&
	 kstat.st_dev == current_keymap_file.st_dev &&
	 kstat.st_ino == current_keymap_file.st_ino &&
	 kstat.st_size == current_keymap_file.st_size))
	return;

    func_tab.next = &w3mDefaultFuncKeyTabList;
    edit_tab.next = &w3mDefaultEditKeyTabList;
#ifdef USE_MENU
    menu_tab.next = &w3mDefaultMenuKeyTabList;
#endif
    lineno = 0;
    while (!feof(kf)) {
	line = Strfgets(kf);
	lineno++;
	Strchop(line);
	Strremovefirstspaces(line);
	if (line->length == 0)
	    continue;
	p = line->ptr;
	s = getWord(&p);
	if (*s == '#')		/* comment */
	    continue;
	if (!strcmp(s, "keymap"))
	    ;
	else if (!strcmp(s, "verbose")) {
	    s = getWord(&p);
	    if (*s)
		verbose = str_to_bool(s, verbose);
	    continue;
	} else {                /* error */
	    emsg = Sprintf("line %d: syntax error '%s'", lineno, s)->ptr;
	    record_err_message(emsg);
	    if (verbose)
		disp_message_nsec(emsg, FALSE, 0, FALSE, TRUE);
	    continue;
	}
	setKeymap(p, lineno, verbose, &func_tab, &edit_tab
#ifdef USE_MENU
		  , &menu_tab
#endif
		  );
    }
    fclose(kf);
    w3mFuncKeyTabList = copyKeyTabList(&func_tab);
    w3mEditKeyTabList = copyKeyTabList(&edit_tab);
#ifdef USE_MENU
    w3mMenuKeyTabList = copyKeyTabList(&menu_tab);
#endif
    keymap_initialized = TRUE;
    current_keymap_file = kstat;
}

#define DEFKEY(str) str, sizeof(str) - 1

static struct {
    const char *s;
    size_t len;
    int key;
} arrow_key[] = {
    {DEFKEY("UP"), K_ESCB | 'A'},
    {DEFKEY("DOWN"), K_ESCB | 'B'},
    {DEFKEY("RIGHT"), K_ESCB | 'C'},
    {DEFKEY("LEFT"), K_ESCB | 'D'},
}, esc_key[] = {
    {DEFKEY("ESC-"), K_ESC},
    {DEFKEY("ESC "), K_ESC},
    {DEFKEY("\\E"), K_ESC},
    {DEFKEY("M-"), K_ESC},
    {DEFKEY("\x1B"), K_ESC},
}, ctrl_key[] = {
    {DEFKEY("C-"), 1},
    {DEFKEY("CTL-"), 1},
    {DEFKEY("CTRL-"), 1},
    {DEFKEY("^"), 1},
}, sym_key[] = {
    {DEFKEY("SPC"), ' '},
    {DEFKEY("TAB"), '\t'},
    {DEFKEY("DEL"), DEL_CODE},
    {DEFKEY("CR"), '\r'},
    {DEFKEY("LF"), '\n'},
}
#ifdef USE_MOUSE
, mouse_key[] = {
    {DEFKEY("MOUSE-CLICK-"), K_MOUSE(CLICK)},
    {DEFKEY("MOUSE-DCLICK-"), K_MOUSE(DCLICK)},
    {DEFKEY("MOUSE-DOUBLE-CLICK-"), K_MOUSE(DCLICK)},
    {DEFKEY("MOUSE-DRAG-"), K_MOUSE(DRAG)},
    {DEFKEY("MOUSE-MOVE-"), K_MOUSE(MOVE)},
}
#endif
;

int
getKey(char *s)
{
    int c, esc, ctrl, key, n, i;

    if (!s || !*s)
	return -1;

    for (esc = ctrl = key = n = 0 ; *s ;) {
	if (!esc && !ctrl)
	    key <<= K_ONE_LEN;

	for (i = sizeof(arrow_key) / sizeof(arrow_key[0]) ; i > 0 ;) {
	    --i;

	    if (!strncasecmp(s, arrow_key[i].s, arrow_key[i].len)) {
		key |= arrow_key[i].key;
		s += arrow_key[i].len;
		goto next_key;
	    }
	}

	for (i = sizeof(esc_key) / sizeof(esc_key[0]) ; i > 0 ;) {
	    --i;

	    if (!strncasecmp(s, esc_key[i].s, esc_key[i].len)) {
		esc = esc_key[i].key;
		s += esc_key[i].len;
		goto next_modifier;
	    }
	}

	for (i = sizeof(ctrl_key) / sizeof(ctrl_key[0]) ; i > 0 ;) {
	    --i;

	    if (!strncasecmp(s, ctrl_key[i].s, ctrl_key[i].len)) {
		ctrl = 1;
		s += ctrl_key[i].len;
		goto next_modifier;
	    }
	}

#ifdef USE_MOUSE
	if (!n && !esc && !ctrl)
	    for (i = sizeof(mouse_key) / sizeof(mouse_key[0]) ; i > 0 ;) {
		--i;

		if (!strncasecmp(s, mouse_key[i].s, mouse_key[i].len)) {
		    char *e;
		    long btn;

		    key = mouse_key[i].key;
		    s += mouse_key[i].len;

		    if ((btn = strtol(s, &e, 10)) <= 0 || btn > K_MOUSE_NBTNS || *e)
			return -1;

		    key += btn - 1;
		    ++n;
		    goto end;
		}
	    }
#endif

	if (!esc && ctrl && *s == '[') {	/* ^[ */
	    ++s;
	    ctrl = 0;
	    esc = K_ESC;
	    goto next_modifier;
	}

	if (esc && !ctrl) {
	    if (*s == '[' || *s == 'O') {	/* ^[[, ^[O */
		++s;
		esc = K_ESCB;
		goto next_modifier;
	    }
	}

	if (ctrl) {
	    if (*s >= '@' && *s <= '_')		/* ^@ .. ^_ */
		key |= esc | (*s - '@');
	    else if (*s >= 'a' && *s <= 'z')	/* ^a .. ^z */
		key |= esc | (*s - 'a' + 1);
	    else if (*s == '?')			/* ^? */
		key |= esc | DEL_CODE;
	    else
		return -1;

	    ++s;
	    goto next_key;
	}

	if (esc == K_ESCB && IS_DIGIT(*s)) {
	    c = (unsigned char)*s++ - '0';

	    if (IS_DIGIT(*s))
		c = c * 10 + (unsigned char)*s++ - '0';

	    if (*s++ != '~')
		return -1;

	    key |= K_ESCD | c;
	    goto next_key;
	}

	for (i = sizeof(sym_key) / sizeof(sym_key[0]) ; i > 0 ;) {
	    --i;

	    if (!strncasecmp(s, sym_key[i].s, sym_key[i].len)) {
		key |= esc | sym_key[i].key;
		s += sym_key[i].len;
		goto next_key;
	    }
	}

	if (*s == '\\' && *(s + 1) != '\0') {
	    switch (*(s + 1)) {
	    case 'a':		/* ^g */
		key |= esc | CTRL_G;
		break;
	    case 'b':		/* ^h */
		key |= esc | CTRL_H;
		break;
	    case 't':		/* ^i */
		key |= esc | CTRL_I;
		break;
	    case 'n':		/* ^j */
		key |= esc | CTRL_J;
		break;
	    case 'r':		/* ^m */
		key |= esc | CTRL_M;
		break;
	    case 'e':		/* ^[ */
		key |= esc | ESC_CODE;
		break;
	    default:
		if (!IS_ASCII(*(s + 1)))
		    return -1;

		key |= esc | *(s + 1);
		break;
	    }

	    s += 2;
	    goto next_key;
	}

	if (IS_ASCII(*s))		/* Ascii */
	    key |= esc | *s++;
	else
	    return -1;
    next_key:
	++n;
	esc = ctrl = 0;
    next_modifier:
    }
#ifdef USE_MOUSE
end:
#endif
    return (n <= 0 || n > K_LEN_MAX) ? -1 : (key << K_LEN_LEN) | n;
}

int
lookupKeyIndex(int key, KeyTabList *tabl)
{
    int b, e, i, this_key;

    for (b = 0, e = tabl->nitem ; b < e ;) {
	i = (b + e) / 2;
	this_key = tabl->key_table[i] & K_CMP_MASK;

	if (key < this_key)
	    e = i;
	else if (key > this_key)
	    b = i + 1;
	else
	    return i;
    }

    return -1;
}

FuncList *
lookupKey(int key, KeyTabList *tabl)
{
    int i;

    for (; tabl ; tabl = tabl->next)
	if ((i = lookupKeyIndex(key, tabl)) >= 0)
	    return &w3mFuncList[K_GET_FUNC(tabl->key_table[i])];

    return NULL;
}

char *
getKeyData(int key)
{
    if (KeyData) {
	void *value;

	if (btri_fast_search_uint(key, KeyData, &value) != bt_failure)
	    return value;
    }

    return NULL;
}

char *
getWord(char **str)
{
    char *p, *s;

    p = *str;
    SKIP_BLANKS(p);
    for (s = p; *p && ! IS_SPACE(*p) && *p != ';'; p++) ;
    *str = p;
    return Strnew_charp_n(s, p - s)->ptr;
}

char *
getQWord(char **str)
{
    Str tmp = Strnew();
    char *p;
    int in_q = 0, in_dq = 0, esc = 0;

    p = *str;
    SKIP_BLANKS(p);
    for (; *p; p++) {
	if (esc) {
	    if (in_q) {
		if (*p != '\\' && *p != '\'')	/* '..\\..', '..\'..' */
		    Strcat_char(tmp, '\\');
	    }
	    else if (in_dq) {
		if (*p != '\\' && *p != '"')	/* "..\\..", "..\".." */
		    Strcat_char(tmp, '\\');
	    }
	    else {
		if (*p != '\\' && *p != '\'' &&		/* ..\\.., ..\'..  */
		    *p != '"' && !IS_SPACE(*p))		/* ..\".., ..\.. */
		    Strcat_char(tmp, '\\');
	    }
	    Strcat_char(tmp, *p);
	    esc = 0;
	}
	else if (*p == '\\') {
	    esc = 1;
	}
	else if (in_q) {
	    if (*p == '\'')
		in_q = 0;
	    else
		Strcat_char(tmp, *p);
	}
	else if (in_dq) {
	    if (*p == '"')
		in_dq = 0;
	    else
		Strcat_char(tmp, *p);
	}
	else if (*p == '\'') {
	    in_q = 1;
	}
	else if (*p == '"') {
	    in_dq = 1;
	}
	else if (IS_SPACE(*p) || *p == ';') {
	    break;
	}
	else {
	    Strcat_char(tmp, *p);
	}
    }
    *str = p;
    return tmp->ptr;
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
