/*	input.c
 *
 *	Various input routines
 *
 *	written by Daniel Lawrence 5/9/86
 *	modified by Petri Kutvonen
 */

#include <stdio.h>
#include <unistd.h>

#include "estruct.h"
#include "globals.h"
#include "efunc.h"
#include "wrapper.h"

/*
 * Ask a yes or no question in the message line. Return either TRUE, FALSE, or
 * ABORT. The ABORT status is returned if the user bumps out of the question
 * with a ^G. Used any time a confirmation is required.
 */
int ask_yesno(char *prompt)
{
	char c;					/* input character */
	char buf[NPAT];				/* prompt to user */

	for (;;) {
		/* build and prompt the user */
		strcpy(buf, prompt);
		strcat(buf, " (y/n)? ");
		msg_puts(buf);

		/* get the responce */
		c = tgetc();

		if (c == keycode_to_char(abort_char))		/* Bail out! */
			return ABORT;

		if (c == 'y' || c == 'Y')
			return TRUE;

		if (c == 'n' || c == 'N')
			return FALSE;
	}
}

/*
 * Write a prompt into the message line, then read back a response. Keep
 * track of the physical position of the cursor. If we are in a keyboard
 * macro throw the prompt away, and return the remembered response. This
 * lets macros run at full speed. The reply is always terminated by a carriage
 * return. Handle erase, kill, and abort keys.
 */

int ask_string(char *prompt, char *buf, int nbuf)
{
	return nextarg(prompt, buf, nbuf, char_to_keycode('\n'));
}

int ask_string_until(char *prompt, char *buf, int nbuf, int eolchar)
{
	return nextarg(prompt, buf, nbuf, eolchar);
}

/*
 * keycode_to_char:
 *	expanded character to character
 *	collapse the CONTROL and SPEC flags back into an ascii code
 */
int keycode_to_char(int c)
{
	if (c & CONTROL)
		c = c & ~(CONTROL | 0x40);
	if (c & SPEC)
		c = c & 255;
	return c;
}

/*
 * char_to_keycode:
 *	character to extended character
 *	pull out the CONTROL and SPEC prefixes (if possible)
 */
int char_to_keycode(int c)
{
	if (c >= 0x00 && c <= 0x1F)
		c = CONTROL | (c + '@');
	return c;
}

/*
 * get a command name from the command line. Command completion means
 * that pressing a <SPACE> will attempt to complete an unfinished command
 * name if it is unique.
 */
fn_t getname(void)
{
	int cpos;				/* current column on screen output */
	int c;
	char *sp;				/* pointer to string for output */
	struct name_bind *ffp;			/* first ptr to entry in name binding table */
	struct name_bind *cffp;			/* current ptr to entry in name binding table */
	struct name_bind *lffp;			/* last ptr to entry in name binding table */
	char buf[NSTRING];			/* buffer to hold tentative command name */

	/* starting at the beginning of the string buffer */
	cpos = 0;

	/* if we are executing a command line get the next arg and match it */
	if (executing_command_line) {
		if (macarg(buf) != TRUE)
			return NULL;
		return fncmatch(&buf[0]);
	}

	/* build a name string from the keyboard */
	while (TRUE) {
		c = tgetc();

		/* if we are at the end, just match it */
		if (c == 0x0d) {
			buf[cpos] = 0;

			/* and match it off */
			return fncmatch(&buf[0]);

		} else if (c == keycode_to_char(abort_char)) {	/* Bell, abort */
			cmd_abort_command(FALSE, 0);
			ttflush();
			return NULL;

		} else if (c == 0x7F || c == 0x08) {	/* rubout/erase */
			if (cpos != 0) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--shown_col;
				--cpos;
				ttflush();
			}

		} else if (c == 0x15) {		/* C-U, kill */
			while (cpos != 0) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--cpos;
				--shown_col;
			}

			ttflush();

		} else if (c == ' ' || c == 0x1b || c == 0x09) {
/* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
			/* attempt a completion */
			buf[cpos] = 0;		/* terminate it for us */
			ffp = &names[0];	/* scan for matches */
			while (ffp->n_func != NULL) {
				if (strncmp(buf, ffp->n_name, strlen(buf))
				    == 0) {
					/* a possible match! More than one? */
					if ((ffp + 1)->n_func == NULL ||
					    (strncmp(buf, (ffp + 1)->n_name, strlen(buf)) != 0)) {
						/* no...we match, print it */
						sp = ffp->n_name + cpos;
						while (*sp)
							ttputc(*sp++);
						ttflush();
						return ffp->n_func;
					} else {
/* << << << << << << << << << << << << << << << << << */
						/* try for a partial match against the list */

						/* first scan down until we no longer match the current input */
						lffp = (ffp + 1);
						while ((lffp + 1)->n_func != NULL) {
							if (strncmp
							    (buf, (lffp + 1)->n_name, strlen(buf))
							    != 0)
								break;
							++lffp;
						}

						/* and now, attempt to partial complete the string, char at a time */
						while (TRUE) {
							/* add the next char in */
							buf[cpos] = ffp->n_name[cpos];

							/* scan through the candidates */
							cffp = ffp + 1;
							while (cffp <= lffp) {
								if (cffp->n_name[cpos]
								    != buf[cpos])
									goto onward;
								++cffp;
							}

							/* add the character */
							ttputc(buf[cpos++]);
						}
/* << << << << << << << << << << << << << << << << << */
					}
				}
				++ffp;
			}

			/* no match.....beep and onward */
			tcapbeep();
 onward:		;
			ttflush();
/* <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
		} else {
			if (cpos < NSTRING - 1 && c > ' ') {
				buf[cpos++] = c;
				ttputc(c);
			}

			++shown_col;
			ttflush();
		}
	}
}

/*	tgetc:	Get a key from the terminal driver, resolve any keyboard
		macro action					*/

int tgetc(void)
{
	int c;					/* fetched character */

	/* if we are playing a keyboard macro back, */
	if (keyboard_macro_mode == PLAY) {

		/* if there is some left... */
		if (keyboard_macro_pos < keyboard_macro_end)
			return (int)*keyboard_macro_pos++;

		/* at the end of last repitition? */
		if (--keyboard_macro_repeat < 1) {
			keyboard_macro_mode = STOP;
			/* force a screen update after all is done */
			update();
		} else {

			/* reset the macro to the begining for the next rep */
			keyboard_macro_pos = &keyboard_macro[0];
			return (int)*keyboard_macro_pos++;
		}
	}

	/* fetch a character from the terminal driver */
	c = ttgetc();

	/* record it for $lastkey */
	last_key = c;

	/* save it if we need to */
	if (keyboard_macro_mode == RECORD) {
		*keyboard_macro_pos++ = c;
		keyboard_macro_end = keyboard_macro_pos;

		/* don't overrun the buffer */
		if (keyboard_macro_pos == &keyboard_macro[NKBDM - 1]) {
			keyboard_macro_mode = STOP;
			tcapbeep();
		}
	}

	/* and finally give the char back */
	return c;
}

/*	GET1KEY:	Get one keystroke. The only prefixs legal here
			are the SPEC and CONTROL prefixes.
								*/

int get1key(void)
{
	int c;

	/* get a keystroke */
	c = tgetc();

	if (c >= 0x00 && c <= 0x1F)		/* C0 control -> C-     */
		c = CONTROL | (c + '@');
	return c;
}

/*	GETCMD:	Get a command from the keyboard. Process all applicable
		prefix keys
							*/
int getcmd(void)
{
	int c;					/* fetched keystroke */
	int d;					/* second character P.K. */
	int cmask = 0;
	/* get initial character */
	c = get1key();

 proc_metac:
	if (c == 128 + 27)			/* CSI */
		goto handle_CSI;
	/* process META prefix */
	if (c == (CONTROL | '[')) {
		c = get1key();
		if (c == '[' || c == 'O') {	/* CSI P.K. */
 handle_CSI:
			c = get1key();
			if (c >= 'A' && c <= 'D')
				return SPEC | c | cmask;
			if (c >= 'E' && c <= 'z' && c != 'i' && c != 'c')
				return SPEC | c | cmask;
			d = get1key();
			if (d == '~')		/* ESC [ n ~   P.K. */
				return SPEC | c | cmask;
			switch (c) {		/* ESC [ n n ~ P.K. */
			case '1':
				c = d + 32;
				break;
			case '2':
				c = d + 48;
				break;
			case '3':
				c = d + 64;
				break;
			default:
				c = '?';
				break;
			}
			if (d != '~')		/* eat tilde P.K. */
				get1key();
			if (c == 'i') {		/* DO key    P.K. */
				c = ctlx_char;
				goto proc_ctlxc;
			} else if (c == 'c')	/* ESC key   P.K. */
				c = get1key();
			else
				return SPEC | c | cmask;
		}
		if (c == (CONTROL | '[')) {
			cmask = META;
			goto proc_metac;
		}
		if (islower(c))			/* Force to upper */
			c ^= DIFCASE;
		if (c >= 0x00 && c <= 0x1F)	/* control key */
			c = CONTROL | (c + '@');
		return META | c;
	} else if (c == meta_char) {
		c = get1key();
		if (c == (CONTROL | '[')) {
			cmask = META;
			goto proc_metac;
		}
		if (islower(c))			/* Force to upper */
			c ^= DIFCASE;
		if (c >= 0x00 && c <= 0x1F)	/* control key */
			c = CONTROL | (c + '@');
		return META | c;
	}

 proc_ctlxc:
	/* process CTLX prefix */
	if (c == ctlx_char) {
		c = get1key();
		if (c == (CONTROL | '[')) {
			cmask = CTLX;
			goto proc_metac;
		}
		if (c >= 'a' && c <= 'z')	/* Force to upper */
			c -= 0x20;
		if (c >= 0x00 && c <= 0x1F)	/* control key */
			c = CONTROL | (c + '@');
		return CTLX | c;
	}

	/* otherwise, just return it */
	return c;
}

/*	A more generalized prompt/reply function allowing the caller
	to specify the proper terminator. If the terminator is not
	a return ('\n') it will echo as "<NL>"
							*/
int getstring(char *prompt, char *buf, int nbuf, int eolchar)
{
	int cpos;				/* current character position in string */
	int c;
	int quotef;				/* are we quoting the next char? */
	int ffile, ocpos, nskip = 0, didtry = 0;

	static char tmp[] = "/tmp/meXXXXXX";
	FILE *tmpf = NULL;

	ffile = (strcmp(prompt, "Find file: ") == 0
		 || strcmp(prompt, "View file: ") == 0
		 || strcmp(prompt, "Insert file: ") == 0
		 || strcmp(prompt, "Write file: ") == 0
		 || strcmp(prompt, "Read file: ") == 0 || strcmp(prompt, "File to execute: ") == 0);

	cpos = 0;
	quotef = FALSE;

	/* prompt the user for the input string */
	msg_puts(prompt);

	for (;;) {
		if (!didtry)
			nskip = -1;
		didtry = 0;
		/* get a character from the user */
		c = get1key();

		/* If it is a <ret>, change it to a <NL> */
		if (c == (CONTROL | 0x4d) && !quotef)
			c = CONTROL | 0x40 | '\n';

		/* if they hit the line terminate, wrap it up */
		if (c == eolchar && quotef == FALSE) {
			buf[cpos++] = 0;

			/* clear the message line */
			msg_printf("");
			ttflush();

			/* if we default the buffer, return FALSE */
			if (buf[0] == 0)
				return FALSE;

			return TRUE;
		}

		/* change from command form back to character form */
		c = keycode_to_char(c);

		if (c == keycode_to_char(abort_char) && quotef == FALSE) {
			/* Abort the input? */
			cmd_abort_command(FALSE, 0);
			ttflush();
			return ABORT;
		} else if ((c == 0x7F || c == 0x08) && quotef == FALSE) {
			/* rubout/erase */
			if (cpos != 0) {
				outstring("\b \b");
				--shown_col;

				if (buf[--cpos] < 0x20) {
					outstring("\b \b");
					--shown_col;
				}
				if (buf[cpos] == '\n') {
					outstring("\b\b  \b\b");
					shown_col -= 2;
				}

				ttflush();
			}

		} else if (c == 0x15 && quotef == FALSE) {
			/* C-U, kill */
			while (cpos != 0) {
				outstring("\b \b");
				--shown_col;

				if (buf[--cpos] < 0x20) {
					outstring("\b \b");
					--shown_col;
				}
				if (buf[cpos] == '\n') {
					outstring("\b\b  \b\b");
					shown_col -= 2;
				}
			}
			ttflush();

		} else if ((c == 0x09 || c == ' ') && quotef == FALSE && ffile) {
			/* TAB, complete file name */
			char ffbuf[255];
			int n, iswild = 0;

			didtry = 1;
			ocpos = cpos;
			while (cpos != 0) {
				outstring("\b \b");
				--shown_col;

				if (buf[--cpos] < 0x20) {
					outstring("\b \b");
					--shown_col;
				}
				if (buf[cpos] == '\n') {
					outstring("\b\b  \b\b");
					shown_col -= 2;
				}
				if (buf[cpos] == '*' || buf[cpos] == '?')
					iswild = 1;
			}
			ttflush();
			if (nskip < 0) {
				buf[ocpos] = 0;
				if (tmpf != NULL)
					fclose(tmpf);
				strcpy(tmp, "/tmp/meXXXXXX");
				strcpy(ffbuf, "echo ");
				strcat(ffbuf, buf);
				if (!iswild)
					strcat(ffbuf, "*");
				strcat(ffbuf, " >");
				xmkstemp(tmp);
				strcat(ffbuf, tmp);
				strcat(ffbuf, " 2>&1");
				system(ffbuf);
				tmpf = fopen(tmp, "r");
				nskip = 0;
			}
			c = ' ';
			for (n = nskip; n > 0; n--)
				while ((c = getc(tmpf)) != EOF && c != ' ') ;
			nskip++;

			if (c != ' ') {
				tcapbeep();
				nskip = 0;
			}

			while ((c = getc(tmpf)) != EOF && c != '\n' && c != ' ' && c != '*') {
				if (cpos < nbuf - 1)
					buf[cpos++] = c;
			}

			if (c == '*')
				tcapbeep();

			for (n = 0; n < cpos; n++) {
				c = buf[n];
				if ((c < ' ') && (c != '\n')) {
					outstring("^");
					++shown_col;
					c ^= 0x40;
				}

				if (c != '\n') {
					if (display_input)
						ttputc(c);
				} else {	/* put out <NL> for <ret> */
					outstring("<NL>");
					shown_col += 3;
				}
				++shown_col;
			}
			ttflush();
			rewind(tmpf);
			unlink(tmp);

		} else if ((c == quote_char || c == 0x16) && quotef == FALSE) {
			quotef = TRUE;
		} else {
			quotef = FALSE;
			if (cpos < nbuf - 1) {
				buf[cpos++] = c;

				if ((c < ' ') && (c != '\n')) {
					outstring("^");
					++shown_col;
					c ^= 0x40;
				}

				if (c != '\n') {
					if (display_input)
						ttputc(c);
				} else {	/* put out <NL> for <ret> */
					outstring("<NL>");
					shown_col += 3;
				}
				++shown_col;
				ttflush();
			}
		}
	}
}

/*
 * output a string of characters
 *
 * char *s;		string to output
 */
void outstring(char *s)
{
	if (display_input)
		while (*s)
			ttputc(*s++);
}

/*
 * output a string of output characters
 *
 * char *s;		string to output
 */
void ostring(char *s)
{
	if (display_commands)
		while (*s)
			ttputc(*s++);
}
