/*
 * Echo line reading and writing. 
 *
 * Common routines for reading and writing characters in the echo line area of
 * the display screen. Used by the entire known universe. 
 */
#include	"no_macro.h"

#include	"def.h"
#include	"key.h"
#ifdef		ANSI
#include	<stdarg.h>
#else
#include	"varargs.h"
#endif
#include	"line.h"
#include	"buffer.h"
#include	"no_metakey.h"
#ifndef NO_MACRO
#  include	"macro.h"
#endif

int             epresf = FALSE;	/* Stuff in echo line flag.	 */
#ifdef	LATTICE_50
static char     a;		/* Sigh */
#endif

#ifdef	ANSI
#include <stdlib.h>
#include <string.h>
#else
VOID            ewprintf();
#endif

static int veread 
PROTO((char *fp, char *buf, int nbuf, int flag, va_list * ap));
static int complt 
PROTO((int flags, int c, char *buf, int cpos));
static VOID eformat 
PROTO((char *fp, va_list * ap));
static VOID eputi 
PROTO((int i, int r));
static VOID eputl 
PROTO((long l, int r));
static VOID eputs 
PROTO((char *s));
static VOID eputc 
PROTO((int c));
static int      complete_list
PROTO((struct list * lh, int c, char *buf, int cpos));
static int      getxtra
PROTO((register struct list *, register struct list *, int, register int));

/*
 * Erase the echo line. 
 */
VOID
eerase()
{
#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	ttcolor(CTEXT);
	ttmove(nrow - 1, 0);
	tteeol();
	ttflush();
	epresf = FALSE;
}

/*
 * Ask "yes" or "no" question. Return ABORT if the user answers the question
 * with the abort ("^G") character. Return FALSE for "no" and TRUE for "yes".
 * No formatting services are available. No newline required. 
 */
eyorn(sp)
	char           *sp;
{
	register int    s;

	ewprintf("%s? (y or n) ", sp);
	for (;;) {
		s = getkey(DISSCR);
		if (s == 'y' || s == 'Y')
			return TRUE;
		if (s == 'n' || s == 'N')
			return FALSE;
		if (s == CCHR('G'))
			return ctrlg(FFRAND, 1);
		ewprintf("Please answer y or n.  %s? (y or n) ", sp);
	}
	/* NOTREACHED */
}

/*
 * Like eyorn, but for more important question. User must type either all of
 * "yes" or "no", and the trainling newline. 
 */
eyesno(sp)
	char           *sp;
{
	register int    s;
	char            buf[64];

	s = ereply("%s? (yes or no) ", buf, sizeof(buf), sp);
	for (;;) {
		if (s == ABORT)
			return ABORT;
		if (s != FALSE) {
			if ((buf[0] == 'y' || buf[0] == 'Y')
			    && (buf[1] == 'e' || buf[1] == 'E')
			    && (buf[2] == 's' || buf[2] == 'S')
			    && (buf[3] == '\0'))
				return TRUE;
			if ((buf[0] == 'n' || buf[0] == 'N')
			    && (buf[1] == 'o' || buf[0] == 'O')
			    && (buf[2] == '\0'))
				return FALSE;
		}
		s = ereply("Please answer yes or no.  %s? (yes or no) ",
			   buf, sizeof(buf), sp);
	}
	/* NOTREACHED */
}
/*
 * Write out a prompt, and read back a reply. The prompt is now written out
 * with full "ewprintf" formatting, although the arguments are in a rather
 * strange place. This is always a new message, there is no auto completion,
 * and the return is echoed as such. 
 */
#ifndef ANSI
/* VARARGS 0 */
ereply(va_alist)
va_dcl
{
	va_list         pvar;
	register char  *fp, *buf;
	register int    nbuf;
	register int    i;

	va_start(pvar);
	fp = va_arg(pvar, char *);
	buf = va_arg(pvar, char *);
	nbuf = va_arg(pvar, int);
	i = veread(fp, buf, nbuf, EFNEW | EFCR, &pvar);
	va_end(pvar);
	return i;
}
#else

int
#ifdef	LATTICE
                __stdargs
#endif
ereply(char *fp, char *buf, int nbuf,...)
{
	va_list         pvar;
	register int    i;

	va_start(pvar, nbuf);
	i = veread(fp, buf, nbuf, EFNEW | EFCR, &pvar);
	va_end(pvar);
	return i;
}
#endif
/*
 * This is the general "read input from the echo line" routine. The basic
 * idea is that the prompt string "prompt" is written to the echo line, and a
 * one line reply is read back into the supplied "buf" (with maximum length
 * "len"). The "flag" contains EFNEW (a new prompt), an EFFUNC
 * (autocomplete), or EFCR (echo the carriage return as CR). 
 */
#ifndef	ANSI
/* VARARGS 0 */
eread(va_alist)
va_dcl
{
	va_list         pvar;
	char           *fp, *buf;
	int             nbuf, flag, i;
	va_start(pvar);
	fp = va_arg(pvar, char *);
	buf = va_arg(pvar, char *);
	nbuf = va_arg(pvar, int);
	flag = va_arg(pvar, int);
	i = veread(fp, buf, nbuf, flag, &pvar);
	va_end(pvar);
	return i;
}
#else
int
#ifdef	LATTICE
                __stdargs
#endif
eread(char *fp, char *buf, int nbuf, int flag,...)
{
	va_list         pvar;
	int             i;

	va_start(pvar, flag);
	i = veread(fp, buf, nbuf, flag, &pvar);
	va_end(pvar);
	return i;
}
#endif
static
veread(fp, buf, nbuf, flag, ap)
	char           *fp;
	char           *buf;
	va_list        *ap;
{
	register int    cpos;
	register int    i;
	register int    c;
#ifndef	NO_METAKEY
	extern int      use_metakey;
	int             save_metakey = use_metakey;

	use_metakey = FALSE;
#endif

	cpos = 0;
#ifndef	NO_MACRO
	if (!inmacro) {
#endif
		if ((flag & EFNEW) == 0 && ttrow == nrow - 1)
			eputc(' ');
		else {
			ttcolor(CTEXT);
			ttmove(nrow - 1, 0);
			epresf = TRUE;
		}
		eformat(fp, ap);
		tteeol();
		ttflush();
#ifndef	NO_MACRO
	}
#endif
	for (;;) {
		c = getkey(FALSE);
		if ((flag & EFAUTO) != 0 && (c == ' ' || c == CCHR('I'))) {
			cpos += complt(flag, c, buf, cpos);
			continue;
		}
		switch (c) {
		case CCHR('J'):
			c = CCHR('M');	/* and continue		 */
		case CCHR('M'):/* Return, done.	 */
			if ((flag & EFFORCE) == 0)
				buf[cpos] = '\0';
			else if ((i = complt(flag, c, buf, cpos)) >= 0) {
				cpos += i;
				continue;
			}
			if ((flag & EFCR) != 0
#ifndef	NO_MACRO
			    && inmacro
#endif
				) {
				ttputc(CCHR('M'));
				ttflush();
			}
#ifndef	NO_METAKEY
			use_metakey = save_metakey;
#endif
			return buf[0] != '\0';

		case CCHR('G'):/* Bell, abort.		 */
#ifndef	NO_METAKEY
			use_metakey = save_metakey;
#endif
#ifndef	NO_MACRO
			if (inmacro)
				return ABORT;
#endif
			eputc(CCHR('G'));
			(VOID) ctrlg(FFRAND, 0);
			ttflush();
			return ABORT;

		case CCHR('H'):
		case CCHR('?'):/* Rubout, erase.	 */
			if (cpos != 0) {
#ifndef	NO_MACRO
				if (inmacro) {
					cpos -= 1;
					break;
				}
#endif
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
				ttflush();
			}
			break;

		case CCHR('X'):/* C-X			 */
		case CCHR('U'):/* C-U, kill line.	 */
#ifndef NO_MACRO
			if (inmacro) {
				cpos = 0;
				break;
			}
#endif
			while (cpos != 0) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			ttflush();
			break;

		case CCHR('W'):/* C-W, kill to beginning of */
			/* previous word	 */
			/* back up to first word character or beginning */
			while ((cpos > 0) && !ISWORD(buf[cpos - 1])) {
#ifndef	NO_MACRO
				if (inmacro) {
					cpos -= 1;
					continue;
				}
#endif
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
#ifndef	NO_MACRO
				if (inmacro) {
					cpos -= 1;
					continue;
				}
#endif
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
#ifndef NO_MACRO
			if (!inmacro)
#endif
				ttflush();
			break;

		case CCHR('\\'):
		case CCHR('Q'):/* C-Q, quote next	 */
			c = getkey(FALSE);	/* and continue		 */
		default:	/* All the rest.	 */
			if (cpos < nbuf - 1) {
				buf[cpos++] = (char) c;
#ifndef	NO_MACRO
				if (inmacro)
					break;
#endif
				eputc((char) c);
				ttflush();
			}
		}
	}
}
/*
 * do completion for veread. The completion routines return: >= 0 number of
 * characters we can complete by -1	no match at all -2 ambiguous -3	match
 * is correct & complete as it is, used only if c = C-m 
 */
static int
complt(flags, c, buf, cpos)
	register char  *buf;
	register int    cpos;
{
	register int    i, j;
	int             msglen, nshown;
	char           *msg;
#ifndef	NO_MACRO
	register int	xtra;
	char		macbuf[NMACN];
	extern struct macro kbdmacro;
#endif

	if (flags & EFBUF)
		i = complete_list(&(bheadp->b_list), c, buf, cpos);
#ifndef	NO_MACRO
	else if (flags & EFMACRO)
		i = complete_list(&(kbdmacro.m_list), c, buf, cpos);
#endif
	else if (flags & EFFUNC) {
		buf[cpos] = '\0';
		i = complete_function(buf, c);
#ifndef	NO_MACRO
		strncpy(macbuf, buf, NMACN);
		j = complete_list(&(kbdmacro.m_list), c, macbuf, cpos);
		if (i == -3 || j == -3)
			i = -3;
		else if (i == -2 || j == -2)
			i = -2;
		else if (i == -1) {
			if (j > 0)
				bcopy(macbuf + cpos, buf + cpos, j + 1);
			i = j;
		} else if (j >= 0) {
			for (xtra = 0; xtra < i & xtra < j; xtra += 1)
				if (buf[cpos + xtra] != macbuf[cpos + xtra])
					break;
			i = xtra == 0 ? -2 : xtra;
			buf[cpos + xtra + 1] = '\0';
		}
#endif
	} else
		panic("broken complt call: flags");

#ifndef	NO_MACRO
	if (inmacro)
		return i;
#endif

	if (i > 0) {
		eputs(&buf[cpos]);
		ttflush();
		return i;
	}
	switch (i) {
	case -1:
		msg = " [No match]";
		break;
	case -2:
		msg = " [Ambiguous]";
		break;
	case -3:
	case 0:
		return i;
	default:
		msg = " [Internal error]";
		break;
	}
	/* Set up backspaces, etc., being mindful of echo line limit */
	msglen = strlen(msg);
	nshown = (ttcol + msglen + 2 > ncol) ?
		ncol - ttcol - 2 : msglen;
	eputs(msg);
	ttcol -= (j = nshown);	/* update ttcol!		 */
	while (j--)		/* move back before msg		 */
		ttputc('\b');
	ttflush();		/* display to user		 */
	j = nshown;
	while (j--)		/* blank out	on next flush	 */
		eputc(' ');
	ttcol -= (j = nshown);	/* update ttcol on BS's		 */
	while (j--)
		ttputc('\b');	/* update ttcol again!		 */
	return 0;
}


/*
 * do completion on a list of objects 
 */
static int
complete_list(lh, c, buf, cpos)
	register struct list *lh;
	register char  *buf;
	register int    cpos;
{
	register struct list *lh2;
	register int    i;
	int             nhits, nxtra, bxtra;
	int             match = FALSE;

	nhits = 0;
	nxtra = HUGE;

	while (lh != NULL) {
		for (i = 0; i < cpos; i += 1) {
			if (buf[i] != lh->l_name[i])
				break;
		}
		if (i == cpos) {
			if (nhits == 0)
				lh2 = lh;
			nhits += 1;
			if (lh->l_name[i] == '\0')
				match = TRUE;
			bxtra = getxtra(lh, lh2, cpos, c == ' ');
			if (bxtra < nxtra)
				nxtra = bxtra;
			lh2 = lh;
		}
		lh = lh->l_next;
	}
	if (match && c == CCHR('M'))
		return -3;	/* complete match */
	if (nhits == 0)
		return -1;	/* No match */
	if (nhits > 1 && nxtra == 0)
		return -2;	/* Ambiguous */

	/* Match; copy it out and return the count */
	for (i = 0; i < nxtra; i += 1) {
		buf[cpos + i] = lh2->l_name[cpos + i];
	}
	buf[cpos + i] = '\0';
	return nxtra;
}

/*
 * The "lp1" and "lp2" point to list structures. The "cpos" is a horizontal
 * position in the name. Return the longest block of characters that can be
 * autocompleted at this point. Sometimes the two symbols are the same, but
 * this is normal. 
 */
static int
getxtra(lp1, lp2, cpos, wflag)
	register struct list *lp1, *lp2;
	register int    wflag;
{
	register int    i;

	i = cpos;
	for (;;) {
		if (lp1->l_name[i] != lp2->l_name[i])
			break;
		if (lp1->l_name[i] == '\0')
			break;
		i += 1;
		if (wflag && !ISWORD(lp1->l_name[i - 1]))
			break;
	}
	return (i - cpos);
}

/*
 * Special "printf" for the echo line. Each call to "ewprintf" starts a new
 * line in the echo area, and ends with an erase to end of the echo line. The
 * formatting is done by a call to the standard formatting routine. 
 */
/* VARARGS 0 */
VOID
#ifndef	ANSI
ewprintf(va_alist)
va_dcl
#else
#ifdef	LATTICE
__stdargs
#endif
ewprintf(char *fp,...)
#endif
{
	va_list         pvar;
#ifndef	ANSI
	register char  *fp;
#endif

#ifndef NO_MACRO
	if (inmacro)
		return;
#endif
#ifndef	ANSI
	va_start(pvar);
	fp = va_arg(pvar, char *);
#else
	va_start(pvar, fp);
#endif
	ttcolor(CTEXT);
	ttmove(nrow - 1, 0);
	eformat(fp, &pvar);
	va_end(pvar);
	tteeol();
	ttflush();
	epresf = TRUE;
}

/*
 * Printf style formatting. This is called by both "ewprintf" and "ereply" to
 * provide formatting services to their clients. They move to the start of
 * the echo line, and the erase to the end of the echo line, is done by the
 * caller. Note: %c works, and prints the "name" of the character. %k prints
 * the name of a key (and takes no arguments). 
 */
static          VOID
eformat(fp, ap)
	register char  *fp;
	register va_list *ap;
{
	register int    c;
	char            kname[NKNAME];
	char           *keyname();
	char           *cp;

#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	while ((c = *fp++) != '\0') {
		if (c != '%')
			eputc(c);
		else {
			c = *fp++;
			switch (c) {
			case 'c':
				(VOID) keyname(kname, va_arg(*ap, int));
				eputs(kname);
				break;

			case 'k':
				cp = kname;
				for (c = 0; c < key.k_count; c++) {
					cp = keyname(cp, key.k_chars[c]);
					*cp++ = ' ';
				}
				*--cp = '\0';
				eputs(kname);
				break;

			case 'd':
				eputi(va_arg(*ap, int), 10);
				break;

			case 'o':
				eputi(va_arg(*ap, int), 8);
				break;

			case 's':
				eputs(va_arg(*ap, char *));
				break;

			case 'l':	/* explicit longword */
				c = *fp++;
				switch (c) {
				case 'd':
					eputl((long) va_arg(*ap, long), 10);
					break;
				default:
					eputc(c);
					break;
				}
				break;

			default:
				eputc(c);
			}
		}
	}
}

/*
 * Put integer, in radix "r". 
 */
static          VOID
eputi(i, r)
	register int    i;
	register int    r;
{
	register int    q;

#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	if (i < 0) {
		eputc('-');
		i = -i;
	}
	if ((q = i / r) != 0)
		eputi(q, r);
	eputc(i % r + '0');
}

/*
 * Put long, in radix "r". 
 */
static          VOID
eputl(l, r)
	register long   l;
	register int    r;
{
	register long   q;

#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	if (l < 0) {
		eputc('-');
		l = -l;
	}
	if ((q = l / r) != 0)
		eputl(q, r);
	eputc(l % r + '0');
}

/*
 * Put string. 
 */
static          VOID
eputs(s)
	register char  *s;
{
	register int    c;

#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	while ((c = *s++) != '\0')
		eputc(c);
}

/*
 * Put character. Watch for control characters, and for the line getting too
 * long. 
 */
static          VOID
eputc(c)
	register int    c;
{
#ifndef	NO_MACRO
	if (inmacro)
		return;
#endif
	if (ttcol + 2 < ncol) {
		if (ISCTRL(c)) {
			eputc('^');
			c = CCHR(c);
		}
		ttputc((char) c);
		++ttcol;
	}
}
