/*
 * key.c		
 *
 * Anthony's Editor February 96
 *
 * Copyright 1993, 1996 by Anthony Howe.  All rights reserved.  No warranty.
 */

#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include "header.h"
#include "key.h"

/* Variable length structure. */
typedef struct t_input {
	struct t_input *next;
	char *ptr;
	char buf[1];
} t_input;

static t_input *istack = NULL;
static char blank[] = " \t\r\n";

static int k_default _((t_keymap *));
static int k_define _((t_keymap *));
static int k_erase  _((t_keymap *));
static int k_itself _((t_keymap *));
static int k_kill _((t_keymap *));
static int k_token _((t_keymap *));
static t_keymap *growkey _((t_keymap *, size_t));
static int ipush _((char *));
static int ipop _((void));
static void iflush _((void));

t_keyinit keywords[] = {
	{ K_INSERT_ENTER, ".insert_enter", k_default },
	{ K_INSERT_EXIT, ".insert_exit", k_default },
	{ K_DELETE_LEFT, ".delete_left", k_default },
	{ K_DELETE_RIGHT, ".delete_right", k_default },
	{ K_FLIP_CASE, ".flip_case", k_default },
	{ K_BLOCK, ".block", k_default },
	{ K_CUT, ".cut", k_default },
	{ K_PASTE, ".paste", k_default },
	{ K_UNDO, ".undo", k_default },
	{ K_CURSOR_UP, ".cursor_up", k_default },
	{ K_CURSOR_DOWN, ".cursor_down", k_default },
	{ K_CURSOR_LEFT, ".cursor_left", k_default },
	{ K_CURSOR_RIGHT, ".cursor_right", k_default },
	{ K_ECCE, ".ecce", k_default },
	{ K_PAGE_UP, ".page_up", k_default },
	{ K_PAGE_DOWN, ".page_down", k_default },
	{ K_WORD_LEFT, ".word_left", k_default },
	{ K_WORD_RIGHT, ".word_right", k_default },
	{ K_LINE_LEFT, ".line_left", k_default },
	{ K_LINE_RIGHT, ".line_right", k_default },
	{ K_FILE_TOP, ".file_top", k_default },
	{ K_FILE_BOTTOM, ".file_bottom", k_default },
	{ K_HELP, ".help", k_default },
	{ K_HELP_OFF, ".help_off", k_token },
	{ K_MACRO, ".macro", k_default },
	{ K_MACRO_DEFINE, ".macro_define", k_define },
	{ K_QUIT, ".quit", k_default },
	{ K_QUIT_ASK, ".quit_ask", k_default },
	{ K_FILE_READ, ".file_read", k_default },
	{ K_FILE_WRITE, ".file_write", k_default },
	{ K_STTY_ERASE, ".stty_erase", k_erase },
	{ K_STTY_KILL, ".stty_kill", k_kill },
	{ K_ITSELF, ".itself", k_itself },
	{ K_REDRAW, ".redraw", k_default },
	{ K_SHOW_VERSION, ".show_version", k_default },
	{ K_LITERAL, ".literal", k_default },
	{ K_ERROR, NULL, NULL }
};

int
ismsg(str)
char *str;
{
	char *ptr;
	for (ptr = str; isdigit(*ptr); ++ptr)
		;
	return (str < ptr && *ptr == ':');
}

/*
 * Read a configuration file from either the current directory or
 * the user's home directory.  Return a pointer to a key mapping table. 
 */
t_keymap *
initkey(fn)
char *fn;
{
	FILE *fp;
	int code;
	t_keyinit *kp;
	t_keymap *array;
	size_t len, count, line;
	char *buf, *token, *lhs, *rhs, *error;

	if ((fp = openrc(fn)) == NULL)
		return (t_keymap *) 0;

	/* Allocate an array big enough to hold at least one of everything. */
	if ((array = growkey(NULL, len = K_MAX_CODES)) == NULL) {
		error = f_alloc;
		goto error1;
	}

	count = line = 0;
	error = (char *) 0;
	for ( ; (code = getblock(fp, &buf)) != GETBLOCK_EOF; free(buf)) {
		if (code == GETBLOCK_ALLOC) {
			error = f_alloc;
			goto error1;
		}

		++line;
	
		/* Strip \r\n from end of buffer. */
		if ((token = strrchr(buf, '\n')) != NULL) {
			if (buf < token && token[-1] == '\r')
				--token;
			*token = '\0';
		}

		if (ismsg(buf)) {
			long index;

			if ((token = encode(buf)) == (char *) 0) {
				error = f_alloc;
				goto error1;
			}

			index = strtol(token, &token, 0);
			if (0 < index) 
				message[index] = token+1;
			continue;
		}
			
		if (buf[0] != '.' 
		|| (token = strtok(buf, blank)) == NULL
		|| (kp = findikey(keywords, strlwr(token))) == NULL)
			continue;
	
		array[count].code = kp->code;
		array[count].lhs = array[count].rhs = (char *) 0;

		/* Determine lhs and rhs parameters. */
		if ((lhs = strtok((char *) 0, blank)) != (char *) 0) {
			rhs = strtok((char *) 0, blank);

			array[count].lhs = encode(lhs);
			if (array[count].lhs == (char *) 0) {
				error = f_config;
				goto error1;
			}

			/* Find rhs if present. */
			if (rhs != (char *) 0
			&& (array[count].rhs = encode(token)) == (char *) 0) {
				error = f_config;
				goto error1;
			}
		}

		if (kp->fn != NULL && !(*kp->fn)(&array[count])) {
			error = f_config;
			goto error1;
		}
		++count;

		if (len <= count) {
			t_keymap *new;
			len += K_MAX_CODES;
			if ((new = growkey(array, len)) == NULL) {
				error = f_alloc;
				goto error1;
			}
			array = new;
		}
	}

error1:
	(void) fclose(fp);
	array[count].code = K_ERROR;

	if (error != (char *) 0) {
		finikey(array);
		fatal(error, line);
	}

	return array;
}

void
finikey(keys)
t_keymap *keys;
{
	t_keymap *kp;
	if (keys != NULL) {
		for (kp = keys; kp->code != K_ERROR; ++kp) {
			if (kp->lhs != (char *) 0)
				free(kp->lhs);
			if (kp->rhs != (char *) 0)
				free(kp->rhs);
		}
		free(keys);
	}
}

/*
 * .function string
 */
static int
k_default(kp)
t_keymap *kp;
{
	if (kp->lhs == NULL)
		return (FALSE);
	if (kp->rhs != NULL) {
		free(kp->lhs);
		return (FALSE);
	}
	return (TRUE);
}

/*
 * .macro_define
 * .macro_define lhs rhs
 *
 * The first case is used as a place holder to reserve macro
 * space.  The second case actual defines a macro.
 */
static int
k_define(kp)
t_keymap *kp;
{
	if (kp->lhs != NULL && kp->rhs == NULL) {
		free(kp->lhs);
		return (FALSE);
	}
	return (TRUE);
}

/*
 * .token
 */
static int
k_token(kp)
t_keymap *kp;
{
	if (kp->lhs != NULL) {
		free(kp->lhs);
		return (FALSE);
	}
	return (TRUE);
}

/*
 * .itself character
 */
static int
k_itself(kp)
t_keymap *kp;
{
	if (!k_default(kp))
		return (FALSE);
	kp->code = *(unsigned char *) kp->lhs;
	kp->lhs[1] = '\0';
	return (TRUE);
}

/*
 * .stty_erase
 */
static int
k_erase(kp)
t_keymap *kp;
{
	char buf[2];

	if (!k_token(kp))
		return (FALSE);
	buf[0] = erasechar();
	buf[1] = '\0';
	return ((kp->lhs = strdup(buf)) != NULL);
}

/*
 * .stty_kill
 */
static int
k_kill(kp)
t_keymap *kp;
{
	char buf[2];

	if (!k_token(kp))
		return (FALSE);
	buf[0] = killchar();
	buf[1] = '\0';
	return ((kp->lhs = strdup(buf)) != NULL);
}

/*
 * Find token and return corresponding table entry; else NULL.
 */
t_keymap *
findkey(kp, token)
t_keymap *kp;
char *token;
{
	for (; kp->code != K_ERROR; ++kp)
		if (kp->lhs != NULL && strcmp(token, kp->lhs) == 0)
			return (kp);
	return (NULL);
}

t_keyinit *
findikey(kp, token)
t_keyinit *kp;
char *token;
{
	for (; kp->code != K_ERROR; ++kp)
		if (kp->lhs != NULL && strcmp(token, kp->lhs) == 0)
			return (kp);
	return (NULL);
}

/*
 *
 */
static t_keymap *
growkey(array, len)
t_keymap *array;
size_t len;
{
	t_keymap *new;
	if (len == 0)
		return (NULL);
	len *= sizeof (t_keymap);
	if (array == NULL)
		return ((t_keymap *) malloc(len));
	return ((t_keymap *) realloc(array, len));
}

int
getkey(keys)
t_keymap *keys;
{
	t_keymap *k;
	int submatch;
	static char buffer[K_BUFFER_LENGTH];
	static char *record = buffer;

	/* If recorded bytes remain, return next recorded byte. */
	if (*record != '\0')
		return (*(unsigned char *)record++);
	/* Reset record buffer. */
	record = buffer;
	do {
		if (K_BUFFER_LENGTH < record - buffer) {
			record = buffer;
			buffer[0] = '\0';
			return (K_ERROR); 
		}
		/* Read and record one byte. */
		*record++ = getliteral();
		*record = '\0';

		/* If recorded bytes match any multi-byte sequence... */
		for (k = keys, submatch = FALSE; k->code != K_ERROR; ++k) {
			if (k->lhs == NULL || k->code == K_DISABLED)
				continue;
			if (strncmp(buffer, k->lhs, record-buffer) != 0)
				continue;
			if (k->lhs[record-buffer] == '\0') {
				/* Exact match. */
				if (k->code != K_MACRO_DEFINE) {
					/* Return extended key code. */
					return (k->code);
				}
				if (k->rhs != NULL) {
					(void) ipush(k->rhs);
					record = buffer;
				}
			}
			/* Recorded bytes match anchored substring. */
			submatch = TRUE;
			break;
		}
		/* If recorded bytes matched an anchored substring, loop. */
	} while (submatch);
	/* Return first recorded byte. */
	record = buffer;
	return (*(unsigned char *)record++);
}

int
getliteral()
{
	int ch;

	ch = ipop();
	if (ch == EOF)
		return ((unsigned) getch());
	return (ch);
}

/*
 * Return true if a new input string was pushed onto the stack,
 * else false if there was no more memory for the stack.
 */
static int
ipush(buf)
char *buf;
{
	t_input *new;

	new = (t_input *) malloc(sizeof (t_input) + strlen (buf));
	if (new == NULL)
		return (FALSE);
	(void) strcpy(new->buf, buf);
	new->ptr = new->buf;
	new->next = istack;
	istack = new;
	return (TRUE);
}

/*
 * Pop and return a character off the input stack.  Return EOF if
 * the stack is empty.  If the end of an input string is reached, 
 * then free the node.  This will allow clean tail recursion that 
 * won't blow the stack.  
 */
static int
ipop()
{
	int ch;
	t_input *node;

	if (istack == NULL)
		return (EOF);
	ch = (unsigned) *istack->ptr++;
	if (*istack->ptr == '\0') {
		node = istack;
		istack = istack->next;
		free(node);
	}
	return (ch);
}

/*
 * Flush the entire input stack.
 */
static void
iflush()
{
	t_input *node;

	while (istack != NULL) {
		node = istack;
		istack = istack->next;
		free(node);
	}
}

int
ismacro()
{
	return (istack != NULL);
}

/*
 * Field input.
 */
typedef struct t_keyfield {
	int code;
	int (*func) _((void));
} t_keyfield;

static int fld_done _((void));
static int fld_erase _((void));
static int fld_kill _((void));
static int fld_left _((void));
static int fld_insert _((void));

#define ERASE_KEY	0
#define KILL_KEY	1

static t_keyfield ktable[] = {
	{ K_STTY_ERASE, fld_erase },
	{ K_STTY_KILL, fld_kill },
	{ '\r', fld_done },
	{ '\n', fld_done },
	{ '\b', fld_erase },
	{ -1, fld_insert }
};

static int fld_row;
static int fld_col;
static int fld_key;
static int fld_echo;
static int fld_index;
static int fld_length;
static char *fld_buffer;

#ifndef getmaxyx
#define getmaxyx(w,r,c)		(r=LINES,c=COLS)
#endif

int
getinput(buf, len, echoing)
char *buf;
int len, echoing;
{
	int first;
	t_keyfield *k;
	fld_buffer = buf;
	fld_index = (int) strlen(fld_buffer);
	fld_length = len < 0 ? COLS : len;
	if (--fld_length < 1)
		return (FALSE);
	ktable[ERASE_KEY].code = erasechar();
	ktable[KILL_KEY].code = killchar();	
	fld_echo = echoing;
	getyx(stdscr, fld_row, fld_col);
	addstr(fld_buffer);
	move(fld_row, fld_col);
	for (first = TRUE;; first = FALSE) {
		refresh();
		fld_key = getliteral();
		for (k = ktable; k->code != -1 && k->code != fld_key; ++k)
			;
		if (first && k->func == fld_insert)
			fld_kill();
		if (k->func != NULL && !(*k->func)()) {
			fld_buffer[fld_index] = '\0';
			break;
		}
	}
	return (TRUE);
}
	
static int
fld_done()
{
	return (FALSE);
}

static int
fld_left()
{
	int row, col, max_row, max_col;
	getyx(stdscr, row, col);
	getmaxyx(stdscr, max_row, max_col);
	if (0 < fld_index) {
		--fld_index;
		/* Assume that if 0 < fld_index then fld_row <= row 
		 * and fld_col < col.  So when fld_index == 0, then
		 * fld_row == row and fld_col == col. 
		 */
		if (0 < col) {
			--col;
		} else if (0 < row) {
			/* Handle reverse line wrap. */
			--row;
			col = max_col-1;
		}
		move(row, col);
	}
	return (TRUE);
}

static int
fld_erase()
{
	int row, col;
	if (0 < fld_index) {
		fld_left();
		getyx(stdscr, row, col);
		addch(' ');
		move(row, col);
		fld_buffer[fld_index] = '\0';
	}
	return (TRUE);
}

static int
fld_kill()
{
	move(fld_row, fld_col);
	while (0 < fld_index--)
		addch(' ');
	move(fld_row, fld_col);
	fld_buffer[0] = '\0';
	fld_index = 0;
	return (TRUE);
}
	
static int
fld_insert()
{
	if (fld_index < fld_length) {
		if (!ISFUNCKEY(fld_key)) {
			fld_buffer[fld_index++] = fld_key;
			if (fld_echo)
				addch(fld_key);
		}
	}
	return (fld_index < fld_length);
}


