/* Terminfo database scanning and keyboard escape sequence matching functions.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 1, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

#include "ne.h"
#include <time.h>
#include <signal.h>


/* Maximum number of key definitions from terminfo */

#define MAX_TERM_KEY 100

/* Size of the keyboard input buffer. */

#define KBD_BUF_SIZE 512



/* This structure describes a key in the terminfo database. These structures
are ordered with respect to the string field, in order to greatly optimize
their scanning. */


typedef struct {
	unsigned char *string;
	int code;
} term_key;



static term_key key[MAX_TERM_KEY];
static int num_keys = 0;

/* Function to pass to qsort */

static int keycmp(const void *t1, const void *t2) {

	return(-strcmp(((term_key *)t1)->string, ((term_key *)t2)->string));

}

static void key_set(char *cap_string, int code) {

	if (!cap_string) return;

	key[num_keys].string = cap_string;
	key[num_keys].code = code;
	num_keys++;

}

/* Here we scan the terminfo database and build a term_key structure for
each key available. num_keys records the number of entries. The array is
sorted in reverse order with respect to string field (this optimizes the
comparisons, assuming that usually almost all control sequences start with a
character smaller than ' ', while the characters typed by the user are
almost always greater than or equal to ' '). */


void read_key_capabilities(void) {


	/* Cursor movement keys */

	key_set(key_up, NE_KEY_UP);
	key_set(key_down, NE_KEY_DOWN);
	key_set(key_left, NE_KEY_LEFT);
	key_set(key_right, NE_KEY_RIGHT);
	key_set(key_home, NE_KEY_HOME);
	key_set(key_ll, NE_KEY_HOME_DOWN);
	key_set(key_npage, NE_KEY_NPAGE);
	key_set(key_ppage, NE_KEY_PPAGE);
	key_set(key_sf, NE_KEY_SCROLL_FORWARD);
	key_set(key_sr, NE_KEY_SCROLL_REVERSE);


	/* Editing keys */

	key_set(key_eol, NE_KEY_CLEAR_TO_EOL);
	key_set(key_eos, NE_KEY_CLEAR_TO_EOS);
	key_set(key_backspace, NE_KEY_BACKSPACE);
	key_set(key_dl, NE_KEY_DELETE_LINE);
	key_set(key_il, NE_KEY_INSERT_LINE);
	key_set(key_dc, NE_KEY_DELETE_CHAR);
	key_set(key_ic, NE_KEY_INSERT_CHAR);
	key_set(key_eic, NE_KEY_EXIT_INSERT_CHAR);
	key_set(key_clear, NE_KEY_CLEAR);


	/* Keypad keys */

	key_set(key_a1, NE_KEY_A1);
	key_set(key_a3, NE_KEY_A3);
	key_set(key_b2, NE_KEY_B2);
	key_set(key_c1, NE_KEY_C1);
	key_set(key_c3, NE_KEY_C3);


	/* Tab keys (never used in the standard configuration) */

	key_set(key_catab, NE_KEY_CLEAR_ALL_TABS);
	key_set(key_ctab, NE_KEY_CLEAR_TAB);
	key_set(key_stab, NE_KEY_SET_TAB);


	/* Function keys */

	key_set(key_f0, NE_KEY_F(0));
	key_set(key_f1, NE_KEY_F(1));
	key_set(key_f2, NE_KEY_F(2));
	key_set(key_f3, NE_KEY_F(3));
	key_set(key_f4, NE_KEY_F(4));
	key_set(key_f5, NE_KEY_F(5));
	key_set(key_f6, NE_KEY_F(6));
	key_set(key_f7, NE_KEY_F(7));
	key_set(key_f8, NE_KEY_F(8));
	key_set(key_f9, NE_KEY_F(9));
	key_set(key_f10, NE_KEY_F(10));
	key_set(key_f11, NE_KEY_F(11));
	key_set(key_f12, NE_KEY_F(12));
	key_set(key_f13, NE_KEY_F(13));
	key_set(key_f14, NE_KEY_F(14));
	key_set(key_f15, NE_KEY_F(15));
	key_set(key_f16, NE_KEY_F(16));
	key_set(key_f17, NE_KEY_F(17));
	key_set(key_f18, NE_KEY_F(18));
	key_set(key_f19, NE_KEY_F(19));
	key_set(key_f20, NE_KEY_F(20));
	key_set(key_f21, NE_KEY_F(21));
	key_set(key_f22, NE_KEY_F(22));
	key_set(key_f23, NE_KEY_F(23));
	key_set(key_f24, NE_KEY_F(24));
	key_set(key_f25, NE_KEY_F(25));
	key_set(key_f26, NE_KEY_F(26));
	key_set(key_f27, NE_KEY_F(27));
	key_set(key_f28, NE_KEY_F(28));
	key_set(key_f29, NE_KEY_F(29));
	key_set(key_f30, NE_KEY_F(30));
	key_set(key_f31, NE_KEY_F(31));
	key_set(key_f32, NE_KEY_F(32));
	key_set(key_f33, NE_KEY_F(33));
	key_set(key_f34, NE_KEY_F(34));
	key_set(key_f35, NE_KEY_F(35));
	key_set(key_f36, NE_KEY_F(36));
	key_set(key_f37, NE_KEY_F(37));
	key_set(key_f38, NE_KEY_F(38));
	key_set(key_f39, NE_KEY_F(39));
	key_set(key_f40, NE_KEY_F(40));
	key_set(key_f41, NE_KEY_F(41));
	key_set(key_f42, NE_KEY_F(42));
	key_set(key_f43, NE_KEY_F(43));
	key_set(key_f44, NE_KEY_F(44));
	key_set(key_f45, NE_KEY_F(45));
	key_set(key_f46, NE_KEY_F(46));
	key_set(key_f47, NE_KEY_F(47));
	key_set(key_f48, NE_KEY_F(48));
	key_set(key_f49, NE_KEY_F(49));
	key_set(key_f50, NE_KEY_F(50));
	key_set(key_f51, NE_KEY_F(51));
	key_set(key_f52, NE_KEY_F(52));
	key_set(key_f53, NE_KEY_F(53));
	key_set(key_f54, NE_KEY_F(54));
	key_set(key_f55, NE_KEY_F(55));
	key_set(key_f56, NE_KEY_F(56));
	key_set(key_f57, NE_KEY_F(57));
	key_set(key_f58, NE_KEY_F(58));
	key_set(key_f59, NE_KEY_F(59));
	key_set(key_f60, NE_KEY_F(60));
	key_set(key_f61, NE_KEY_F(61));
	key_set(key_f62, NE_KEY_F(62));
	key_set(key_f63, NE_KEY_F(63));

	/* Fake (simulated) menu key. */

	key_set("\x1B:", NE_KEY_MENU);

	assert(num_keys<MAX_TERM_KEY-1);

	D(printf("Got %d keys from terminfo\n\r", num_keys);)

	qsort(key, num_keys, sizeof(term_key), keycmp);

}

/* This code is the alarm signal handler. It just sets the signal handler to
itself, because our only use of the alarm clock is to get out of a getchar()
in case an ambiguity arises when parsing escape sequences from the terminfo
database. */

void set_alarm_code(int sig) {
	signal(SIGALRM, set_alarm_code);
}


/* This function reads in characters, and tries to match them with the
sequences corresponding to special keys. It tries to by highly optimized and
efficient by employing a sorted array of strings for the terminal keys. An
index keeps track of the key which has a partial match with the current
contents of the keyboard buffer. As each character is input, a match is
tried with the rest of the string. If a new character does not match, we can
just increment the key counter (because the array is sorted). When we get
out of the array, we give back the first char in the keyboard buffer (the
next call will retry a match on the following chars). */


int get_key_code(void) {

	static int cur_len = 0;
	static unsigned char kbd_buffer[KBD_BUF_SIZE];

	register int c, last_match = 0, cur_key = 0, alarm_set = FALSE;

	while(TRUE) {

		if (cur_len) {

			/* Something is already in the buffer. last_match is the position
			we have to check. */

			while(last_match < cur_len) {

				/* First easy case. We felt off the array. We return the first character
				in the buffer and restart the match. */

				if (!key[cur_key].string) {
					c = kbd_buffer[0];
					if (--cur_len) memmove(kbd_buffer, kbd_buffer+1, cur_len);
					return(c);
				}

				/* Second case. We have a partial match on the first last_match
				characters. If another character matches, either the string is terminated,
				and we return the key code, or we increment the match count. */


				else if (key[cur_key].string[last_match] == kbd_buffer[last_match]) {
					if (key[cur_key].string[last_match+1] == 0) {
						if (cur_len -= last_match+1) memmove(kbd_buffer, kbd_buffer+last_match+1, cur_len);

						assert(key[cur_key].code < NUM_KEYS);

						return(key[cur_key].code);
					}
					else last_match++;
				}

				/* The tricky part. If there is a failed match, we first have to check that
				we have more than one character. If this is not the case, the order guarantees that
				no match if possible if the code of the char is greater than the code of
				the first string char. If there is more than one char in the buffer, a sophisticated
				backtrack system could be used; instead, we simply reset last_match to 0
				and we advance the key counter. */

				else if (key[cur_key].string[last_match] != kbd_buffer[last_match]) {
					if (key[cur_key].string[last_match] < kbd_buffer[last_match] && last_match == 0) {

						c = kbd_buffer[0];
						if (--cur_len) memmove(kbd_buffer, kbd_buffer+1, cur_len);
						return(c);
					}
					else {
						last_match = 0;
						cur_key++;
					}
				}
			}

			/* If we have a partial match, let's look at stdin for one second. If nothing
			arrives, it is probably time to return what we got. */

			/* PORTABILITY PROBLEM: it seems like alarm() is spec'ed by POSIX as having a +0, -1
			variance on the argument passed; this means that on some systems it could
			be necessary to alarm(2), because alarm(1) just signals immediately. On
			the other systems, however, alarm(2) would be intolerably slow. */

			alarm(1);
			alarm_set = TRUE;
		}

		c = getchar();

		if (alarm_set) {
			alarm(0);
			alarm_set = FALSE;
		}

		if (c != EOF && cur_len < KBD_BUF_SIZE) kbd_buffer[cur_len++] = c;
		else if (cur_len) {

			/* We ran out of time. The current partial match is discarded,
			and the first character of the keyboard buffer is returned. */

			c = kbd_buffer[0];
			if (--cur_len) memmove(kbd_buffer, kbd_buffer+1, cur_len);
			return(c);
		}
	}
}
