/*
 * keys.c: Decides what happens when you press a key
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "$Id: keys.c,v 1.9 1993/11/14 10:25:28 mrgreen Exp $";
#endif

#include "irc.h"

#include "config.h"
#include "edit.h"
#include "history.h"
#include "hold.h"
#include "ircaux.h"
#include "input.h"
#include "keys.h"
#include "names.h"
#include "output.h"
#include "screen.h"
#include "term.h"
#include "translat.h"
#include "vars.h"
#include "window.h"

/* !!!DONT!!! change this number on a whim!  You could break stuff.  It
 * is defined here so that if/when we want to add more key maps, it will
 * be real easy to change all the stuff that is dependant on how many
 * key maps there are.  However, there is no support for more then 10
 * keymaps in the code!  You have been warned!
 */
#define MAX_META 9
#define NUM_KEYMAPS MAX_META + 1

static void new_key _((int, int, int, int, int, char *));

/* This is it.. this is the keymap -- replaces all those thousands of
 * lines that used to be at the end of this file.... good riddens, too.
 */
KeyMap *keys[NUM_KEYMAPS][256];

/*
 * init_keys_1 - initializes all the key bindings to zero and calls
 *    init_keys_2 which does all the default bindings
 */
void init_keys_1 _((void))
{
	static void init_keys_2 _((void));
	int i, j;

	for (i = 0; i <= MAX_META; i++)
		for (j = 0; j <= 255; j++)
			keys[i][j] = NULL;

	init_keys_2();
}
 
/*
 * lookup_function: looks up an irc function by name, and returns the
 * number of functions that match the name, and sets where index points
 * to to be the index of the (first) function found.
 */
#ifdef __STDC__
int lookup_function (char *name, int *lf_index)
#else
int lookup_function(name, lf_index)
	char	*name;
	int	*lf_index;
#endif
{
	int	len,
		cnt,
		i;

	if (name)
	{
		upper(name);
		len = strlen(name);
		cnt = 0;
		*lf_index = -1;
		for (i = 0; i < NUMBER_OF_FUNCTIONS; i++)
		{
			if (strncmp(name, key_names[i].name, len) == 0)
			{
				cnt++;
				if (*lf_index == -1)
					*lf_index = i;
			}
		}
		if (*lf_index == -1)
			return (0);
		if (my_stricmp(name, key_names[*lf_index].name) == 0)
			return (1);
		else
			return (cnt);
	}
	return (0);
}

/*
 * display_key: converts the character c to a displayable form and returns
 * it.  Very simple indeed 
 */
#ifdef __STDC__
unsigned char *display_key (unsigned char c)
#else
unsigned char *
display_key(c)
	unsigned char c;
#endif
{
	static	unsigned char key[3];

	key[2] = (char) 0;
	if (c < 32)
	{
		key[0] = '^';
		key[1] = c + 64;
	}
	else if (c == '\177')
	{
		key[0] = '^';
		key[1] = '?';
	}
	else
	{
		key[0] = c;
		key[1] = (char) 0;
	}
	return (key);
}

/*
 * show_binding: given the ascii value of a key and a meta key status (1 for
 * meta1 keys, 2 for meta2 keys, anything else for normal keys), this will
 * display the key binding for the key in a nice way
 */
#ifdef __STDC__
void show_binding (unsigned char c, int meta)
#else
void show_binding(c, meta)
	unsigned char	c;
	int	meta;
#endif
{
	char	meta_str[8];

	*meta_str = 0;
	if (meta < 1 || meta > MAX_META)
		meta = 0;
	else
		sprintf(meta_str, "META%d-", meta);

	if (keys[meta][c])
		say("%s%s is bound to %s %s", meta_str, display_key(c),
			key_names[keys[meta][c]->key_index].name, (keys[meta][c]->stuff &&
			(*(keys[meta][c]->stuff))) ? keys[meta][c]->stuff : empty_string);
	else
		say("%s%s is bound to NOTHING", meta_str, display_key(c));
}

/*
 * parse_key: converts a key string. Accepts any key, or ^c where c is any
 * key (representing control characters), or META1- or META2- for meta1 or
 * meta2 keys respectively.  The string itself is converted to true ascii
 * value, thus "^A" is converted to 1, etc.  Meta key info is removed and
 * returned as the function value, 0 for no meta key, 1 for meta1, and 2 for
 * meta2.  Thus, "META1-a" is converted to "a" and a 1 is returned.
 * Furthermore, if ^X is bound to META2_CHARACTER, and "^Xa" is passed to
 * parse_key(), it is converted to "a" and 2 is returned.  Do ya understand
 * this? 
 */
#ifdef __STDC__
int parse_key (char *key_str)
#else
int parse_key(key_str)
	char	*key_str;
#endif
{
	char	*ptr1,
		*ptr2;
	unsigned char	c;
	int	meta = 0;

	ptr2 = ptr1 = key_str;
	while (*ptr1)
	{
		if (*ptr1 == '^')
		{
			ptr1++;
			switch (*ptr1)
			{
				case 0:
					*(ptr2++) = '^';
					break;
				case '?':
					*(ptr2++) = '\177';
					ptr1++;
					break;
				default:
					c = *(ptr1++);
					if (islower(c))
						c = toupper(c);
					if (c < 64)
					{
						say("Illegal key sequence: ^%c", c);
						return (-1);
					}
					*(ptr2++) = c - 64;
			}
		}
		else
			*(ptr2++) = *(ptr1++);
	}
	*ptr2 = (char) 0;
	if (strlen(key_str) > 1)
	{
		/* 
		 * There used to be ten cases here that checked for
		 * each of the METAX- strings.  Now we want to be able
		 * to have double digit METAXX strings, so we look after
		 * the META and extract the number there before the hyphen.
		 * then we remove the METAXX- string.
		 */
		if (my_strnicmp(key_str, "META", 4) == 0)
		{
			char *ptr = key_str+4,
			     *str = key_str+4;

			while (*ptr && *ptr != '-')
				ptr++;
			if (*ptr)
				ptr++;
			meta = atoi(str);
			strcpy(key_str, ptr);
		}
		/* 
		 * Here too, used to be ten cases.  Im being a little more
		 * risky by doing it this way, becuase it makes the assumption
		 * that METAX_CHARACTERs are all defined as consecutive
		 * numbers.  A note has gone in keys.h.proto to not break
		 * up the METAX_CHARACTER defines.
		 */
		else
		{
			int foo = META1_CHARACTER - 1; /* just to make sure */
			if (keys[0][(u_char)*key_str])
				foo = keys[0][(u_char) *key_str]->key_index;
			if ((foo >= META1_CHARACTER) && (foo - META1_CHARACTER < MAX_META))
				meta = foo - META1_CHARACTER + 1;
			strcpy(key_str, key_str + 1);
		}
	}
	return (meta);
}

/*
 * bind_it: does the actually binding of the function to the key with the
 * given meta modifier
 */
#ifdef __STDC__
static void bind_it (char *function, char *string, char key, int meta)
#else
static	void
bind_it(function, string, key, meta)
	char	key;
	char	*function,
		*string;
	int	meta;
#endif
{
	int	cnt,
		bi_index,
		i;
	int 	changed;

	if (meta < 1 || meta > MAX_META)
		meta = 0;

	if (*string == (char) 0)
		string = (char *) 0;

	switch (cnt = lookup_function(function, &bi_index))
	{
		case 0:
			say("No such function: %s", function);
			break;
		case 1:
		{
			changed = 1;
			new_key (meta, key, bi_index, changed, loading_global, string);
			show_binding(key, meta);
			break;
		}
		default:
			say("Ambiguous function name: %s", function);
			for (i = 0; i < cnt; i++, bi_index++)
				put_it("%s", key_names[bi_index].name);
			break;
	}
}

/* parsekeycmd: does the PARSEKEY command.  */
#ifdef __STDC__
void parsekeycmd (char *command, char *args, char *subargs)
#else
void parsekeycmd(command, args, subargs)
	char	*command,
		*args;
	char	*subargs;
#endif
{
	int	i;
	char	*arg;

	if ((arg = next_arg(args, &args)) != NULL)
	{
		switch (lookup_function(arg, &i))
		{
			case 0:
				say("No such function %s", arg);
				return;
			case 1:
				key_names[i].func(0, args);
				break;
			default:
				say("Ambigious function %s", arg);
				break;
		}
	}
}

/*
 * bindcmd: the bind command, takes a key sequence followed by a function
 * name followed by option arguments (used depending on the function) and
 * binds a key.  If no function is specified, the current binding for that
 * key is shown 
 */
#ifdef __STDC__
void bindcmd (char *command, char *args, char *subargs)
#else
void bindcmd(command, args, subargs)
	char	*command,
		*args;
	char	*subargs;
#endif
{
	char	*key,
		*function;
	int	meta;

	if ((key = next_arg(args, &args)) != NULL)
	{
		if ((meta = parse_key(key)) == -1)
			return;
		if (strlen(key) > 1)
		{
			say("Key sequences may not contain more than two keys");
			return;
		}
		if ((function = next_arg(args, &args)) != NULL)
			bind_it(function, args, *key, meta);
		else
			show_binding(*key, meta);
	}
	else
	{
		int i, j, k = charset_size();
		for (i = 0; i <= MAX_META; i++)
			for (j = 0; j <= k; j++)
				if (keys[i][j] && (keys[i][j]->key_index != NOTHING) && (keys[i][j]->key_index != SELF_INSERT))
					show_binding(j, i);
	}
}

/*
 * rbindcmd: does the rbind command.  you give it a string that something
 * is bound to and it tells you all the things that are bound to that
 * functions
 */
#ifdef __STDC__
void rbindcmd (char *command, char *args, char *subargs)
#else
void rbindcmd(command, args, subargs)
	char	*command,
		*args;
	char 	*subargs;
#endif
{
	int	f;
	char	*arg;

	if ((arg = next_arg(args, &args)) != NULL)
	{
		int	i, j;
		int	charsize = charset_size();

		switch (lookup_function(arg, &f))
		{
			case 0:
				say("No such function %s", arg);
				return;

			case 1:
				break;

			default:
				say("Ambigious function %s", arg);
				return;
		}

		for (i = 0; i <= MAX_META; i++)
			for (j = 0; j <= charsize; j++)
				if (keys[i][j] && keys[i][j]->key_index == f)
					show_binding(j, i);
	}
}

/*
 * type: The TYPE command.  This parses the given string and treats each
 * character as tho it were typed in by the user.  Thus key bindings are used
 * for each character parsed.  Special case characters are control character
 * sequences, specified by a ^ follow by a legal control key.  Thus doing
 * "/TYPE ^B" will be as tho ^B were hit at the keyboard, probably moving the
 * cursor backward one character.
 */
#ifdef __STDC__
void type (char *command, char *args, char *subargs)
#else
void type(command, args, subargs)
	char	*command,
		*args;
	char	*subargs;
#endif
{
	int	c;
	char	key;

	while (*args)
	{
		if (*args == '^')
		{
			switch (*(++args))
			{
			case '?':
				key = '\177';
				args++;
				break;
			default:
				c = *(args++);
				if (islower(c))
					c = toupper(c);
				if (c < 64)
				{
					say("Illegal key sequence: ^%c", c);
					return;
				}
				key = c - 64;
				break;
			}
		}
		else if (*args == '\\')
		{
			key = *++args;
			args++;
		}
		else
			key = *(args++);
		edit_char(key);
	}
}

KeyMapNames key_names[] =
{
	{ "BACKSPACE",			input_backspace },
	{ "BACKWARD_CHARACTER",		backward_character },
	{ "BACKWARD_HISTORY",		backward_history },
	{ "BACKWARD_WORD",		input_backward_word },
	{ "BEGINNING_OF_LINE",		input_beginning_of_line },
	{ "BOLD",			insert_bold },
	{ "CLEAR_SCREEN",		clear_screen },
	{ "COMMAND_COMPLETION",		command_completion },
	{ "DELETE_CHARACTER",		input_delete_character },
	{ "DELETE_NEXT_WORD",		input_delete_next_word },
	{ "DELETE_PREVIOUS_WORD",	input_delete_previous_word },
	{ "DELETE_TO_PREVIOUS_SPACE",	input_delete_to_previous_space },
	{ "END_OF_LINE",		input_end_of_line },
	{ "ENTER_DIGRAPH",		(KeyBinding)enter_digraph },
	{ "ENTER_MENU",			enter_menu },
	{ "ERASE_LINE",			input_clear_line },
	{ "ERASE_TO_BEG_OF_LINE",	input_clear_to_bol },
	{ "ERASE_TO_END_OF_LINE",	input_clear_to_eol },
	{ "FORWARD_CHARACTER",		forward_character },
	{ "FORWARD_HISTORY",		forward_history },
	{ "FORWARD_WORD",		input_forward_word },
	{ "HIGHLIGHT_OFF",		highlight_off },
	{ "META1_CHARACTER",		meta1_char },
	{ "META2_CHARACTER",		meta2_char },
	{ "META3_CHARACTER",		meta3_char },
	{ "META4_CHARACTER",		meta4_char },
	{ "META5_CHARACTER",		meta5_char },
	{ "META6_CHARACTER",		meta6_char },
	{ "META7_CHARACTER",		meta7_char },
	{ "META8_CHARACTER",		meta8_char },
	{ "META9_CHARACTER",		meta9_char },
	{ "NEXT_WINDOW",		next_window },
	{ "NOTHING",			NULL },
	{ "PARSE_COMMAND",		parse_text },
	{ "PREVIOUS_WINDOW",		previous_window },
	{ "QUIT_IRC",			irc_quit },
	{ "QUOTE_CHARACTER",		quote_char },
	{ "REFRESH_INPUTLINE",		refresh_inputline },
	{ "REFRESH_SCREEN",		(KeyBinding) refresh_screen },
	{ "REVERSE",			insert_reverse },
	{ "SCROLL_BACKWARD",		scrollback_backwards },
	{ "SCROLL_END",			scrollback_end },
	{ "SCROLL_FORWARD",		scrollback_forwards },
	{ "SCROLL_START",		scrollback_start },
	{ "SELF_INSERT",		input_add_character },
	{ "SEND_LINE",			get_line_return },
	{ "SHOVE_TO_HISTORY",		shove_to_history },
	{ "STOP_IRC",			term_pause },
	{ "SWAP_LAST_WINDOW",		swap_last_window },
	{ "SWAP_NEXT_WINDOW",		swap_next_window },
	{ "SWAP_PREVIOUS_WINDOW",	swap_previous_window },
	{ "SWITCH_CHANNELS",		switch_channels },
	{ "TOGGLE_INSERT_MODE",		toggle_insert_mode },
	{ "TOGGLE_STOP_SCREEN",		toggle_stop_screen },
	{ "TRANSPOSE_CHARACTERS",	input_transpose_characters },
	{ "TYPE_TEXT",			type_text },
	{ "UNDERLINE",			insert_underline },
	{ "UNSTOP_ALL_WINDOWS",		unstop_all_windows },
	{ "YANK_FROM_CUTBUFFER",	input_yank_cut_buffer }
};

/* real simple way to make a new keybinding */
#ifdef __STDC__
static void new_key (int map, int chr, int type, int change, int global, char *stuff)
#else
static void new_key (map, chr, type, change, global, stuff)
int map, chr, type, change, global;
char *stuff;
#endif
{
	if (keys[map][chr])
	{
		if (keys[map][chr]->stuff)
			new_free((char **)&keys[map][chr]->stuff);
		new_free((char **)&(keys[map][chr]));
		keys[map][chr] = NULL;
	}

	if (type != NOTHING)
	{
		keys[map][chr] = (KeyMap *)new_malloc(sizeof(KeyMap));
		keys[map][chr]->key_index = type;
		keys[map][chr]->changed = change;
		keys[map][chr]->global = global;
		if (stuff)
			keys[map][chr]->stuff = m_strdup(stuff);
		else
			keys[map][chr]->stuff = NULL;
	}
}

/* special interface to new_key for the default key bindings */
#ifdef __STDC__
static void snew_key (int map, int chr, int type)
#else
static void snew_key (map, chr, type)
int map, chr, type;
#endif
{
	new_key (map, chr, type, 0, 0, NULL);
}

/* This is where you put all the default key bindings.  This is a lot
 * simpler, just defining those you need, instead of all of them, isnt
 * it?  And it takes up so much less memory, too...
 */
static void init_keys_2 _((void))
{
	int i;

	/* all the "default" bindings are self_insert unless we bind
	 * them differently */
	for (i = 0; i <= 254; i++)
		snew_key(0, i, SELF_INSERT);

	/* "default" characters that arent self_insert */
	snew_key(0,  1, BEGINNING_OF_LINE);		/* ^A */
	snew_key(0,  2, BOLD);				/* ^B */
#if 0
	snew_key(0,  3, QUIT_IRC);			/* ^C */
#endif
	snew_key(0,  4, DELETE_CHARACTER);		/* ^D */
	snew_key(0,  5, END_OF_LINE);			/* ^E */
	snew_key(0,  6, FORWARD_CHARACTER);		/* ^F */
	snew_key(0,  8, BACKSPACE);			/* ^H (delete) */

	snew_key(0,  9, TOGGLE_INSERT_MODE);		/* ^I (tab) */
	snew_key(0, 10, SEND_LINE);			/* ^J (enter) */
	snew_key(0, 11, ERASE_TO_END_OF_LINE);		/* ^K */
	snew_key(0, 12, REFRESH_SCREEN);		/* ^L (linefeed) */
	snew_key(0, 13, SEND_LINE);			/* ^M (return) */
	snew_key(0, 14, FORWARD_HISTORY);		/* ^N */
	snew_key(0, 15, HIGHLIGHT_OFF);			/* ^O */
	snew_key(0, 16, BACKWARD_HISTORY);		/* ^P */

	snew_key(0, 17, QUOTE_CHARACTER);		/* ^Q */
	snew_key(0, 18, ENTER_MENU);			/* ^R */
	snew_key(0, 19, TOGGLE_STOP_SCREEN);		/* ^S */
	snew_key(0, 20, TRANSPOSE_CHARACTERS);		/* ^T */
	snew_key(0, 21, ERASE_LINE);			/* ^U */
	snew_key(0, 23, REVERSE);			/* ^V */
	snew_key(0, 24, META2_CHARACTER);		/* ^X */

	snew_key(0, 25, YANK_FROM_CUTBUFFER);		/* ^Y */
	snew_key(0, 26, ENTER_DIGRAPH);			/* ^Z */
	snew_key(0, 27, META1_CHARACTER);		/* ^[ (escape) */
	snew_key(0, 29, SHOVE_TO_HISTORY);		/* ^] */
	snew_key(0, 31, UNDERLINE);			/* ^_ */

	snew_key(0, 127, BACKSPACE);			/* ^? (delete) */

	/* european keyboards (and probably others) use the eigth bit
	   for extended characters.  Having these keys bound by default
	   causes them lots of grief, so unless you really want to use
	   these, they are commented out.
	 */
#ifdef EMACS_KEYBINDS
	snew_key(0, 188, SCROLL_START);	
	snew_key(0, 190, SCROLL_END);
	snew_key(0, 226, BACKWARD_WORD);
	snew_key(0, 228, DELETE_NEXT_WORD);
	snew_key(0, 229, SCROLL_END);
	snew_key(0, 230, FORWARD_WORD);
	snew_key(0, 232, DELETE_PREVIOUS_WORD);
	snew_key(0, 255, DELETE_PREVIOUS_WORD);
#endif

	/* meta 1 characters */
	snew_key(1,  27, COMMAND_COMPLETION);
	snew_key(1,  46, CLEAR_SCREEN);
	snew_key(1,  60, SCROLL_START);
	snew_key(1,  62, SCROLL_END);
	snew_key(1,  79, META2_CHARACTER);
	snew_key(1,  91, META2_CHARACTER);
	snew_key(1,  98, BACKWARD_WORD);
	snew_key(1, 100, DELETE_NEXT_WORD);
	snew_key(1, 101, SCROLL_END);
	snew_key(1, 102, FORWARD_WORD);
	snew_key(1, 104, DELETE_PREVIOUS_WORD);
	snew_key(1, 110, SCROLL_FORWARD);
	snew_key(1, 112, SCROLL_BACKWARD);
	snew_key(1, 127, DELETE_PREVIOUS_WORD);


	/* meta 2 characters */
#ifdef ALLOW_STOP_IRC
	snew_key(2,  26, STOP_IRC);
#endif
	snew_key(2,  65, BACKWARD_HISTORY);
	snew_key(2,  66, FORWARD_HISTORY);
	snew_key(2,  67, FORWARD_CHARACTER);
	snew_key(2,  68, BACKWARD_CHARACTER);
	snew_key(2, 110, NEXT_WINDOW);
	snew_key(2, 112, PREVIOUS_WINDOW);

	
	/* meta 3 characters */
	/* <none> */

	/* meta 4 characters -- vi key mappings */
	snew_key(4,   8, BACKWARD_CHARACTER);
	snew_key(4,  32, FORWARD_CHARACTER);
	snew_key(4,  65, META4_CHARACTER);
	snew_key(4,  72, BACKWARD_CHARACTER);
	snew_key(4,  73, META4_CHARACTER);
	snew_key(4,  74, FORWARD_HISTORY);
	snew_key(4,  75, BACKWARD_HISTORY);
	snew_key(4,  76, FORWARD_CHARACTER);
	snew_key(4,  88, DELETE_CHARACTER);
	snew_key(4,  97, META4_CHARACTER);
	snew_key(4, 104, BACKWARD_CHARACTER);
	snew_key(4, 105, META4_CHARACTER);
	snew_key(4, 106, FORWARD_HISTORY);
	snew_key(4, 107, BACKWARD_HISTORY);
	snew_key(4, 108, FORWARD_CHARACTER);
	snew_key(4, 120, DELETE_CHARACTER);
}


/*
 * write_binding: This will write to the given FILE pointer the information
 * about the specified key binding.  The format it writes it out is such that
 * it can be parsed back in later using LOAD or with the -l switch 
 */
#ifdef __STDC__
static void write_binding (unsigned char c, unsigned char meta, FILE *fp, int do_all)
#else
static	void
write_binding(c, meta, fp, do_all)
	unsigned char	c,
		meta;
	FILE	*fp;
	int	do_all;
#endif
{
	char	meta_str[8];

	if (c == 32)
		return;

	*meta_str = 0;
	if (meta < 1 || meta > MAX_META)
		meta = 0;
	else
		sprintf(meta_str, "META%d-", meta);

	if (keys[meta][c] && keys[meta][c]->changed)
	{
		fprintf(fp, "BIND %s%s %s", meta_str, display_key(c), key_names[keys[meta][c]->key_index].name);
		if (keys[meta][c]->stuff && (*(keys[meta][c]->stuff)))
			fprintf(fp, " %s", keys[meta][c]->stuff);
		fprintf(fp, "\n");
	}
}

/*
 * save_bindings: this writes all the keys bindings for IRCII to the given
 * FILE pointer using the write_binding function 
 */
#ifdef __STDC__
void save_bindings (FILE *fp, int do_all)
#else
void
save_bindings(fp, do_all)
	FILE	*fp;
	int	do_all;
#endif
{
	int	i, j;
	int	charsize = charset_size();

	for (i = 0; i <= MAX_META; i++)
		for (j = 0; j < charsize; j++)
			write_binding(j, i, fp, do_all);
}
