/*	$OpenBSD: kbd.c,v 1.35 2021/03/01 10:51:14 lum Exp $	*/

/* This file is in the public domain. */

/*
 *	Terminal independent keyboard handling.
 */

#include <sys/queue.h>
#include <signal.h>
#include <stdio.h>

#include "def.h"
#include "kbd.h"
#include "key.h"
#include "macro.h"

#ifdef  MGLOG
#include "log.h"
#endif

#define METABIT 0x80

#define PROMPTL 80
char	 prompt[PROMPTL] = "", *promptp = prompt;

static int mgwrap(PF, int, int);

static int		 use_metakey = TRUE;
static int		 pushed = FALSE;
static int		 pushedc;

struct map_element	*ele;
struct key		 key;
int			 rptcount;

/*
 * Toggle the value of use_metakey
 */
int
do_meta(int f, int n)
{
	if (f & FFARG)
		use_metakey = n > 0;
	else
		use_metakey = !use_metakey;
	ewprintf("Meta keys %sabled", use_metakey ? "en" : "dis");
	return (TRUE);
}

static int	 bs_map = 0;

/*
 * Toggle backspace mapping
 */
int
bsmap(int f, int n)
{
	if (f & FFARG)
		bs_map = n > 0;
	else
		bs_map = !bs_map;
	ewprintf("Backspace mapping %sabled", bs_map ? "en" : "dis");
	return (TRUE);
}

void
ungetkey(int c)
{
	if (use_metakey && pushed && c == CCHR('['))
		pushedc |= METABIT;
	else
		pushedc = c;
	pushed = TRUE;
}

int
getkey(int flag)
{
	int	 c;

	if (flag && !pushed) {
		if (prompt[0] != '\0' && ttwait(2000)) {
			/* avoid problems with % */
			ewprintf("%s", prompt);
			/* put the cursor back */
			update(CMODE);
			epresf = KCLEAR;
		}
		if (promptp > prompt)
			*(promptp - 1) = ' ';
	}
	if (pushed) {
		c = pushedc;
		pushed = FALSE;
	} else
		c = ttgetc();

	if (bs_map) {
		if (c == CCHR('H'))
			c = CCHR('?');
		else if (c == CCHR('?'))
			c = CCHR('H');
	}
	if (use_metakey && (c & METABIT)) {
		pushedc = c & ~METABIT;
		pushed = TRUE;
		c = CCHR('[');
	}
	if (flag && promptp < &prompt[PROMPTL - 5]) {
		promptp = getkeyname(promptp,
		    sizeof(prompt) - (promptp - prompt) - 1, c);
		*promptp++ = '-';
		*promptp = '\0';
	}
	return (c);
}

/*
 * doscan scans a keymap for a keyboard character and returns a pointer
 * to the function associated with that character.  Sets ele to the
 * keymap element the keyboard was found in as a side effect.
 */
PF
doscan(KEYMAP *map, int c, KEYMAP **newmap)
{
	struct map_element	*elec = &map->map_element[0];
	struct map_element	*last = &map->map_element[map->map_num];
	PF		 ret;

	while (elec < last && c > elec->k_num)
		elec++;

	/* used by prefix and binding code */
	ele = elec;
	if (elec >= last || c < elec->k_base)
		ret = map->map_default;
	else
		ret = elec->k_funcp[c - elec->k_base];
	if (ret == NULL && newmap != NULL)
		*newmap = elec->k_prefmap;

	return (ret);
}

int
doin(void)
{
	KEYMAP	*curmap;
	PF	 funct;

	*(promptp = prompt) = '\0';
	curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
	key.k_count = 0;
	while ((funct = doscan(curmap, (key.k_chars[key.k_count++] =
	    getkey(TRUE)), &curmap)) == NULL)
		/* nothing */;

#ifdef  MGLOG
	if (!mglog(funct, curmap))
		ewprintf("Problem with logging");
#endif

	if (macrodef && macrocount < MAXMACRO)
		macro[macrocount++].m_funct = funct;

	return (mgwrap(funct, 0, 1));
}

int
rescan(int f, int n)
{
	int	 c;
	KEYMAP	*curmap;
	int	 i;
	PF	 fp = NULL;
	int	 md = curbp->b_nmodes;

	for (;;) {
		if (ISUPPER(key.k_chars[key.k_count - 1])) {
			c = TOLOWER(key.k_chars[key.k_count - 1]);
			curmap = curbp->b_modes[md]->p_map;
			for (i = 0; i < key.k_count - 1; i++) {
				if ((fp = doscan(curmap, (key.k_chars[i]),
				    &curmap)) != NULL)
					break;
			}
			if (fp == NULL) {
				if ((fp = doscan(curmap, c, NULL)) == NULL)
					while ((fp = doscan(curmap,
					    key.k_chars[key.k_count++] =
					    getkey(TRUE), &curmap)) == NULL)
						/* nothing */;
				if (fp != rescan) {
					if (macrodef && macrocount <= MAXMACRO)
						macro[macrocount - 1].m_funct
						    = fp;
					return (mgwrap(fp, f, n));
				}
			}
		}
		/* try previous mode */
		if (--md < 0)
			return (ABORT);
		curmap = curbp->b_modes[md]->p_map;
		for (i = 0; i < key.k_count; i++) {
			if ((fp = doscan(curmap, (key.k_chars[i]), &curmap)) != NULL)
				break;
		}
		if (fp == NULL) {
			while ((fp = doscan(curmap, key.k_chars[i++] =
			    getkey(TRUE), &curmap)) == NULL)
				/* nothing */;
			key.k_count = i;
		}
		if (fp != rescan && i >= key.k_count - 1) {
			if (macrodef && macrocount <= MAXMACRO)
				macro[macrocount - 1].m_funct = fp;
			return (mgwrap(fp, f, n));
		}
	}
}

int
universal_argument(int f, int n)
{
	KEYMAP	*curmap;
	PF	 funct;
	int	 c, nn = 4;

	if (f & FFUNIV)
		nn *= n;
	for (;;) {
		key.k_chars[0] = c = getkey(TRUE);
		key.k_count = 1;
		if (c == '-')
			return (negative_argument(f, nn));
		if (c >= '0' && c <= '9')
			return (digit_argument(f, nn));
		curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
		while ((funct = doscan(curmap, c, &curmap)) == NULL) {
			key.k_chars[key.k_count++] = c = getkey(TRUE);
		}
		if (funct != universal_argument) {
			if (macrodef && macrocount < MAXMACRO - 1) {
				if (f & FFARG)
					macrocount--;
				macro[macrocount++].m_count = nn;
				macro[macrocount++].m_funct = funct;
			}
			return (mgwrap(funct, FFUNIV, nn));
		}
		nn <<= 2;
	}
}

/* ARGSUSED */
int
digit_argument(int f, int n)
{
	KEYMAP	*curmap;
	PF	 funct;
	int	 nn, c;

	nn = key.k_chars[key.k_count - 1] - '0';
	for (;;) {
		c = getkey(TRUE);
		if (c < '0' || c > '9')
			break;
		nn *= 10;
		nn += c - '0';
	}
	key.k_chars[0] = c;
	key.k_count = 1;
	curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
	while ((funct = doscan(curmap, c, &curmap)) == NULL) {
		key.k_chars[key.k_count++] = c = getkey(TRUE);
	}
	if (macrodef && macrocount < MAXMACRO - 1) {
		if (f & FFARG)
			macrocount--;
		else
			macro[macrocount - 1].m_funct = universal_argument;
		macro[macrocount++].m_count = nn;
		macro[macrocount++].m_funct = funct;
	}
	return (mgwrap(funct, FFOTHARG, nn));
}

int
negative_argument(int f, int n)
{
	KEYMAP	*curmap;
	PF	 funct;
	int	 c;
	int	 nn = 0;

	for (;;) {
		c = getkey(TRUE);
		if (c < '0' || c > '9')
			break;
		nn *= 10;
		nn += c - '0';
	}
	if (nn)
		nn = -nn;
	else
		nn = -n;
	key.k_chars[0] = c;
	key.k_count = 1;
	curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
	while ((funct = doscan(curmap, c, &curmap)) == NULL) {
		key.k_chars[key.k_count++] = c = getkey(TRUE);
	}
	if (macrodef && macrocount < MAXMACRO - 1) {
		if (f & FFARG)
			macrocount--;
		else
			macro[macrocount - 1].m_funct = universal_argument;
		macro[macrocount++].m_count = nn;
		macro[macrocount++].m_funct = funct;
	}
	return (mgwrap(funct, FFNEGARG, nn));
}

/*
 * Insert a character.	While defining a macro, create a "LINE" containing
 * all inserted characters.
 */
int
selfinsert(int f, int n)
{
	struct line	*lp;
	int	 c;
	int	 count;

	if (n < 0)
		return (FALSE);
	if (n == 0)
		return (TRUE);
	c = key.k_chars[key.k_count - 1];

	if (macrodef && macrocount < MAXMACRO) {
		if (f & FFARG)
			macrocount -= 2;

		/* last command was insert -- tack on the end */
		if (lastflag & CFINS) {
			macrocount--;
			/* Ensure the line can handle the new characters */
			if (maclcur->l_size < maclcur->l_used + n) {
				if (lrealloc(maclcur, maclcur->l_used + n) ==
				    FALSE)
					return (FALSE);
			}
			maclcur->l_used += n;
			/* Copy in the new data */
			for (count = maclcur->l_used - n;
			    count < maclcur->l_used; count++)
				maclcur->l_text[count] = c;
		} else {
			macro[macrocount - 1].m_funct = insert;
			if ((lp = lalloc(n)) == NULL)
				return (FALSE);
			lp->l_bp = maclcur;
			lp->l_fp = maclcur->l_fp;
			maclcur->l_fp = lp;
			maclcur = lp;
			for (count = 0; count < n; count++)
				lp->l_text[count] = c;
		}
		thisflag |= CFINS;
	}
	if (c == *curbp->b_nlchr) {
		do {
			count = lnewline();
		} while (--n && count == TRUE);
		return (count);
	}

	/* overwrite mode */
	if (curbp->b_flag & BFOVERWRITE) {
		lchange(WFEDIT);
		while (curwp->w_doto < llength(curwp->w_dotp) && n--)
			lputc(curwp->w_dotp, curwp->w_doto++, c);
		if (n <= 0)
			return (TRUE);
	}
	return (linsert(n, c));
}

/*
 * selfinsert() can't be called directly from a startup file or by
 * 'eval-current-buffer' since it is by design, meant to be called interactively
 * as characters are typed in a buffer. ask_selfinsert() allows selfinsert() to
 * be used by excline(). Having ask_selfinsert() helps with regression testing.
 * No manual page entry since use case is a bit obscure. See 'insert' command.
 */
int
ask_selfinsert(int f, int n)
{
	char	*c, cbuf[2];

	if ((c = eread("Insert a character: ", cbuf, sizeof(cbuf),
	    EFNEW)) == NULL || (c[0] == '\0'))
		return (ABORT);

	key.k_chars[0] = *c;
	key.k_chars[1] = '\0';
	key.k_count = 1;

	return (selfinsert(FFRAND, 1));
}

/*
 * This could be implemented as a keymap with everything defined as self-insert.
 */
int
quote(int f, int n)
{
	int	 c;

	key.k_count = 1;
	if ((key.k_chars[0] = getkey(TRUE)) >= '0' && key.k_chars[0] <= '7') {
		key.k_chars[0] -= '0';
		if ((c = getkey(TRUE)) >= '0' && c <= '7') {
			key.k_chars[0] <<= 3;
			key.k_chars[0] += c - '0';
			if ((c = getkey(TRUE)) >= '0' && c <= '7') {
				key.k_chars[0] <<= 3;
				key.k_chars[0] += c - '0';
			} else
				ungetkey(c);
		} else
			ungetkey(c);
	}
	return (selfinsert(f, n));
}

/*
 * Wraper function to count invocation repeats.
 * We ignore any function whose sole purpose is to get us
 * to the intended function.
 */
static int
mgwrap(PF funct, int f, int n)
{
	static	 PF ofp;

	if (funct != rescan &&
	    funct != negative_argument &&
	    funct != digit_argument &&
	    funct != universal_argument) {
		if (funct == ofp)
			rptcount++;
		else
			rptcount = 0;
		ofp = funct;
	}

	return ((*funct)(f, n));
}
