/* Copyright (c) 2002-2006 Lucent Technologies; see LICENSE */
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <fmt.h>
#include <assert.h>
#include "plan9.h"
#include "fmt.h"
#include "fmtdef.h"
#include "nan.h"

enum
{
	FDIGIT	= 30,
	FDEFLT	= 6,
	NSIGNIF	= 17
};

/*
 * first few powers of 10, enough for about 1/2 of the
 * total space for doubles.
 */
static double pows10[] =
{
	  1e0,   1e1,   1e2,   1e3,   1e4,   1e5,   1e6,   1e7,   1e8,   1e9,
	 1e10,  1e11,  1e12,  1e13,  1e14,  1e15,  1e16,  1e17,  1e18,  1e19,
	 1e20,  1e21,  1e22,  1e23,  1e24,  1e25,  1e26,  1e27,  1e28,  1e29,
	 1e30,  1e31,  1e32,  1e33,  1e34,  1e35,  1e36,  1e37,  1e38,  1e39,
	 1e40,  1e41,  1e42,  1e43,  1e44,  1e45,  1e46,  1e47,  1e48,  1e49,
	 1e50,  1e51,  1e52,  1e53,  1e54,  1e55,  1e56,  1e57,  1e58,  1e59,
	 1e60,  1e61,  1e62,  1e63,  1e64,  1e65,  1e66,  1e67,  1e68,  1e69,
	 1e70,  1e71,  1e72,  1e73,  1e74,  1e75,  1e76,  1e77,  1e78,  1e79,
	 1e80,  1e81,  1e82,  1e83,  1e84,  1e85,  1e86,  1e87,  1e88,  1e89,
	 1e90,  1e91,  1e92,  1e93,  1e94,  1e95,  1e96,  1e97,  1e98,  1e99,
	1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
	1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
	1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
	1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
	1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
	1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
};
#define	npows10 ((int)(sizeof(pows10)/sizeof(pows10[0])))
#define	pow10(x)  fmtpow10(x)

static double
pow10(int n)
{
	double d;
	int neg;

	neg = 0;
	if(n < 0){
		neg = 1;
		n = -n;
	}

	if(n < npows10)
		d = pows10[n];
	else{
		d = pows10[npows10-1];
		for(;;){
			n -= npows10 - 1;
			if(n < npows10){
				d *= pows10[n];
				break;
			}
			d *= pows10[npows10 - 1];
		}
	}
	if(neg)
		return 1./d;
	return d;
}

/*
 * add 1 to the decimal integer string a of length n.
 * if 99999 overflows into 10000, return 1 to tell caller
 * to move the virtual decimal point.
 */
static int
xadd1(char *a, int n)
{
	char *b;
	int c;

	if(n < 0 || n > NSIGNIF)
		return 0;
	for(b = a+n-1; b >= a; b--) {
		c = *b + 1;
		if(c <= '9') {
			*b = c;
			return 0;
		}
		*b = '0';
	}
	/*
	 * need to overflow adding digit.
	 * shift number down and insert 1 at beginning.
	 * decimal is known to be 0s or we wouldn't
	 * have gotten this far.  (e.g., 99999+1 => 00000)
	 */
	a[0] = '1';
	return 1;
}

/*
 * subtract 1 from the decimal integer string a.
 * if 10000 underflows into 09999, make it 99999
 * and return 1 to tell caller to move the virtual
 * decimal point.  this way, xsub1 is inverse of xadd1.
 */
static int
xsub1(char *a, int n)
{
	char *b;
	int c;

	if(n < 0 || n > NSIGNIF)
		return 0;
	for(b = a+n-1; b >= a; b--) {
		c = *b - 1;
		if(c >= '0') {
			if(c == '0' && b == a) {
				/*
				 * just zeroed the top digit; shift everyone up.
				 * decimal is known to be 9s or we wouldn't
				 * have gotten this far.  (e.g., 10000-1 => 09999)
				 */
				*b = '9';
				return 1;
			}
			*b = c;
			return 0;
		}
		*b = '9';
	}
	/*
	 * can't get here.  the number a is always normalized
	 * so that it has a nonzero first digit.
	 */
	abort();
}

/*
 * format exponent like sprintf(p, "e%+02d", e)
 */
static void
xfmtexp(char *p, int e, int ucase)
{
	char se[9];
	int i;

	*p++ = ucase ? 'E' : 'e';
	if(e < 0) {
		*p++ = '-';
		e = -e;
	} else
		*p++ = '+';
	i = 0;
	while(e) {
		se[i++] = e % 10 + '0';
		e /= 10;
	}
	while(i < 2)
		se[i++] = '0';
	while(i > 0)
		*p++ = se[--i];
	*p++ = '\0';
}

/*
 * compute decimal integer m, exp such that:
 *	f = m*10^exp
 *	m is as short as possible with losing exactness
 * assumes special cases (NaN, +Inf, -Inf) have been handled.
 */
static void
xdtoa(double f, char *s, int *exp, int *neg, int *ns)
{
	int c, d, e2, e, ee, i, ndigit, oerrno;
	char tmp[NSIGNIF+10];
	double g;

	oerrno = errno; /* in case strtod smashes errno */

	/*
	 * make f non-negative.
	 */
	*neg = 0;
	if(f < 0) {
		f = -f;
		*neg = 1;
	}

	/*
	 * must handle zero specially.
	 */
	if(f == 0){
		*exp = 0;
		s[0] = '0';
		s[1] = '\0';
		*ns = 1;
		return;
	}

	/*
	 * find g,e such that f = g*10^e.
	 * guess 10-exponent using 2-exponent, then fine tune.
	 */
	frexp(f, &e2);
	e = (int)(e2 * .301029995664);
	g = f * pow10(-e);
	while(g < 1) {
		e--;
		g = f * pow10(-e);
	}
	while(g >= 10) {
		e++;
		g = f * pow10(-e);
	}

	/*
	 * convert NSIGNIF digits as a first approximation.
	 */
	for(i=0; i<NSIGNIF; i++) {
		d = (int)g;
		s[i] = d+'0';
		g = (g-d) * 10;
	}
	s[i] = 0;

	/*
	 * adjust e because s is 314159... not 3.14159...
	 */
	e -= NSIGNIF-1;
	xfmtexp(s+NSIGNIF, e, 0);

	/*
	 * adjust conversion until strtod(s) == f exactly.
	 */
	for(i=0; i<10; i++) {
		g = fmtstrtod(s, nil);
		if(f > g) {
			if(xadd1(s, NSIGNIF)) {
				/* gained a digit */
				e--;
				xfmtexp(s+NSIGNIF, e, 0);
			}
			continue;
		}
		if(f < g) {
			if(xsub1(s, NSIGNIF)) {
				/* lost a digit */
				e++;
				xfmtexp(s+NSIGNIF, e, 0);
			}
			continue;
		}
		break;
	}

	/*
	 * play with the decimal to try to simplify.
	 */

	/*
	 * bump last few digits up to 9 if we can
	 */
	for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) {
		c = s[i];
		if(c != '9') {
			s[i] = '9';
			g = fmtstrtod(s, nil);
			if(g != f) {
				s[i] = c;
				break;
			}
		}
	}

	/*
	 * add 1 in hopes of turning 9s to 0s
	 */
	if(s[NSIGNIF-1] == '9') {
		strcpy(tmp, s);
		ee = e;
		if(xadd1(tmp, NSIGNIF)) {
			ee--;
			xfmtexp(tmp+NSIGNIF, ee, 0);
		}
		g = fmtstrtod(tmp, nil);
		if(g == f) {
			strcpy(s, tmp);
			e = ee;
		}
	}

	/*
	 * bump last few digits down to 0 as we can.
	 */
	for(i=NSIGNIF-1; i>=NSIGNIF-3; i--) {
		c = s[i];
		if(c != '0') {
			s[i] = '0';
			g = fmtstrtod(s, nil);
			if(g != f) {
				s[i] = c;
				break;
			}
		}
	}

	/*
	 * remove trailing zeros.
	 */
	ndigit = NSIGNIF;
	while(ndigit > 1 && s[ndigit-1] == '0'){
		e++;
		--ndigit;
	}
	s[ndigit] = 0;
	*exp = e;
	*ns = ndigit;
	errno = oerrno;
}

#ifdef PLAN9PORT
static char *special[] = { "NaN", "NaN", "+Inf", "+Inf", "-Inf", "-Inf" };
#else
static char *special[] = { "nan", "NAN", "inf", "INF", "-inf", "-INF" };
#endif

int
__efgfmt(Fmt *fmt)
{
	char buf[NSIGNIF+10], *dot, *digits, *p, *s, suf[10], *t;
	double f;
	int c, chr, dotwid, e, exp, fl, ndigits, neg, newndigits;
	int pad, point, prec, realchr, sign, sufwid, ucase, wid, z1, z2;
	Rune r, *rs, *rt;

	if(fmt->flags&FmtLong)
		f = va_arg(fmt->args, long double);
	else
		f = va_arg(fmt->args, double);

	/*
	 * extract formatting flags
	 */
	fl = fmt->flags;
	fmt->flags = 0;
	prec = FDEFLT;
	if(fl & FmtPrec)
		prec = fmt->prec;
	chr = fmt->r;
	ucase = 0;
	switch(chr) {
	case 'A':
	case 'E':
	case 'F':
	case 'G':
		chr += 'a'-'A';
		ucase = 1;
		break;
	}

	/*
	 * pick off special numbers.
	 */
	if(__isNaN(f)) {
		s = special[0+ucase];
	special:
		fmt->flags = fl & (FmtWidth|FmtLeft);
		return __fmtcpy(fmt, s, strlen(s), strlen(s));
	}
	if(__isInf(f, 1)) {
		s = special[2+ucase];
		goto special;
	}
	if(__isInf(f, -1)) {
		s = special[4+ucase];
		goto special;
	}

	/*
	 * get exact representation.
	 */
	digits = buf;
	xdtoa(f, digits, &exp, &neg, &ndigits);

	/*
	 * get locale's decimal point.
	 */
	dot = fmt->decimal;
	if(dot == nil)
		dot = ".";
	dotwid = utflen(dot);

	/*
	 * now the formatting fun begins.
	 * compute parameters for actual fmt:
	 *
	 *	pad: number of spaces to insert before/after field.
	 *	z1: number of zeros to insert before digits
	 *	z2: number of zeros to insert after digits
	 *	point: number of digits to print before decimal point
	 *	ndigits: number of digits to use from digits[]
	 *	suf: trailing suffix, like "e-5"
	 */
	realchr = chr;
	switch(chr){
	case 'g':
		/*
		 * convert to at most prec significant digits. (prec=0 means 1)
		 */
		if(prec == 0)
			prec = 1;
		if(ndigits > prec) {
			if(digits[prec] >= '5' && xadd1(digits, prec))
				exp++;
			exp += ndigits-prec;
			ndigits = prec;
		}

		/*
		 * extra rules for %g (implemented below):
		 *	trailing zeros removed after decimal unless FmtSharp.
		 *	decimal point only if digit follows.
		 */

		/* fall through to %e */
	default:
	case 'e':
		/*
		 * one significant digit before decimal, no leading zeros.
		 */
		point = 1;
		z1 = 0;

		/*
		 * decimal point is after ndigits digits right now.
		 * slide to be after first.
		 */
		e  = exp + (ndigits-1);

		/*
		 * if this is %g, check exponent and convert prec
		 */
		if(realchr == 'g') {
			if(-4 <= e && e < prec)
				goto casef;
			prec--;	/* one digit before decimal; rest after */
		}

		/*
		 * compute trailing zero padding or truncate digits.
		 */
		if(1+prec >= ndigits)
			z2 = 1+prec - ndigits;
		else {
			/*
			 * truncate digits
			 */
			assert(realchr != 'g');
			newndigits = 1+prec;
			if(digits[newndigits] >= '5' && xadd1(digits, newndigits)) {
				/*
				 * had 999e4, now have 100e5
				 */
				e++;
			}
			ndigits = newndigits;
			z2 = 0;
		}
		xfmtexp(suf, e, ucase);
		sufwid = strlen(suf);
		break;

	casef:
	case 'f':
		/*
		 * determine where digits go with respect to decimal point
		 */
		if(ndigits+exp > 0) {
			point = ndigits+exp;
			z1 = 0;
		} else {
			point = 1;
			z1 = 1 + -(ndigits+exp);
		}

		/*
		 * %g specifies prec = number of significant digits
		 * convert to number of digits after decimal point
		 */
		if(realchr == 'g')
			prec += z1 - point;

		/*
		 * compute trailing zero padding or truncate digits.
		 */
		if(point+prec >= z1+ndigits)
			z2 = point+prec - (z1+ndigits);
		else {
			/*
			 * truncate digits
			 */
			assert(realchr != 'g');
			newndigits = point+prec - z1;
			if(newndigits < 0) {
				z1 += newndigits;
				newndigits = 0;
			} else if(newndigits == 0) {
				/* perhaps round up */
				if(digits[0] >= '5'){
					digits[0] = '1';
					newndigits = 1;
					goto newdigit;
				}
			} else if(digits[newndigits] >= '5' && xadd1(digits, newndigits)) {
				/*
				 * digits was 999, is now 100; make it 1000
				 */
				digits[newndigits++] = '0';
			newdigit:
				/*
				 * account for new digit
				 */
				if(z1)	/* 0.099 => 0.100 or 0.99 => 1.00*/
					z1--;
				else	/* 9.99 => 10.00 */
					point++;
			}
			z2 = 0;
			ndigits = newndigits;
		}
		sufwid = 0;
		break;
	}

	/*
	 * if %g is given without FmtSharp, remove trailing zeros.
	 * must do after truncation, so that e.g. print %.3g 1.001
	 * produces 1, not 1.00.  sorry, but them's the rules.
	 */
	if(realchr == 'g' && !(fl & FmtSharp)) {
		if(z1+ndigits+z2 >= point) {
			if(z1+ndigits < point)
				z2 = point - (z1+ndigits);
			else{
				z2 = 0;
				while(z1+ndigits > point && digits[ndigits-1] == '0')
					ndigits--;
			}
		}
	}

	/*
	 * compute width of all digits and decimal point and suffix if any
	 */
	wid = z1+ndigits+z2;
	if(wid > point)
		wid += dotwid;
	else if(wid == point){
		if(fl & FmtSharp)
			wid += dotwid;
		else
			point++;	/* do not print any decimal point */
	}
	wid += sufwid;

	/*
	 * determine sign
	 */
	sign = 0;
	if(neg)
		sign = '-';
	else if(fl & FmtSign)
		sign = '+';
	else if(fl & FmtSpace)
		sign = ' ';
	if(sign)
		wid++;

	/*
	 * compute padding
	 */
	pad = 0;
	if((fl & FmtWidth) && fmt->width > wid)
		pad = fmt->width - wid;
	if(pad && !(fl & FmtLeft) && (fl & FmtZero)){
		z1 += pad;
		point += pad;
		pad = 0;
	}

	/*
	 * format the actual field.  too bad about doing this twice.
	 */
	if(fmt->runes){
		if(pad && !(fl & FmtLeft) && __rfmtpad(fmt, pad) < 0)
			return -1;
		rt = (Rune*)fmt->to;
		rs = (Rune*)fmt->stop;
		if(sign)
			FMTRCHAR(fmt, rt, rs, sign);
		while(z1>0 || ndigits>0 || z2>0) {
			if(z1 > 0){
				z1--;
				c = '0';
			}else if(ndigits > 0){
				ndigits--;
				c = *digits++;
			}else{
				z2--;
				c = '0';
			}
			FMTRCHAR(fmt, rt, rs, c);
			if(--point == 0) {
				for(p = dot; *p; ){
					p += chartorune(&r, p);
					FMTRCHAR(fmt, rt, rs, r);
				}
			}
		}
		fmt->nfmt += rt - (Rune*)fmt->to;
		fmt->to = rt;
		if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0)
			return -1;
		if(pad && (fl & FmtLeft) && __rfmtpad(fmt, pad) < 0)
			return -1;
	}else{
		if(pad && !(fl & FmtLeft) && __fmtpad(fmt, pad) < 0)
			return -1;
		t = (char*)fmt->to;
		s = (char*)fmt->stop;
		if(sign)
			FMTCHAR(fmt, t, s, sign);
		while(z1>0 || ndigits>0 || z2>0) {
			if(z1 > 0){
				z1--;
				c = '0';
			}else if(ndigits > 0){
				ndigits--;
				c = *digits++;
			}else{
				z2--;
				c = '0';
			}
			FMTCHAR(fmt, t, s, c);
			if(--point == 0)
				for(p=dot; *p; p++)
					FMTCHAR(fmt, t, s, *p);
		}
		fmt->nfmt += t - (char*)fmt->to;
		fmt->to = t;
		if(sufwid && __fmtcpy(fmt, suf, sufwid, sufwid) < 0)
			return -1;
		if(pad && (fl & FmtLeft) && __fmtpad(fmt, pad) < 0)
			return -1;
	}
	return 0;
}
