/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/
/*
 * Modified April-89 by Tom (squeeze...) Hageman
 * - some efficiency improvements
 * - added %x %X %O %r (remote fmt, arg) directives.
 * - padding with '0' only for numeric types.
 * - %n directive does not eat an argument; takes last used number instead
 * [25-Jan-93]
 * - %N (show numeric argument)
 */

#include "jove.h"

RCS("$Id: fmt.c,v 14.30.0.10 1994/04/22 18:24:23 tom Exp tom $")

#include "ctype.h"
#include "io.h"

#ifndef TINY
DEF_INT( "prompt-with-prefix-arg", ShowExp, V_BOOL ) ZERO; _IF(ndef TINY)_IF(def PRIVATE)
#endif

char	mesgbuf[MESG_SIZE];


private char *PPchar __(( unsigned int _(c) ));
private char *
PPchar(c)
register unsigned int	c;
{
	static char	CTLtemplate[] = "C-x";

#ifdef FUNCKEYS
	if (c >= 0200) {
		static char	FKtemplate[] = "^:xx";

		if ((c &= 0177) >= NFKEYS || (c = FKeyMap[c]) == 0)
			c = '?' | ('?' << 8);	/* i.e. '??' */

		*(short *) &FKtemplate[2] = c;
		return FKtemplate;
	}
	else /* the next `if' */
#endif
	if (isctrl(c)) {
		if (c == ESC)
			return "ESC";

		CTLtemplate[2] = c ^ '@';
		return CTLtemplate;
	}
	else if (c == ' ')
		return "SPC";

	CTLtemplate[2] = c;
	return &CTLtemplate[2];
}

private struct {
	File	*sp;
	int	base;
	int	width;
	char	zeropad;
	char	leftadj;
} curiop;

private void puts __(( const char *_(str) ));
private void
puts(str)
const char	*str;
{
	register File		*sp = curiop.sp;
	register const char	*s;
	register int		w;

	if ((s = str) == NULL)
#if pyr
		s = NullStr;
#else
		s = "(null)";
#endif
	if ((w = curiop.width) > 0) {
		w -= strlen(s);
		if (!curiop.leftadj)
			while (--w >= 0)
				putc(' ', sp);
	}
	while (*s)
		putc(*s++, sp);
	while (--w >= 0)
		putc(' ', sp);
}

private void putld __(( long _(d) ));
private void
putld(d)
register long	d;
{
	static const char	Digits[] = "0123456789abcdef";
	char			buf[100];
	register char		*s = &buf[sizeof buf];
	register int		sign = 0;
	register int		w = curiop.width;

	if (d < 0) {
		d = -d;
		--w;
		sign++;
	}
	*--s = '\0';
	do {
		--w;
		*--s = Digits[d % curiop.base];
	} while (d /= curiop.base);

	if (curiop.zeropad) {
		while (--w >= 0)
			*--s = '0';
	}
	if (sign)
		*--s = '-';

	puts(s);
}

private void doformat __(( File *_(sp), const char *_(fmt), va_list _(ap) ));
private void
doformat(sp, fmt, ap)
File			*sp;
register const char	*fmt;
va_register va_list	ap;
{
	register int	c;
	register long	value;

	curiop.sp = sp;
	curiop.base = 10;

	while (c = *fmt++) {
		if (c != '%') {
			putc(c, sp);
			continue;
		}

		curiop.zeropad = curiop.leftadj = curiop.width = 0;
		c = *fmt++;
		if (c == '-') {
			curiop.leftadj++;
			c = *fmt++;
		}
		if (c == '0') {
			curiop.zeropad++;
			c = *fmt++;
		}
		while (isdigit(c)) {
			curiop.width = curiop.width * 10 + (c - '0');
			c = *fmt++;
		}
		if (c == '*') {
			curiop.width = va_arg(ap, int);
			c = *fmt++;
		}
	reswitch:
		/* At this point, fmt points at one past the format letter. */
		/* {JOVE-specific formats are marked with !<comment>!} */
		switch (c) {
		case '%':
			putc(c, sp);
			continue;

		case 'X':			/* curiop.base = 16; */
			curiop.base -= 8 - 16;
			/* fall into... */
		case 'O':			/* curiop.base = 8; */
			curiop.base -= 10 - 8;
			/* fall into... */
		case 'D':
			value = va_arg(ap, long);
			break;

		case 'b':	/* !insert current buffer name! */
			puts(va_arg(ap, Buffer *)->b_name);
			continue;

		case 'c':
		    {
			static char	ch[2] ZERO;

			ch[0] = va_arg(ap, int);
			puts(ch);
			continue;
		    }
		case 'x':
			curiop.base -= 8 - 16;
			/* fall into... */
		case 'o':
			curiop.base -= 10 - 8;
			/* fall into... */
		case 'd':
			value = va_arg(ap, int);
			break;

		case 'f':	/* !insert current command name! */
			puts(LastCmd->Name);
			continue;

		case 'l':
			c = toupper(*fmt++);
			goto reswitch;

		case 'n':	/* !pluralis if last numeric printed not 1! */
			if (value != 1) {
				c = 's';
				putc(c, sp);
			}
			continue;

		case 'N':	/* !insert numeric argument (if any)! */
#ifndef TINY
			if (exp_p && True(ShowExp)) {
				puts(((c = exp) < 0) ? (c = -c, "-") : "+");
				if (exp_p == YES) {
					value = c;
					break;
				}
			}
#endif
			continue;

		case 'p':	/* !insert symbolic keystroke! */
		    	puts(PPchar(va_arg(ap, int)));
		    	continue;
#ifndef BAD_VARARGS
		case 'r':	/* !switch to remote arglist! */
			/* [TRH]
			 * remote -- take format + args from somewhere else
			 * remainder of current format is ignored.
			 * usage: "%r", fmt, ap
			 * (undocumented but rather handy Unix 7th Ed. feature)
			 */
			fmt = va_arg(ap, char *);
			ap = va_arg(ap, va_list);
			continue;
#endif
		case 's':
			puts(va_arg(ap, char *));
			continue;

		default:
		   {
			static char	BadFmt[] = "<%x?>";

			BadFmt[2] = c;
			puts(BadFmt);
			continue;
		   }
		}

		/* we only get here with numeric conversions */

		putld(value);
		curiop.base = 10;
	}
}

int
format(buf, len, fmt, ap)
char		*buf;
const char 	*fmt;
va_list		ap;
{
	File		strbuf;
	register File	*sp = &strbuf;

 	sp->f_ptr /*- = sp->f_base -*/ = buf;
    /*-	sp->f_fd = -1; -*/	/* Not legit for files */
	sp->f_cnt = len - 1;	/* [TRH] assure zero-termination */
	sp->f_flags = F_STRING;
    /*-	sp->f_bufsize = len; -*/

	doformat(sp, fmt, ap);
	*sp->f_ptr = '\0';

	return (sp->f_ptr - buf);
}

/* VARARGS1 */

char *
DEFVARG(sprint, (const char *fmt, ...),
		(fmt, va_alist)
	const char *fmt;)
{
	va_register va_list	ap;
	static char		line[sizeof mesgbuf];

	va_begin(ap, fmt);
	format(line, sizeof line, fmt, ap);
	va_end(ap);
	return line;
}

/* VARARGS1 */

DEFVARG(printf, (const char *fmt, ...),
		(fmt, va_alist)
	const char *fmt;)
{
	va_register va_list	ap;

	va_begin(ap, fmt);
	doformat(stdout, fmt, ap);
	va_end(ap);
}

/* VARARGS2 */

DEFVARG(fprintf, (File *fp, const char *fmt, ...),
		  (fp, fmt, va_alist)
	File		*fp;
	const char	*fmt;)
{
	va_register va_list	ap;

	va_begin(ap, fmt);
	doformat(fp, fmt, ap);
	va_end(ap);
}

/* VARARGS2 */

DEFVARG(sprintf, (char *str, const char *fmt, ...),
		 (str, fmt, va_alist)
	char		*str;
	const char	*fmt;)
{
	va_register va_list	ap;
	register int		n;

	va_begin(ap, fmt);
	n = format(str, LBSIZE, fmt, ap);
	va_end(ap);
	return n;
}

/* VARARGS1 */

void
DEFVARG(s_mess, (const char *fmt, ...),
		(fmt, va_alist)
	const char *fmt;)
{
	va_register va_list	ap;
	register char		*mp;

	if (InJoverc)
		return;

	mp = mesgbuf;
	va_begin(ap, fmt);
	format(mp, sizeof mesgbuf, fmt, ap);
	va_end(ap);
	message(mp);
}

/* VARARGS1 */

void
DEFVARG(f_mess, (const char *fmt, ...),
		(fmt, va_alist)
	const char	*fmt;)
{
	va_register va_list	ap;

	va_begin(ap, fmt);
	format(mesgbuf, sizeof mesgbuf, fmt, ap);
	va_end(ap);
	DrawMesg(NO);
	updmesg();	/* Still needs updating (for convenience) */
}

/* VARARGS1 */

void
DEFVARG(add_mess, (const char *fmt, ...),
		  (fmt, va_alist)
	const char	*fmt;)
{
	va_register va_list	ap;
	register char		*mp;

	if (InJoverc)
		return;

	for (mp = mesgbuf; *mp++; ) ;
	--mp;
	va_begin(ap, fmt);
	format(mp, (int)(&mesgbuf[sizeof mesgbuf] - mp), fmt, ap);
	va_end(ap);
	updmesg();
}

/*======================================================================
 * $Log: fmt.c,v $
 * Revision 14.30.0.10  1994/04/22  18:24:23  tom
 * (doformat,sprint,printf,fprintf,sprintf,s_mess,f_mess,add_mess):
 *  use `va_register va_list'.
 *
 * Revision 14.30  1993/02/06  00:48:31  tom
 * cleanup whitespace; some random optimizations; add %N directive.
 *
 * Revision 14.26  1992/08/26  23:56:53  tom
 * add RCS directives.
 *
 */
