#include "os.h"
#include <mp.h>
#include "dat.h"

static int
toencx(mpint *b, char *buf, int len, int (*enc)(char*, int, uchar*, int))
{
	uchar *p;
	int n, rv;

	p = nil;
	n = mptobe(b, nil, 0, &p);
	if(n < 0)
		return -1;
	rv = (*enc)(buf, len, p, n);
	free(p);
	return rv;
}

static int
topow2(mpint *b, char *buf, int len, int s)
{
	mpdigit *p, x;
	int i, j, sn;
	char *out, *eout;

	if(len < 1)
		return -1;

	sn = 1<<s;
	out = buf;
	eout = buf+len;
	for(p = &b->p[b->top-1]; p >= b->p; p--){
		x = *p;
		for(i = Dbits-s; i >= 0; i -= s){
			j = x >> i & sn - 1;
			if(j != 0 || out != buf){
				if(out >= eout)
					return -1;
				*out++ = enc16chr(j);
			}
		}
	}
	if(out == buf)
		*out++ = '0';
	if(out >= eout)
		return -1;
	*out = 0;
	return 0;
}

static char*
modbillion(int rem, ulong r, char *out, char *buf)
{
	ulong rr;
	int i;

	for(i = 0; i < 9; i++){
		rr = r%10;
		r /= 10;
		if(out <= buf)
			return nil;
		*--out = '0' + rr;
		if(rem == 0 && r == 0)
			break;
	}
	return out;
}

static int
to10(mpint *b, char *buf, int len)
{
	mpint *d, *r, *billion;
	char *out;

	if(len < 1)
		return -1;

	d = mpcopy(b);
	d->flags &= ~MPtimesafe;
	mpnorm(d);
	r = mpnew(0);
	billion = uitomp(1000000000, nil);
	out = buf+len;
	*--out = 0;
	do {
		mpdiv(d, billion, d, r);
		out = modbillion(d->top, r->p[0], out, buf);
		if(out == nil)
			break;
	} while(d->top != 0);
	mpfree(d);
	mpfree(r);
	mpfree(billion);

	if(out == nil)
		return -1;
	len -= out-buf;
	if(out != buf)
		memmove(buf, out, len);
	return 0;
}

static int
to8(mpint *b, char *buf, int len)
{
	mpdigit x, y;
	char *out;
	int i, j;

	if(len < 2)
		return -1;

	out = buf+len;
	*--out = 0;

	i = j = 0;
	x = y = 0;
	while(j < b->top){
		y = b->p[j++];
		if(i > 0)
			x |= y << i;
		else
			x = y;
		i += Dbits;
		while(i >= 3){
Digout:			i -= 3;
			if(out > buf)
				out--;
			else if(x != 0)
				return -1;
			*out = '0' + (x & 7);
			x = y >> Dbits-i;
		}
	}
	if(i > 0)
		goto Digout;

	while(*out == '0') out++;
	if(*out == '\0')
		*--out = '0';

	len -= out-buf;
	if(out != buf)
		memmove(buf, out, len);
	return 0;
}

int
mpfmt(Fmt *fmt)
{
	mpint *b;
	char *x, *p;
	int base;

	b = va_arg(fmt->args, mpint*);
	if(b == nil)
		return fmtstrcpy(fmt, "*");

	base = fmt->prec;
	if(base == 0)
		base = 16;	/* default */
	fmt->flags &= ~FmtPrec;
	p = mptoa(b, base, nil, 0);
	if(p == nil)
		return fmtstrcpy(fmt, "*");
	else{
		if((fmt->flags & FmtSharp) != 0){
			switch(base){
			case 16:
				x = "0x";
				break;
			case 8:
				x = "0";
				break;
			case 2:
				x = "0b";
				break;
			default:
				x = "";
			}
			if(*p == '-')
				fmtprint(fmt, "-%s%s", x, p + 1);
			else
				fmtprint(fmt, "%s%s", x, p);
		}
		else
			fmtstrcpy(fmt, p);
		free(p);
		return 0;
	}
}

char*
mptoa(mpint *b, int base, char *buf, int len)
{
	char *out;
	int rv, alloced;

	if(base == 0)
		base = 16;	/* default */
	alloced = 0;
	if(buf == nil){
		/* rv <= log₂(base) */
		for(rv=1; (base >> rv) > 1; rv++)
			;
		len = 10 + (b->top*Dbits / rv);
		buf = malloc(len);
		if(buf == nil)
			return nil;
		alloced = 1;
	}

	if(len < 2)
		return nil;

	out = buf;
	if(b->sign < 0){
		*out++ = '-';
		len--;
	}
	switch(base){
	case 64:
		rv = toencx(b, out, len, enc64);
		break;
	case 32:
		rv = toencx(b, out, len, enc32);
		break;
	case 16:
		rv = topow2(b, out, len, 4);
		break;
	case 10:
		rv = to10(b, out, len);
		break;
	case 8:
		rv = to8(b, out, len);
		break;
	case 4:
		rv = topow2(b, out, len, 2);
		break;
	case 2:
		rv = topow2(b, out, len, 1);
		break;
	default:
		abort();
		return nil;
	}
	if(rv < 0){
		if(alloced)
			free(buf);
		return nil;
	}
	return buf;
}
