module std::io;
import std::math;

const char[16] XDIGITS_H = "0123456789ABCDEF";
const char[16] XDIGITS_L = "0123456789abcdef";

faultdef BAD_FORMAT;

fn usz? print_hex_chars(Formatter* f, char[] out, bool uppercase) @inline
{
	char past_10 = (uppercase ? 'A' : 'a') - 10;
	usz len = 0;
	foreach (c : out)
	{
		char digit = c >> 4;
		f.out(digit + (digit < 10 ? '0' : past_10))!;
		len++;
		digit = c & 0xf;
		f.out(digit + (digit < 10 ? '0' : past_10))!;
		len++;
	}
	return len;
}

macro fault Formatter.first_err(&self, fault f)
{
	if (self.first_fault) return self.first_fault;
	self.first_fault = f;
	return f;
}

fn usz? Formatter.adjust(&self, usz len) @local
{
	if (!self.flags.left) return 0;
	return self.pad(' ', self.width, len);
}

fn uint128? int_from_any(any arg, bool *is_neg) @private
{
	switch (arg.type.kindof)
	{
		case FUNC:
		case POINTER:
			*is_neg = false;
			return (uint128)(uptr)*(void**)arg.ptr;
		case DISTINCT:
		case CONST_ENUM:
			return int_from_any(arg.as_inner(), is_neg);
		default:
			break;
	}
	*is_neg = false;
	switch (arg.type)
	{
		case bool:
			return (uint128)*(bool*)arg;
		case ichar:
			int val = *(ichar*)arg;
			return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
		case short:
			int val = *(short*)arg;
			return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
		case int:
			int val = *(int*)arg;
			return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
		case long:
			long val = *(long*)arg;
			return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
		case int128:
			int128 val = *(int128*)arg;
			return (*is_neg = val < 0) ? (~(uint128)val) + 1 : (uint128)val;
		case char:
			return *(char*)arg;
		case ushort:
			return *(ushort*)arg;
		case uint:
			return *(uint*)arg;
		case ulong:
			return *(ulong*)arg;
		case uint128:
			return *(uint128*)arg;
		case float:
			float f = *(float*)arg;
			return (uint128)((*is_neg = f < 0) ? -f : f);
		case double:
			double d = *(double*)arg;
			return (uint128)((*is_neg = d < 0) ? -d : d);
		default:
			return BAD_FORMAT~;
	}
}

fn FloatType? float_from_any(any arg) @private
{
	$if env::F128_SUPPORT:
		if (arg.type == float128.typeid) return (FloatType)*((float128*)arg.ptr);
	$endif
	if (arg.type.kindof == DISTINCT || arg.type.kindof == CONST_ENUM)
	{
		return float_from_any(arg.as_inner());
	}
	switch (arg.type)
	{
		case bool:
			return (FloatType)*(bool*)arg;
		case ichar:
			return *(ichar*)arg;
		case short:
			return *(short*)arg;
		case int:
			return *(int*)arg;
		case long:
			return *(long*)arg;
		case int128:
			return *(int128*)arg;
		case char:
			return *(char*)arg;
		case ushort:
			return *(ushort*)arg;
		case uint:
			return *(uint*)arg;
		case ulong:
			return *(ulong*)arg;
		case uint128:
			return *(uint128*)arg;
		case float:
			return (FloatType)*(float*)arg;
		case double:
			return (FloatType)*(double*)arg;
		default:
			return BAD_FORMAT~;
	}
}


<*
 Read a simple integer value, typically for formatting.

 @param [inout] len_ptr : "the length remaining."
 @param [in] buf : "the buf to read from."
 @param maxlen : "the maximum len that can be read."
 @return "The result of the atoi."
*>
fn uint simple_atoi(char* buf, usz maxlen, usz* len_ptr) @inline @private
{
	uint i = 0;
	usz len = *len_ptr;
	while (len < maxlen)
	{
		char c = buf[len];
		if (!c.is_digit()) break;
		i = i * 10 + c - '0';
		len++;
	}
	*len_ptr = len;
	return i;
}

fn usz? Formatter.out_substr(&self, String str) @private
{
	usz l = conv::utf8_codepoints(str);
	uint prec = self.prec;
	if (self.flags.precision && l < prec) l = prec;
	usz index = 0;
	usz chars = str.len;
	char* ptr = str.ptr;
	while (index < chars)
	{
		char c = ptr[index];
		// Break if we have precision set and we ran out...
		if (c & 0xC0 != 0x80 && self.flags.precision && !prec--) break;
		self.out(c)!;
		index++;
	}
	return index;
}

fn usz? Formatter.pad(&self, char c, isz width, isz len) @inline
{
	isz delta = width - len;
	for (isz i = 0; i < delta; i++) self.out(c)!;
	return max(0, delta);
}

fn char* fmt_u(uint128 x, char* s)
{
	for (; x > ulong.max; x /= 10) *--s = '0' + (char)(x % 10);
	for (ulong y = (ulong)x; y; y /= 10) *--s = '0' + (char)(y % 10);
	return s;
}

fn usz? Formatter.out_chars(&self, char[] s)
{
	foreach (c : s) self.out(c)!;
	return s.len;
}

enum FloatFormatting
{
	FLOAT,
	EXPONENTIAL,
	ADAPTIVE,
	HEX
}

fn usz? Formatter.etoa(&self, double y) => self.floatformat(EXPONENTIAL, y);
fn usz? Formatter.ftoa(&self, double y) => self.floatformat(FLOAT, y);
fn usz? Formatter.gtoa(&self, double y) => self.floatformat(ADAPTIVE, y);
fn usz? Formatter.atoa(&self, double y) => self.floatformat(HEX, y);

fn usz? Formatter.floatformat(&self, FloatFormatting formatting, double y) @private
{
	// This code is heavily based on musl's printf code
	const BUF_SIZE = (math::DOUBLE_MANT_DIG + 28) / 29 + 1
		+ (math::DOUBLE_MAX_EXP + math::DOUBLE_MANT_DIG + 28 + 8) / 9;
	uint[BUF_SIZE] big;
	bool is_neg = false;
	if (math::signbit(y))
	{
		is_neg = true;
		y = -y;
	}
	isz pl = is_neg || self.flags.plus ? 1 : 0;
	// Print inf/nan
	if (!math::is_finite(y))
	{
		usz len;
		// Add padding
		if (!self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
		String s = self.flags.uppercase ? "INF" : "inf";
		if (math::is_nan(y)) s = self.flags.uppercase ? "NAN" : "nan";
		if (pl) len += self.out(is_neg ? '-' : '+')!;
		len += self.out_chars(s)!;
		if (self.flags.left) len += self.pad(' ', self.width, 3 + pl)!;
		return len;
	}
	// Rescale
	int e2;

	y = math::frexp(y, &e2) * 2;
	if (y) e2--;
	char[12] ebuf0;
	char* ebuf = 12 + (char*)&ebuf0;
	char[9 + math::DOUBLE_MANT_DIG / 4] buf_array;
	char* buf = &buf_array;
	isz p = self.flags.precision ? self.prec : -1;
	if (formatting == HEX)
	{
		double round = 8.0;
		// 0x / 0X
		pl += 2;
		if (p > 0 && p < math::DOUBLE_MANT_DIG / 4 - 1)
		{
			int re = math::DOUBLE_MANT_DIG / 4 - 1 - (int)p;
			round *= 1 << (math::DOUBLE_MANT_DIG % 4);
			while (re--) round *= 16;
			if (is_neg)
			{
				y = -y;
				y -= round;
				y += round;
				y = -y;
			}
			else
			{
				y += round;
				y -= round;
			}
		}
		// Reverse print
		char* estr = fmt_u(e2 < 0 ? (int128)-e2 : (int128)e2, ebuf);
		if (estr == ebuf) *--estr = '0';
		*--estr = (e2 < 0 ? '-' : '+');
		*--estr = self.flags.uppercase ? 'P' : 'p';
		char* s = buf;
		char* xdigits = self.flags.uppercase ? &XDIGITS_H : &XDIGITS_L;
		do
		{
			int x = (int)y;
			*s++ = xdigits[x];
			y = 16 * (y - x);
			if (s - buf == 1 && (y || p > 0 || self.flags.hash)) *s++ = '.';
		} while (y);
		isz outlen = s - buf;
		isz explen = ebuf - estr;
		if (p > int.max - 2 - explen - pl) return INTERNAL_BUFFER_EXCEEDED~;
		usz len;
		usz l = p && outlen - 2 < p
				? p + 2 + explen
				: outlen + explen;
		if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
		if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
		len += self.out_chars(self.flags.uppercase ? "0X" : "0x")!;
		if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
		len += self.out_chars(buf[:outlen])!;
		len += self.pad('0', l - outlen - explen, 0)!;
		len += self.out_chars(estr[:explen])!;
		if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;
		return len;
	}
	if (p < 0) p = 6;
	if (y)
	{
		y *= 0x1p28;
		e2 -= 28;
	}

	uint* a, z, r;
	if (e2 < 0)
	{
		a = r = z = &big;
	}
	else
	{
		a = r = z = (uint*)&big + big.len - math::DOUBLE_MANT_DIG - 1;
	}

	do
	{
		uint v = z++[0] = (uint)y;
		y = 1000000000 * (y - v);
	} while (y);

	while (e2 > 0)
	{
		uint carry = 0;
		int sh = math::min(29, e2);
		for (uint* d = z - 1; d >= a; d--)
		{
			ulong x = (ulong)*d << sh + carry;
			*d = (uint)(x % 1000000000);
			carry = (uint)(x / 1000000000);
		}
		if (carry) *--a = carry;
		while (z > a && !z[-1]) z--;
		e2 -= sh;
	}

	while (e2 < 0)
	{
		uint carry = 0;
		uint* b;
		int sh = math::min(9, -e2);
		int need = (int)(1 + (p + math::DOUBLE_MANT_DIG / 3u + 8) / 9);
		for (uint* d = a; d < z; d++)
		{
			// CHECK THIS
			uint rm = *d & ((1 << sh) - 1);
			*d = (*d >> sh) + carry;
			carry = (1000000000 >> sh) * rm;
		}
		if (!a[0]) a++;
		if (carry) z++[0] = carry;
		// Avoid (slow!) computation past requested precision
		b = formatting == FLOAT ? r : a;
		if (z - b > need) z = b + need;
		e2 += sh;
	}

	int e;
	if (a < z)
	{
		for (int i = 10, e = (int)(9 * (r - a)); *a >= i; i *= 10, e++);
	}

	// Perform rounding: j is precision after the radix (possibly neg)
	int j = (int)p;
	if (formatting != FLOAT) j -= e;
	if (formatting == ADAPTIVE && p != 0) j -= 1;

	if (j < 9 * (z - r - 1))
	{
		uint x;
		// We avoid C's broken division of negative numbers
		uint* d = r + 1 + ((j + 9 * math::DOUBLE_MAX_EXP) / 9 - math::DOUBLE_MAX_EXP);
		j += 9 * math::DOUBLE_MAX_EXP;
		j %= 9;
		int i;
		for (i = 10, j++; j < 9; i *= 10, j++);
		x = *d % i;
		// Are there any significant digits past j?
		if (x || (d + 1) != z)
		{
			double round = 2 / math::DOUBLE_EPSILON;
			double small;
			if (((*d / i) & 1) || (i == 1000000000 && d > a && (d[-1] & 1)))
			{
				round += 2;
			}
			switch
			{
				case x < i / 2:
					small = 0x0.8p0;
				case x == i / 2 && d + 1 == z:
					small = 0x1.0p0;
				default:
					small = 0x1.8p0;
			}
			if (pl && is_neg)
			{
				round *= -1;
				small *= -1;
			}
			*d -= x;
			// Decide whether to round by probing round+small
			if (round + small != round)
			{
				*d = *d + i;
				while (*d > 999999999)
				{
					*d-- = 0;
					if (d < a) *--a = 0;
					(*d)++;
				}
				for (i = 10, e = (int)(9 * (r - a)); *a >= i; i *= 10, e++);
			}
		}
		if (z > d + 1) z = d + 1;
	}
	for (; z>a && !z[-1]; z--);

	if (formatting == ADAPTIVE)
	{
		if (!p) p++;
		if (p > e && e >= -4)
		{
			formatting = FLOAT;
			p -= (isz)e + 1;
		}
		else
		{
			formatting = EXPONENTIAL;
			p--;
		}
		if (!self.flags.hash)
		{
			// Count trailing zeros in last place
			if (z > a && z[-1])
			{
				for (int i = 10, j = 0; z[-1] % i == 0; i *= 10, j++);
			}
			else
			{
				j = 9;
			}
			if (formatting == FLOAT)
			{
				p = math::min(p, math::max((isz)0, 9 * (z - r - 1) - j));
			}
			else
			{
				p = math::min(p, math::max((isz)0, 9 * (z - r - 1) + e - j));

			}
		}
	}
	if (p > int.max - 1 - (isz)(p || self.flags.hash)) return INTERNAL_BUFFER_EXCEEDED~;
	int l = (int)(1 + p + (isz)(p || self.flags.hash));
	char* estr @noinit;
	if (formatting == FLOAT)
	{
		if (e > int.max - l) return INTERNAL_BUFFER_EXCEEDED~;
		if (e > 0) l += e;
	}
	else
	{
		estr = fmt_u((uint128)(e < 0 ? -e : e), ebuf);
		while (ebuf - estr < 2) (--estr)[0] = '0';
		*--estr = (e < 0 ? '-' : '+');
		*--estr = self.flags.uppercase ? 'E' : 'e';
		if (ebuf - estr > (isz)int.max - l) return INTERNAL_BUFFER_EXCEEDED~;
		l += (int)(ebuf - estr);
	}
	if (l > int.max - pl) return INTERNAL_BUFFER_EXCEEDED~;
	usz len;
	if (!self.flags.left && !self.flags.zeropad) len += self.pad(' ', self.width, pl + l)!;
	if (is_neg || self.flags.plus) len += self.out(is_neg ? '-' : '+')!;
	if (self.flags.zeropad) len += self.pad('0', self.width, pl + l)!;
	if (formatting == FLOAT)
	{
		if (a > r) a = r;
		uint* d = a;
		for (; d <= r; d++)
		{
			char* s = fmt_u(*d, buf + 9);
			switch
			{
				case d != a:
					while (s > buf) (--s)[0] = '0';
				case s == buf + 9:
					*--s = '0';
			}
			len += self.out_chars(s[:buf + 9 - s])!;
		}
		if (p || self.flags.hash) len += self.out('.')!;
		for (; d < z && p > 0; d++, p -= 9)
		{
			char* s = fmt_u(*d, buf + 9);
			while (s > buf) *--s = '0';
			len += self.out_chars(s[:math::min((isz)9, p)])!;
		}
		len += self.pad('0', p + 9, 9)!;
	}
	else
	{
		if (z <= a) z = a + 1;
		for (uint* d = a; d < z && p >= 0; d++)
		{
			char* s = fmt_u(*d, buf + 9);
			if (s == buf + 9) (--s)[0] = '0';
			if (d != a)
			{
				while (s > buf) (--s)[0] = '0';
			}
			else
			{
				len += self.out(s++[0])!;
				if (p > 0 || self.flags.hash) len += self.out('.')!;
			}
			len += self.out_chars(s[:math::min(buf + 9 - s, p)])!;
			p -= buf + 9 - s;
		}
		len += self.pad('0', p + 18, 18)!;
		len += self.out_chars(estr[:ebuf - estr])!;
	}

	if (self.flags.left) len += self.pad(' ', self.width, pl + l)!;

	return len;
}

const char[201] DIGIT_PAIRS @private =
  "00102030405060708090"
  "01112131415161718191"
  "02122232425262728292"
  "03132333435363738393"
  "04142434445464748494"
  "05152535455565758595"
  "06162636465666768696"
  "07172737475767778797"
  "08182838485868788898"
  "09192939495969798999";


fn usz? Formatter.ntoa(&self, uint128 value, bool negative, uint base) @private
{
	char[PRINTF_NTOA_BUFFER_SIZE] buf @noinit;
	usz len;

	// no hash for 0 values
	if (!value) self.flags.hash = false;

	// write if precision != 0 or value is != 0
	if (!self.flags.precision || value)
	{
		char past_10 = (self.flags.uppercase ? 'A' : 'a') - 10;
		switch (base)
		{
			case 2:
				do
				{
					if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					buf[len++] = '0' + (char)value & 1;
					value >>= 1;
				}
				while (value);
			case 10:
				if (!value)
				{
					if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					buf[len++] = '0';
					break;
				}
				while (value >= 10)
				{
					if (len + 1 >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					char digit = (char)(value % 100);
					buf[len:2] = DIGIT_PAIRS[2 * digit:2];
					len += 2;
					value /= 100;
				}
				if (value > 0)
				{
					if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					buf[len++] = '0' + (char)value;
				}
			case 16:
				do
				{
					if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					char digit = (char)value & 0xF;
					buf[len++] = digit + (digit < 10 ? '0' : past_10);
					value >>= 4;
				}
				while (value);
			case 8:
				do
				{
					if (len >= PRINTF_NTOA_BUFFER_SIZE) return INTERNAL_BUFFER_EXCEEDED~;
					buf[len++] = '0' + (char)value & 0x7;
					value >>= 3;
				}
				while (value);
			default:
				unreachable();
		}
	}
	return self.ntoa_format((String)buf[:PRINTF_NTOA_BUFFER_SIZE], len, negative, base);
}

fn usz? Formatter.ntoa_format(&self, String buf, usz len, bool negative, uint base) @private
{
	// pad leading zeros
	if (!self.flags.left)
	{
		if (self.width && self.flags.zeropad && (negative || self.flags.plus || self.flags.space)) self.width--;
		while (len < self.prec)
		{
			if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			buf[len++] = '0';
		}
		while (self.flags.zeropad && len < self.width)
		{
			if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			buf[len++] = '0';
		}
	}

	// handle hash
	if (self.flags.hash && base != 10)
	{
		if (!self.flags.precision && len && len == self.prec && len == self.width)
		{
			len--;
			if (len) len--;
		}
		if (base != 10)
		{
			if (len + 1 >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			switch (base)
			{
				case 16:
					buf[len++] = self.flags.uppercase ? 'X' : 'x';
				case 8:
					buf[len++] = self.flags.uppercase ? 'O' : 'o';
				case 2:
					buf[len++] = self.flags.uppercase ? 'B' : 'b';
				default:
					unreachable();
			}
			buf[len++] = '0';
		}
	}

	switch (true)
	{
		case negative:
			if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			buf[len++] = '-';
		case self.flags.plus:
			if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			buf[len++] = '+';
		case self.flags.space:
			if (len >= buf.len) return INTERNAL_BUFFER_EXCEEDED~;
			buf[len++] = ' ';
	}
	if (len) self.out_reverse(buf[:len])!;
	return len;
}


fn usz? Formatter.ntoa_any(&self, any arg, uint base) @private
{
	bool is_neg;
	return self.ntoa(int_from_any(arg, &is_neg)!!, is_neg, base) @inline;
}

fn usz? Formatter.out_char(&self, any arg) @private
{
	if (!arg.type.kindof.is_int())
	{
		return self.out_substr("<NOT CHAR>");
	}
	usz len = 1;
	// pre padding
	if (!self.flags.left)
    {
    	len += self.pad(' ', self.width, len)!;
    }

	// char output
	Char32 c = types::any_to_int(arg, uint) ?? 0xFFFD;
	switch (true)
	{
		case c < 0x7f:
			self.out((char)c)!;
		case c < 0x7ff:
			self.out((char)(0xC0 | c >> 6))!;
			self.out((char)(0x80 | (c & 0x3F)))!;
		case c < 0xffff:
			self.out((char)(0xE0 | c >> 12))!;
			self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
			self.out((char)(0x80 | (c & 0x3F)))!;
		default:
			self.out((char)(0xF0 | c >> 18))!;
			self.out((char)(0x80 | (c >> 12 & 0x3F)))!;
			self.out((char)(0x80 | (c >> 6 & 0x3F)))!;
			self.out((char)(0x80 | (c & 0x3F)))!;
	}
	if (self.flags.left)
    {
        len += self.pad(' ', self.width, len)!;
	}
	return len;
}


fn usz? Formatter.out_reverse(&self, char[] buf) @private
{
	usz n;
	usz len = buf.len;
	// pad spaces up to given width
	if (!self.flags.zeropad && !self.flags.left)
	{
		n += self.pad(' ', self.width, len)!;
	}
	// reverse string
	while (len) n += self.out(buf[--len])!;

	// append pad spaces up to given width
	n += self.adjust(n)!;
	return n;
}


fn int? printf_parse_format_field(
	any* args_ptr, usz args_len, usz* args_index_ptr,
	char* format_ptr, usz format_len, usz* index_ptr) @inline @private
{
	char c = format_ptr[*index_ptr];
	if (c.is_digit()) return simple_atoi(format_ptr, format_len, index_ptr);
	if (c != '*') return 0;
	usz len = ++(*index_ptr);
	if (len >= format_len) return BAD_FORMAT~;
	if (*args_index_ptr >= args_len) return BAD_FORMAT~;
	any val = args_ptr[(*args_index_ptr)++];
	if (!val.type.kindof.is_int()) return BAD_FORMAT~;
	uint? intval = types::any_to_int(val, int);
	return intval ?? BAD_FORMAT~;
}
