/*
 * Get input string, with VMS-style input line editing
 * and previous-command scrolling.
 *
 * Written for Turbo C 2.0 / Borland C++ 2.0
 * Bob Bybee, 2/91
 */
#include <stdio.h>
#include <string.h>

/* ASCII key definitions */
#define ESC_KEY			0x1b
#define DELETE_KEY		0x7f
#define BACKSPACE_KEY	0x08
#define RETURN_KEY 		0x0d
#define CTRL(x) ((x) & 0x1f)

/* Arbitrary values for tracking cursor key entry.
 * These happen to match PC BIOS scan codes, but any
 * unique values would work.
 */
#define UP_ARROW    0x4800
#define DOWN_ARROW  0x5000
#define RIGHT_ARROW 0x4d00
#define LEFT_ARROW  0x4b00

/* MAX_RECALL is two greater than the number of lines
 * we want in the "lastlines" recall buffer.
 */
#define MAX_RECALL 22
#define RECSUB(x) (((x) + MAX_RECALL) % MAX_RECALL)
#define RECALL_LEN 100
static char lastlines[MAX_RECALL][RECALL_LEN];

static int num_got;			/* # chars in input buffer */
static int cursor_pos;		/* cursor position on line */
static int lastptr = 0;		/* ptr to last line entered */
static char erase_one[] = "\b \b";	/* erase one character */
static char *str_ptr;		/* ptr to current input string */


/* prototypes for this file */
static void clear_line( void );
static int cursor_right( void );
static int cursor_left( void );
static int get_char_esc( void );
static void put_str( char *str );

/* external functions (see listing2.c) */
void sys_putchar( char ch );
int sys_getchar( void );



/*
 * get_str() is called by main() to get a line of input.
 * The input line is placed in "str" and will be no
 * more than "len" characters.
 */
void get_str( char *str, int len )
	{
	int i, c, curptr, insert_mode = 1;

	num_got = 0;
	cursor_pos = 0;
	str_ptr = str;				/* copy the buffer pointer */
	curptr = RECSUB(lastptr + 1);
	lastlines[curptr][0] = '\0';
	lastlines[RECSUB(curptr + 1)][0] = '\0';
	if (len > RECALL_LEN - 1)	/* limit len to RECALL_LEN */
		len = RECALL_LEN - 1;

	while (1)
		{
		c = get_char_esc();

		if (c == RETURN_KEY)
			break;
		else if (c == DELETE_KEY || c == BACKSPACE_KEY)
			{
			if (cursor_left())
				{
				++cursor_pos;
				for (i = cursor_pos; i < num_got; ++i)
					{
					str[i - 1] = str[i];
					sys_putchar(str[i]);
					}
				sys_putchar(' ');
				for (i = cursor_pos; i <= num_got; ++i)
					sys_putchar('\b');
				--num_got;
				--cursor_pos;
				}
			}
		else if (c == CTRL('X'))	/* erase line? */
			clear_line();
		else if (c == CTRL('A'))	/* insert/overtype? */
			insert_mode ^= 1;
		else if (c == CTRL('B'))	/* beginning-of-line? */
			{
			while (cursor_left())
				;
			}
		else if (c == CTRL('E'))	/* end-of-line? */
			{
			while (cursor_right())
				;
			}
		else if (c == CTRL('R'))	/* recall last line? */
			{
			clear_line();
			strcpy(str, lastlines[lastptr]);
			if ((num_got = strlen(str)) > 0)
				{
				put_str(str);
				break;
				}
			}
		else if (c == UP_ARROW)
			{
			clear_line();
			if (lastlines[curptr][0] != '\0' ||
				lastlines[RECSUB(curptr - 1)][0] != '\0')
				{
				curptr = RECSUB(curptr - 1);
				strcpy(str, lastlines[curptr]);
				put_str(str);
				cursor_pos = num_got = strlen(str);
				}
			}
		else if (c == DOWN_ARROW)
			{
			clear_line();
			if (lastlines[curptr][0] != '\0' ||
				lastlines[RECSUB(curptr + 1)][0] != '\0')
				{
				curptr = RECSUB(curptr + 1);
				strcpy(str, lastlines[curptr]);
				put_str(str);
				cursor_pos = num_got = strlen(str);
				}
			}
		else if (c == LEFT_ARROW)
			{
			if (cursor_pos > 0)
				{
				sys_putchar('\b');
				--cursor_pos;
				}
			}
		else if (c == RIGHT_ARROW)
			cursor_right();
		else if (' ' <= c && c < 0x7f && num_got < len - 1)
			{
			if (insert_mode)
				{
				/* Move right, all the characters
				 * to the right of cursor_pos.
				 */
				for (i = num_got; i > cursor_pos; --i)
					str[i] = str[i - 1];
				str[cursor_pos] = c;
				for (i = cursor_pos; i <= num_got; ++i)
					sys_putchar(str[i]);
				for (i = cursor_pos; i < num_got; ++i)
					sys_putchar('\b');
				++num_got;
				++cursor_pos;
				}
			else	/* insert is off, use overtype mode */
				{
				str[cursor_pos] = c;
				sys_putchar(c);
				if (cursor_pos == num_got)
					++num_got;
				++cursor_pos;
				}
			}
		}

	str[num_got] = '\0';
	sys_putchar('\n');

	/* If this line is non-empty, and different
	 * from the last one accepted, store it into
	 * the recall buffer.
	 */
	if (num_got > 0 && strcmp(str, lastlines[lastptr]) != 0)
		{
		lastptr = RECSUB(lastptr + 1);
		strcpy(lastlines[lastptr], str);
		}
	}


/*
 * Move the cursor right one position, by echoing the
 * character it's currently over.
 * Return 1-OK, 0-can't move.
 */
static int cursor_right( void )
	{
	if (cursor_pos < num_got)
		{
		sys_putchar(str_ptr[cursor_pos]);
		++cursor_pos;
		return (1);
		}
	return (0);
	}


/*
 * Move the cursor left one position, by echoing
 * a backspace.  Return 1-OK, 0-can't move.
 */
static int cursor_left( void )
	{
	if (cursor_pos > 0)
		{
		sys_putchar('\b');
		--cursor_pos;
		return (1);
		}
	return (0);
	}


/*
 * Erase all characters on the current line.
 */
static void clear_line( void )
	{

	while (cursor_right())
		;		/* move right, to end of line */

	cursor_pos = 0;
	while (num_got > 0)
		{
		put_str(erase_one);		/* then, erase to left */
		--num_got;
		}
	}


/*
 * Get a character, with escape processing.
 * Handles special sequences like "ESC [ A" for up-arrow.
 * This function would need to be modified to handle
 * keyboards that are neither PC's nor VT-100's.
 */
static int get_char_esc( void )
	{
	int ch;

	ch = sys_getchar();
	if (ch != ESC_KEY)
		return (ch);

	ch = sys_getchar();
	if (ch != '[')
		return (ch);

	ch = sys_getchar();
	if (ch == 'A')
		return (UP_ARROW);		/* was ESC [ A */
	else if (ch == 'B')
		return (DOWN_ARROW);	/* was ESC [ B */
	else if (ch == 'C')
		return (RIGHT_ARROW);	/* was ESC [ C */
	else if (ch == 'D')
		return (LEFT_ARROW);	/* was ESC [ D */
	else
		return (ch);
	}


/*
 * Put a string to sys_putchar().
 */
static void put_str( char *str )
	{
	while (*str != '\0')
		sys_putchar(*str++);
	}
