/*
 * Key binding & rebinding & unbinding. 
 */
#include	"no_macro.h"

#include	"def.h"
#include	"kbd.h"
#include	"buffer.h"

#ifndef NO_MACRO
#include	"macro.h"
#endif

#ifdef	ANSI
#include <stdlib.h>
#include <string.h>
#endif

static int remap 
PROTO((struct keymap * curmap, int c, struct function * funct));
static int dobind 
PROTO((struct keymap * curmap, char *p, int unbind));
static int growelement 
PROTO((struct keymap *, struct map_element *, int));
static int initelement 
PROTO((struct keymap *, struct map_element *, int));
static struct keymap *realocmap 
PROTO((struct keymap * curmap));
static VOID fixmap 
PROTO((struct keymap *, struct keymap *, struct keymap *));

/*
 * Bind a key to a function. No attempt is made to reclaim space no longer
 * used, if this is a problem flags must be added to indicate malloced verses
 * static storage in both keymaps and map_elements. 
 */
static int
remap(curmap, c, funct)
	register struct keymap *curmap;
	int             c;
	struct function *funct;
{
	extern struct map_element *ele;

	/*
	 * At this point, we know that A) curmap is the map that's going to
	 * be changed; B) c is the key that's going to change in that map; C)
	 * ele is one of three things: 1) beyond the end of the map, 2) above
	 * c, and the preceeding element is below it, or 3) the element that
	 * needs to be changed. 
	 *
	 * We deal with this by changing the map for case 1 & 2, and then
	 * letting the case 3 code deal with the changes. 
	 */

	if (ele >= &curmap->map_element[curmap->map_num]) {
		/*
		 * Case 1: ele is past the end of the map. In this case, we
		 * try to grow the last element to encompass the char if it's
		 * close enough. If it isn't, we create a new map element,
		 * and put the the char in that. 
		 */
		if ((--ele)->k_num + MAPELEDEF >= c) {
			if (growelement(curmap, ele, c) == NULL)
				return FALSE;
		} else if ((curmap = realocmap(curmap)) == NULL)
			return FALSE;
		else if (initelement(curmap, ++ele, c) == FALSE)
			return FALSE;
		else
			curmap->map_num += 1;
	} else if (c < ele->k_base) {
		/*
		 * Case 2: c lands between map elements. Ele points to the
		 * next one. As before, but with three cases: try to grow ele
		 * down to c; try to grow ele - 1 up to c; and if all else
		 * fails, get a new element inited for c put between them. 
		 */

		if (ele->k_base <= c + MAPELEDEF) {
			if (growelement(curmap, ele, c) == FALSE)
				return FALSE;
		} else if (ele > curmap->map_element
			   && ele[-1].k_num + MAPELEDEF >= c) {
			if (growelement(curmap, --ele, c) == FALSE)
				return FALSE;
		} else if ((curmap = realocmap(curmap)) == NULL)
			return FALSE;
		else {
			/*
			 * adding a new element to the map. If this code
			 * didn't leak memory already, I'd seriously consider
			 * checking on the possiblities of an element merge
			 * here. 
			 */
			struct map_element save;
			register struct map_element *ep;

			if (initelement(curmap, &save, c) == FALSE)
				return FALSE;

			/* Shuffle upper elements up */
			for (ep = &curmap->map_element[curmap->map_num]; ep > ele; ep -= 1)
				ep[0] = ep[-1];
			*ep = save;
			curmap->map_num += 1;
		}
	}
	/*
	 * Case 3: Just change the key in the existing map. The other two
	 * cases twiddle the kemap so it's right, then fall through to this
	 * code. 
	 */
	ele->k_entry[c - ele->k_base] = *funct;
	return TRUE;
}

/*
 * growelement - grow an element up/down to encompass the given character,
 * shuffling things as needed. We don't check the reasonableness of doing
 * that extension, as the caller (which should only be remap) has to do that
 * before calling us. 
 */
static int
growelement(curmap, ele, c)
	struct keymap  *curmap;
	register struct map_element *ele;
	register int    c;
{
	register int    newnum, newbase, i;
	register struct function *fp;

	if (c < ele->k_base) {	/* growing down */
		newbase = c;
		newnum = ele->k_num;
	} else {		/* Growing up */
		newbase = ele->k_base;
		newnum = c;
	}

	if ((fp = (struct function *)
	malloc((newnum - newbase + 1) * sizeof(struct function))) == NULL) {
		ewprintf("Out of memory");
		return FALSE;
	}
	for (i = newbase; i < ele->k_base; i += 1)
		fp[i - newbase] = *curmap->map_default;
	for (; i <= ele->k_num; i += 1)
		fp[i - newbase] = ele->k_entry[i - ele->k_base];
	for (; i < newnum; i += 1)
		fp[i - newbase] = *curmap->map_default;

	ele->k_entry = fp;
	ele->k_base = newbase;
	ele->k_num = newnum;
	return TRUE;
}

/*
 * initelement sets up the existing element pointer as apropos for the
 * character and map it just got passed. 
 */
static int
initelement(curmap, ele, c)
	struct keymap  *curmap;
	register struct map_element *ele;
	int             c;
{
	register struct function *fp;

	if ((fp = (struct function *) malloc(sizeof(struct function))) == NULL)
		return FALSE;
	ele->k_base = c;
	ele->k_num = c;
	ele->k_entry = fp;
	return TRUE;
}

/*
 * Adds another element to the map - moving it if necessary. Also makes sure
 * that the global ele points to the same place afterwards. 
 */
static struct keymap *
realocmap(curmap)
	register struct keymap *curmap;
{
	register struct keymap *mp;
	register int    i;
	extern int      nmaps;


	if (curmap->map_num < curmap->map_max)
		return curmap;

	if ((mp = (struct keymap *) malloc((unsigned) (sizeof(struct keymap) +
						       (curmap->map_max + MAPGROW - 1) * sizeof(struct map_element)))) == NULL) {
		ewprintf("Out of memory");
		return NULL;
	}
	mp->map_num = curmap->map_num;
	mp->map_max = curmap->map_max + MAPGROW;
	mp->map_default = curmap->map_default;

	for (i = curmap->map_num; i--;)
		mp->map_element[i] = curmap->map_element[i];

	for (i = nmaps; i--;) {
		if (map_table[i].p_map == curmap)
			map_table[i].p_map = mp;
		else
			fixmap(curmap, mp, map_table[i].p_map);
	}
	ele = &mp->map_element[ele - &curmap->map_element[0]];
	return mp;
}

/* fix references to a realocated keymap (recursive) */
static          VOID
fixmap(curmap, mp, mt)
	struct keymap  *curmap, *mp;
	register struct keymap *mt;
{
	register int    i, j;
	register struct map_element *ele;
	register struct function *fp;

	for (i = mt->map_num; i--;) {
		ele = &(mt->map_element[i]);
		for (j = ele->k_num; j >= ele->k_base; j -= 1) {
			fp = &(ele->k_entry[j - ele->k_base]);
			if (fp->f_type == F_PREFIX)
				if ((struct keymap *) fp->f_pref == curmap)
					fp->f_pref = (int (*) ()) mp;
				else
					fixmap(curmap, mp, (struct keymap *) fp->f_pref);
		}
	}
}

/*
 * do the input for local-set-key, global-set-key  and define-key then call
 * remap to do the work. 
 */
static int
dobind(curmap, p, unbind)
	register struct keymap *curmap;
	char           *p;
	int             unbind;
{
	char            prompt[80];
	char           *pep;
	int             c;
	int             s;
	int             rescan();
	struct function funct;

	(VOID) strcpy(prompt, p);
	pep = prompt + strlen(prompt);
	for (;;) {
		ewprintf("%s", prompt);
		pep[-1] = ' ';
		pep = keyname(pep, c = getkey(DISSCR));
		funct = *doscan(curmap, c);
		if (funct.f_type != F_PREFIX)
			break;
		*pep++ = '-';
		*pep = '\0';
		curmap = (struct keymap *) funct.f_pref;
	}
	if (unbind)
		funct.f_funcp = rescan;
	else {
		if ((s = eread("%s to command: ", prompt, 80, EFFUNC | EFNEW, prompt))
		    != TRUE)
			return s;
		if ((funct.f_funcp = name_function(prompt)) != NULL)
			funct.f_type = F_CFUNCT;
#ifndef	NO_MACRO
		else if ((funct.f_macro = (int (*) ()) find_macro(prompt)) != NULL)
			funct.f_type = F_MACRO;
#endif
		else if ((funct.f_pref = (int (*) ()) name_map(prompt)) != NULL)
			funct.f_type = F_PREFIX;
		else {
			ewprintf("[No match]");
			return FALSE;
		}
	}
	return remap(curmap, c, &funct);
}
/*
 * This function redefines a key in any keymap. 
 */
/* ARGSUSED */
define_key(f, n)
{
	static char     buf[48] = "Define key map: ";
	struct maps    *mp;
	char           *strncat();

	buf[16] = '\0';
	if (eread(buf, &buf[16], 48 - 16, EFNEW) != TRUE)
		return FALSE;
	if ((mp = name_mode(&buf[16])) == NULL) {
		ewprintf("Unknown map %s", &buf[16]);
		return FALSE;
	}
	(VOID) strncat(&buf[16], " key: ", 48 - 16 - 1);
	return dobind(mp->p_map, buf, FALSE);
}
/*
 * This function modifies the fundamental keyboard map. 
 */
/* ARGSUSED */
bindtokey(f, n)
{
	return dobind(map_table[0].p_map, "Global set key: ", FALSE);
}

/*
 * This function modifies the current mode's keyboard map. 
 */
/* ARGSUSED */
localbind(f, n)
{
	return dobind(curbp->b_modes[curbp->b_nmodes]->p_map, "Local set key: ",
		      FALSE);
}

unbindtokey(f, n)
	int             f, n;
{
	return dobind(map_table[0].p_map, "Global unset key: ", TRUE);
}

localunbind(f, n)
	int             f, n;
{
	return dobind(curbp->b_modes[curbp->b_nmodes]->p_map, "Local unset key: ",
		      TRUE);
}
