/*
 *		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.
 */
/*
 * The varargs lint directive comments are 0 an attempt to get lint to shup
 * up about CORRECT usage of varargs.h.  It won't.
 */
#include	"def.h"
#include	"key.h"
#ifdef	LOCAL_VARARGS
#include	"varargs.h"
#else
#include	<varargs.h>
#endif
#ifndef NO_MACRO
#  include	"macro.h"
#endif

static int	veread();
VOID		ewprintf();
static VOID	eformat();
static VOID	eputi();
static VOID	eputl();
static VOID	eputs();
static VOID	eputc();
static int	complt();

int	epresf	= FALSE;		/* Stuff in echo line flag.	*/
/*
 * Erase the echo line.
 */
VOID
eerase() {
	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;

#ifndef NO_MACRO
	if(inmacro) return TRUE;
#endif
	ewprintf("%s? (y or n) ", sp);
	for (;;) {
		s = getkey(FALSE);
		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];

#ifndef NO_MACRO
	if(inmacro) return TRUE;
#endif
	s = ereply("%s? (yes or no) ", buf, sizeof(buf), sp);
	for (;;) {
		if (s == ABORT) return ABORT;
		if (s != FALSE) {
#ifndef NO_MACRO
			if (macrodef) {
			    LINE *lp = maclcur;

			    maclcur = lp->l_bp;
			    maclcur->l_fp = lp->l_fp;
			    free((char *)lp);
			}
#endif
			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.
 */
/*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;
}

/*
 * 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).
 */
/* 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;
}

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_MACRO
	if(inmacro) {
	    bcopy(maclcur->l_text, buf, maclcur->l_used);
	    buf[maclcur->l_used] = '\0';
	    maclcur = maclcur->l_fp;
	    return TRUE;
	}
#endif
	cpos = 0;
	if ((flag&EFNEW)!=0 || ttrow!=nrow-1) {
		ttcolor(CTEXT);
		ttmove(nrow-1, 0);
		epresf = TRUE;
	} else
		eputc(' ');
	eformat(fp, ap);
	tteeol();
	ttflush();
	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&EFFUNC) != 0) {
				if ((i = complt(flag, c, buf, cpos)) == 0)
					continue;
				if (i > 0) cpos += i;
			}
			buf[cpos] = '\0';
			if ((flag&EFCR) != 0) {
				ttputc(CCHR('M'));
				ttflush();
			}
#ifndef NO_MACRO
			if(macrodef) {
			    LINE *lp;

			    if((lp = lalloc(cpos)) == NULL) return FALSE;
			    lp->l_fp = maclcur->l_fp;
			    maclcur->l_fp = lp;
			    lp->l_bp = maclcur;
			    maclcur = lp;
			    bcopy(buf, lp->l_text, cpos);
			}
#endif
			goto done;

		    case CCHR('G'):		/* Bell, abort.		*/
			eputc(CCHR('G'));
			(VOID) ctrlg(FFRAND, 0);
			ttflush();
			return ABORT;

		    case CCHR('H'):
		    case CCHR('?'):		/* Rubout, erase.	*/
			if (cpos != 0) {
				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.	*/
			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])) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			while ((cpos > 0) && ISWORD(buf[cpos - 1])) {
				ttputc('\b');
				ttputc(' ');
				ttputc('\b');
				--ttcol;
				if (ISCTRL(buf[--cpos]) != FALSE) {
					ttputc('\b');
					ttputc(' ');
					ttputc('\b');
					--ttcol;
				}
			}
			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;
				eputc((char) c);
				ttflush();
			}
		}
	}
done:	return buf[0] != '\0';
}

/*
 * do completion on a list of objects.
 */
static int complt(flags, c, buf, cpos)
register char *buf;
register int cpos;
{
	register LIST	*lh, *lh2;
	int		i, nxtra;
	int		nhits, bxtra;
	int		wflag = FALSE;
	int		msglen, nshown;
	char		*msg;

	if ((flags&EFFUNC) != 0) {
	    buf[cpos] = '\0';
	    i = complete_function(buf, c);
	    if(i>0) {
		eputs(&buf[cpos]);
		ttflush();
		return i;
	    }
	    switch(i) {
		case -3:
		    msg = " [Ambiguous]";
		    break;
		case -2:
		    i=0;
		    msg = " [No match]";
		    break;
		case -1:
		case 0:
		    return i;
		default:
		    msg = " [Internal error]";
		    break;
	    }
	} else {
	    if ((flags&EFBUF) != 0) lh = &(bheadp->b_list);
	    else panic("broken complt call: flags");

	    if (c == ' ') wflag = TRUE;
	    else if (c != '\t' && c != CCHR('M')) panic("broken complt call: c");

	    nhits = 0;
	    nxtra = HUGE;

	    while (lh != NULL) {
		for (i=0; i<cpos; ++i) {
			if (buf[i] != lh->l_name[i])
				break;
		}
		if (i == cpos) {
			if (nhits == 0)
				lh2 = lh;
			++nhits;
			if (lh->l_name[i] == '\0') nxtra = -1;
			else {
				bxtra = getxtra(lh, lh2, cpos, wflag);
				if (bxtra < nxtra) nxtra = bxtra;
				lh2 = lh;
			}
		}
		lh = lh->l_next;
	    }
	    if (nhits == 0)
		msg = " [No match]";
	    else if (nhits > 1 && nxtra == 0)
		msg = " [Ambiguous]";
	    else {		/* Got a match, do it to it */
		/*
		 * Being lazy - ought to check length, but all things
		 * autocompleted have known types/lengths.
		 */
		if (nxtra < 0 && nhits > 1 && c == ' ') nxtra = 1;
		for (i = 0; i < nxtra; ++i) {
			buf[cpos] = lh2->l_name[cpos];
			eputc(buf[cpos++]);
		}
		ttflush();
		if (nxtra < 0 && c != CCHR('M')) return 0;
		return nxtra;
	    }
	}
	/* 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 -= (i = nshown);		/* update ttcol!		*/
	while (i--)			/* move back before msg		*/
		ttputc('\b');
	ttflush();			/* display to user		*/
	i = nshown;
	while (i--)			/* blank out	on next flush	*/
		eputc(' ');
	ttcol -= (i = nshown);		/* update ttcol on BS's		*/
	while (i--)
		ttputc('\b');		/* update ttcol again!		*/
	return 0;
}

/*
 * 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.
  */
getxtra(lp1, lp2, cpos, wflag) register 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;
		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
ewprintf(va_alist)
va_dcl
{
	va_list pvar;
	register char *fp;

#ifndef NO_MACRO
	if(inmacro) return;
#endif
	va_start(pvar);
	fp = va_arg(pvar, char *);
	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. The 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;

	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;

	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;

	if(l < 0) {
	    eputc('-');
	    l = -l;
	}
	if ((q=l/r) != 0)
		eputl(q, r);
	eputc((int)(l%r)+'0');
}

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

	while ((c = *s++) != '\0')
		eputc(c);
}

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

