#include <u.h>
#include <libc.h>
#include <stdarg.h>
#include <assert.h>

/*
 * $Id: doprint.c,v 1.7 1998/11/10 22:35:28 mhw Exp $
 */

enum {
	FL_PLUS =	1<<0,
	FL_MINUS =	1<<1,
	FL_HASH =	1<<2,
	FL_LONG =	1<<3,
	FL_SHORT =	1<<4,
	FL_UNSIGNED =	1<<5,
	FL_LONGLONG =	1<<6
};

enum {
	F_DIG = 1,
	F_STAR,
	F_PER,
	F_FLG,
	F_DOT,
	F_C,
	F_S,
#if defined(PRINT_RUNES)
	F_CR,
	F_SR,
#endif
	F_ERR,
	F_BI
#if !defined(PRINT_RUNES)
		,
	F_CR = 0,
	F_SR = 0
#endif
};

static char fmttab[256] = {
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
/* !"#$%&'*/	0,	0,	0,	F_FLG,	0,	F_PER,	0,	0,
/*()*+,-./ */	0,	0,	F_STAR,	F_FLG,	0,	F_FLG,	F_DOT,	0,
/*01234567*/	F_DIG,	F_DIG,	F_DIG,	F_DIG,	F_DIG,	F_DIG,	F_DIG,	F_DIG,
/*89:;<=>?*/	F_DIG,	F_DIG,	0,	0,	0,	0,	0,	0,
/*@ABCDEFG*/	0,	0,	0,	F_CR,	0,	0,	0,	0,
/*HIJKLMNO*/	0,	0,	0,	0,	0,	0,	0,	0,
/*PQRSTUVW*/	0,	0,	0,	F_SR,	0,	0,	0,	0,
/*XYZ[\]^_*/	F_BI+0,	0,	0,	0,	0,	0,	0,	0,
/*`abcdefg*/	0,	0,	0,	F_C,	F_BI+0,	0,	0,	0,
/*hijklmno*/	F_FLG,	0,	0,	0,	F_FLG,	0,	0,	F_BI+0,
/*pqrstuvw*/	0,	0,	F_ERR,	F_S,	0,	F_FLG,	0,	0,
/*xyz{|}~ */	F_BI+0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
		0,	0,	0,	0,	0,	0,	0,	0,
};

static int (*fmtfns[16])(void *, Fconv *) = {
	numbconv,
};

int
fmtinstall(int c, int (*f)(void *, Fconv *))
{
	int i;

	if (c < 0 || c >= 256)
		return -1;
	for (i = 0; i < 16; ++i)
		if (fmtfns[i] == 0 || fmtfns[i] == f)
			break;
	if (i == 16)
		return -1;
	fmtfns[i] = f;
	fmttab[c] = F_BI+i;
	return 0;
}

char *
doprint(char *s, char *es, char *format, void *argp)
{
	va_list ap = argp;
	int c;
	int percent = 0;
	int dot = 0;
	Fconv f;

	while (s < es && (c = *format++) != 0) {
		if (!percent && c != '%') {
			*s++ = c;
			continue;
		}
		switch (fmttab[c]) {
		case 0:		/* non-special */
			*s++ = c;
			percent = 0;
			break;
		case F_DIG:		/* 0-9 */
			if (dot == 0)
				f.f1 = f.f1*10+(c-'0');
			else if (dot == 1)
				f.f2 = f.f2*10+(c-'0');
			else {
				*s++ = c;
				percent = 0;
			}
			break;
		case F_STAR:		/* * */
			if (dot == 0)
				f.f1 = abs(va_arg(ap, int));
			else
				f.f2 = abs(va_arg(ap, int));
			break;
		case F_PER:		/* % */
			if (percent) {
				*s++ = '%';
				percent = 0;
				break;
			}
			percent = 1;
			dot = 0;
			f.out = s;
			f.eout = es;
			f.f1 = 0;
			f.f2 = -1;
			f.f3 = 0;
			break;
		case F_FLG:		/* flags */
			switch (c) {
			case '+':	f.f3 |= FL_PLUS;	break;
			case '-':	f.f3 |= FL_MINUS;	break;
			case '#':	f.f3 |= FL_HASH;	break;
			case 'h':	f.f3 |= FL_SHORT;	break;
			case 'u':	f.f3 |= FL_UNSIGNED;	break;
			case 'l':
				if ((f.f3 & (FL_LONG|FL_LONGLONG)) == FL_LONG)
					f.f3 |= FL_LONGLONG;
				else 
					f.f3 |= FL_LONG;
				break;
			}
			break;
		case F_DOT:		/* . */
			++dot;
			f.f2 = 0;
			break;
		case F_C:		/* c */
			{
				char arg[2];
				arg[0] = va_arg(ap, int);
				arg[1] = 0;
				strconv(arg, &f);
				s = f.out;
				percent = 0;
			}
			break;
		case F_S:		/* s */
			{
				char *arg = va_arg(ap, char *);
				strconv(arg, &f);
				s = f.out;
				percent = 0;
			}
			break;
#if defined(PRINT_RUNES)
		case F_CR:	/* C */
			{
				Rune r = va_arg(ap, int);
				char arg[UTFmax+1];
				int l = runetochar(arg, &r);
				arg[l] = 0;
				strconv(arg, &f);
				s = f.out;
				percent = 0;
			}
			break;
		case F_SR:	/* S */
			{
				Rune *arg = va_arg(ap, Rune *);
				Strconv(arg, &f);
				s = f.out;
				percent = 0;
			}
			break;
#endif
		case F_ERR:		/* r */
			strconv(strerror(errno), &f);
			s = f.out;
			percent = 0;
			break;
		default:
			{
				int fmt, r;

				f.chr = c;
				fmt = fmttab[c];
				assert(fmt >= F_BI);
				assert(fmtfns[fmt-F_BI] != 0);
				r = fmtfns[fmt-F_BI](ap, &f);
				if (r < 0)
					f.f3 |= ~r;
				else {
					ap += r;
					s = f.out;
					percent = 0;
				}
			}
			break;
		}
	}
	return s;
}

void
strconv(char *s, Fconv *fp)
{
	int l, i;
	int pad = (fp->f1 != 0);

	if (pad || fp->f2 > -1) {
		char *s2 = s;
		for (l = 0; l < fp->f2 && *s2 != 0; ++l)
			++s2;
		if (pad && (fp->f3 & FL_MINUS) == 0)
			for (i = fp->f1-l; fp->out < fp->eout && i > 0; --i)
				*fp->out++ = ' ';
		for (i = 0; i < l && fp->out < fp->eout; ++i)
			*fp->out++ = *s++;
		if (pad && (fp->f3 & FL_MINUS) != 0)
			for (i = fp->f1-l; fp->out < fp->eout && i > 0; --i)
				*fp->out++ = ' ';
	} else {
		while (fp->out < fp->eout && *s != 0)
			*fp->out++ = *s++;
	}
}

#if defined(PRINT_RUNES)
void
Strconv(Rune *s, Fconv *fp)
{
	char temp[UTFmax];
	int l, i, j;
	int pad = (fp->f1 != 0);

	if (pad || fp->f2 > -1) {
		Rune *s2 = s;
		for (l = 0; l < fp->f2 && *s2 != 0; ++l)
			++s2;
		if (pad && (fp->f3 & FL_MINUS) == 0)
			for (i = fp->f1-l; fp->out < fp->eout && i > 0; --i)
				*fp->out++ = ' ';
		for (i = 0; i < l && fp->out < fp->eout-UTFmax+1; ++i)
			fp->out += runetochar(fp->out, s++);
		while (i < l && fp->out+(i = runetochar(temp, s)) <= fp->eout)
			for (j = 0; j < i; ++j)
				*fp->out++ = temp[j];
		if (pad && (fp->f3 & FL_MINUS) != 0)
			for (i = fp->f1-l; fp->out < fp->eout && i > 0; --i)
				*fp->out++ = ' ';
	} else {
		while (fp->out <= fp->eout-UTFmax && *s != 0)
			fp->out += runetochar(fp->out, s++);
		while (fp->out+(i = runetochar(temp, s)) <= fp->eout && *s != 0)
			for (j = 0; j < i; ++j)
				*fp->out++ = temp[j];
	}
}
#endif

int
numbconv(void *o, Fconv *fp)
{
	static char digits[16] = "0123456789abcdef";
	char buf[80];	/* arbitrary limit. enough digits, but no limit on f2 */
	char *s = buf+sizeof(buf)-1;
	char sign = 0;
	va_list ap = o;
	int uc = 0;
	unsigned long u;

	if (fp->f3 & FL_UNSIGNED) {
		if (fp->f3 & FL_LONG)
			u = va_arg(ap, unsigned long);
		else
			u = va_arg(ap, unsigned int);
	} else {
		long sl;

		if (fp->f3 & FL_LONG)
			sl = va_arg(ap, long);
		else
			sl = va_arg(ap, int);
		if (sl < 0) {
			sign = '-';
			u = -sl;
		} else {
			u = sl;
		}
	}
	*s = 0;
	switch (fp->chr) {
	case 'd':
		do {
			*--s = digits[u%10];
			u /= 10;
		} while (u != 0);
		while (s > buf && buf+sizeof(buf)-s < fp->f2)
			*--s = '0';
		if (sign)
			*--s = sign;
		break;
	case 'o':
		do {
			*--s = digits[u%8];
			u /= 8;
		} while (u != 0);
		while (s > buf && buf+sizeof(buf)-s < fp->f2)
			*--s = '0';
		if ((fp->f3 & FL_HASH) != 0 && *s != '0')
			*--s = '0';
		if (sign)
			*--s = sign;
		break;
	case 'X':
		uc = 1;
		/* FALLTHROUGH */
	case 'x':
		do {
			char digit = digits[u%16];
			if (uc && digit >= 'a')
				digit -= 'a'-'A';
			*--s = digit;
			u /= 16;
		} while (u != 0);
		while (s > buf && buf+sizeof(buf)-s < fp->f2)
			*--s = '0';
		if (s > buf+2 && (fp->f3 & FL_HASH) != 0) {
			*--s = uc? 'X' : 'x';
			*--s = '0';
		}
		if (sign)
			*--s = sign;
		break;
	}
	strconv(s, fp);
	return ap-(va_list)o;
}
