module std::io;
import std::collections::map;
import libc;

// It is vitally important that the formatter doesn't do any allocations,
// or it ceases to be useful in constrained environments.

const int PRINTF_NTOA_BUFFER_SIZE = 256;

interface Printable
{
	fn String to_constant_string() @optional;
	fn usz? to_format(Formatter* formatter) @optional;
}

faultdef BUFFER_EXCEEDED, INTERNAL_BUFFER_EXCEEDED, INVALID_FORMAT, NOT_ENOUGH_ARGUMENTS, INVALID_ARGUMENT;

alias OutputFn = fn void?(void* buffer, char c);
alias FloatType = double;


macro bool is_struct_with_default_print($Type)
{
	return ($Type.kindof == STRUCT ||| $Type.kindof == BITSTRUCT)
		&&& !$defined($Type.to_format)
		&&& !$defined($Type.to_constant_string);
}

<*
 Introspect a struct and print it to a formatter

 @require $kindof(value) == STRUCT || $kindof(value) == BITSTRUCT : `This macro is only valid on macros`
*>
macro usz? struct_to_format(value, Formatter* f, bool $force_dump)
{
	var $Type = $typeof(value);
	usz total = f.print("{ ")!;
	$foreach $i, $member : $Type.membersof:
		$if $i > 0:
			total += f.print(", ")!;
		$endif
		$if $member.nameof != "":
			total += f.printf("%s: ", $member.nameof)!;
		$endif
		$if ($force_dump &&& ($member.typeid.kindof == STRUCT || $member.typeid.kindof == BITSTRUCT)) |||
			is_struct_with_default_print($member.typeid):
			total += struct_to_format($member.get(value), f, $force_dump)!;
		$else
			total += f.printf("%s", $member.get(value))!;
		$endif
	$endforeach
	return total + f.print(" }");
}

fn usz? ReflectedParam.to_format(&self, Formatter* f) @dynamic
{
	return f.printf("[Parameter '%s']", self.name);
}

fn usz? Formatter.printf(&self, String format, args...)
{
	return self.vprintf(format, args) @inline;
}

struct Formatter
{
	void *data;
	OutputFn out_fn;
	struct
	{
		PrintFlags flags;
		uint width;
		uint prec;
		fault first_fault;
	}
}

bitstruct PrintFlags : uint
{
	bool zeropad;
	bool left;
	bool plus;
	bool space;
	bool hash;
	bool uppercase;
	bool precision;
}

fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
{
	*self = { .data = data, .out_fn = out_fn};
}

fn usz? Formatter.out(&self, char c) @private
{
	if (catch err = self.out_fn(self.data, c))
	{
		if (self.first_fault) return self.first_fault~;
		self.first_fault = err;
		return err~;
	}
	return 1;
}

fn usz? Formatter.print_with_function(&self, Printable arg)
{
	if (&arg.to_format)
	{
		PrintFlags old = self.flags;
		uint old_width = self.width;
		uint old_prec = self.prec;
		defer
		{
			self.flags = old;
			self.width = old_width;
			self.prec = old_prec;
		}
		if (!arg) return self.out_substr("(null)");
		return arg.to_format(self);
	}
	if (&arg.to_constant_string)
	{
		PrintFlags old = self.flags;
		uint old_width = self.width;
		uint old_prec = self.prec;
		defer
		{
			self.flags = old;
			self.width = old_width;
			self.prec = old_prec;
		}
		if (!arg) return self.out_substr("(null)");
		return self.out_substr(arg.to_constant_string());
	}
	return NOT_FOUND~;
}

fn usz? Formatter.out_unknown(&self, String category, any arg) @private
{
	return self.out_substr("<") + self.out_substr(category) + self.out_substr(" type:") + self.ntoa((iptr)arg.type, false, 16) + self.out_substr(", addr:") + self.ntoa((iptr)arg.ptr, false, 16) + self.out_substr(">");
}
fn usz? Formatter.out_str(&self, any arg) @private
{
	switch (arg.type.kindof)
	{
		case VOID:
			return self.out_substr("void");
		case FAULT:
			fault f = *(fault*)arg.ptr;
			return self.out_substr(f ? f.nameof : "(empty-fault)");
		case INTERFACE:
			any a = *(any*)arg;
			return a ? self.out_str(a) : self.out_substr("(empty-interface)");
		case ANY:
			any a = *(any*)arg;
			return a ? self.out_str(a) : self.out_substr("(empty-any)");
		case OPTIONAL:
			unreachable();
		case SIGNED_INT:
		case UNSIGNED_INT:
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.flags = {};
			self.width = 0;
			return self.ntoa_any(arg, 10) ?? self.out_substr("<INVALID>");
		case FLOAT:
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.flags = {};
			self.width = 0;
			return self.ftoa(float_from_any(arg)) ?? self.out_substr("ERR");
		case BOOL:
			return self.out_substr(*(bool*)arg.ptr ? "true" : "false");
		default:
	}
	usz? n = self.print_with_function((Printable)arg);
	if (try n) return n;
	if (@catch(n) != NOT_FOUND) n!;
	switch (arg.type.kindof)
	{
		case TYPEID:
			return self.out_substr("typeid[")! + self.ntoa((iptr)*(typeid*)arg, false, 16)! + self.out_substr("]")!;
		case ENUM:
			usz i = types::any_to_enum_ordinal(arg, usz)!!;
			assert(i < arg.type.names.len, "Illegal enum value found, numerical value was %d.", i);
			return self.out_substr(arg.type.names[i]);
		case STRUCT:
			return self.out_unknown("struct", arg);
		case UNION:
			return self.out_unknown("union", arg);
		case BITSTRUCT:
			return self.out_unknown("bitstruct", arg);
		case FUNC:
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.width = 0;
			return self.out_substr("0x")! + self.ntoa_any(arg, 16);
		case CONST_ENUM:
		case DISTINCT:
			if (arg.type == String.typeid)
			{
				return self.out_substr(*(String*)arg);
			}
			if (arg.type == ZString.typeid)
			{
				return self.out_substr(*(ZString*)arg ? ((ZString*)arg).str_view() : "(null)");
			}
			if (arg.type == DString.typeid)
			{
				return self.out_substr(*(DString*)arg ? ((DString*)arg).str_view() : "(null)");
			}
			return self.out_str(arg.as_inner());
		case POINTER:
			typeid inner = arg.type.inner;
			void** pointer = arg.ptr;
			if (arg.type.inner != void.typeid)
			{
				any deref = any_make(*pointer, inner);
				n = self.print_with_function((Printable)deref);
				if (try n) return n;
				if (@catch(n) != NOT_FOUND) n!;
			}
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.width = 0;
			return self.out_substr("0x")! + self.ntoa_any(arg, 16);
		case ARRAY:
			// this is SomeType[*] so grab the "SomeType"
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.flags = {};
			self.width = 0;
			typeid inner = arg.type.inner;
			usz size = inner.sizeof;
			usz alen = arg.type.len;
			// Pretend this is a String
			void* ptr = (void*)arg.ptr;
			usz len = self.out('[')!;
			for (usz i = 0; i < alen; i++)
			{
				if (i != 0) len += self.out_substr(", ")!;
				len += self.out_str(any_make(ptr, inner))!;
				ptr += size;
			}
			len += self.out(']')!;
			return len;
		case VECTOR:
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.flags = {};
			self.width = 0;
			// this is SomeType[*] so grab the "SomeType"
			typeid inner = arg.type.inner;
			usz size = inner.sizeof;
			usz vlen = arg.type.len;
			// Pretend this is a String
			void* ptr = (void*)arg.ptr;
			usz len = self.out_substr("[<")!;
			for (usz i = 0; i < vlen; i++)
			{
				if (i != 0) len += self.out_substr(", ")!;
				len += self.out_str(any_make(ptr, inner))!;
				ptr += size;
			}
			len += self.out_substr(">]")!;
			return len;
		case SLICE:
			// this is SomeType[] so grab the "SomeType"
			typeid inner = arg.type.inner;
			if (inner == void.typeid) inner = char.typeid;
			PrintFlags flags = self.flags;
			uint width = self.width;
			defer
			{
				self.flags = flags;
				self.width = width;
			}
			self.flags = {};
			self.width = 0;
			usz size = inner.sizeof;
			// Pretend this is a String
			String* temp = (void*)arg.ptr;
			void* ptr = (void*)temp.ptr;
			usz slen = temp.len;
			usz len = self.out('[')!;
			for (usz i = 0; i < slen; i++)
			{
				if (i != 0) len += self.out_substr(", ")!;
				len += self.out_str(any_make(ptr, inner))!;
				ptr += size;
			}
			len += self.out(']')!;
			return len;
		case ANY:
		case INTERFACE:
			unreachable("Already handled");
		default:
	}
	return self.out_substr("Invalid type");
}




fn void? out_null_fn(void* data @unused, char c @unused) @private
{
}

macro usz? @report_fault(Formatter* f, $fault)
{
	(void)f.out_substr($fault);
	return INVALID_FORMAT~;
}

macro usz? @wrap_bad(Formatter* f, #action)
{
	usz? len = #action;
	if (catch err = len)
	{
		switch (err)
		{
			case BUFFER_EXCEEDED:
			case INTERNAL_BUFFER_EXCEEDED:
				return f.first_err(err)~;
			default:
				err = f.first_err(INVALID_ARGUMENT);
				f.out_substr("<INVALID>")!;
				return err~;
		}
	}
	return len;
}

fn usz? Formatter.vprintf(&self, String format, any[] anys)
{
	self.first_fault = {};
	if (!self.out_fn)
	{
		// use null output function
		self.out_fn = &out_null_fn;
	}
	usz total_len;
	usz format_len = format.len;
	usz variant_index = 0;
	for (usz i = 0; i < format_len; i++)
	{
		// format specifier?  %[flags][width][.precision][length]
		char c = format[i];
		if (c != '%')
		{
			// no
			total_len += self.out(c)!;
			continue;
		}
		i++;
		if (i >= format_len) return @report_fault(self, "%ERR");
		c = format[i];
		if (c == '%')
		{
			total_len += self.out(c)!;
			continue;
		}
		// evaluate flags
		self.flags = {};
		while FLAG_EVAL: (true)
		{
			switch (c)
			{
				case '0': self.flags.zeropad = true;
				case '-': self.flags.left = true;
				case '+': self.flags.plus = true;
				case ' ': self.flags.space = true;
				case '#': self.flags.hash = true;
				default: break FLAG_EVAL;
			}
			if (++i >= format_len) return @report_fault(self, "%ERR");
			c = format[i];
		}
		// evaluate width field
		int? w = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
		if (catch w) return @report_fault(self, "%ERR");
		c = format[i];
		if (w < 0)
		{
			self.flags.left = true;
			w = -w;
		}
		self.width = w;
		// evaluate precision field
		self.prec = 0;
		if (c == '.')
		{
			self.flags.precision = true;
			if (++i >= format_len) return @report_fault(self, "<BAD FORMAT>");
			int? prec = printf_parse_format_field(anys.ptr, anys.len, &variant_index, format.ptr, format.len, &i);
			if (catch prec) return @report_fault(self, "<BAD FORMAT>");
			self.prec = prec < 0 ? 0 : prec;
			c = format[i];
		}

		// evaluate specifier
		uint base = 0;
		if (variant_index >= anys.len)
		{
			self.first_err(NOT_ENOUGH_ARGUMENTS);
			total_len += self.out_substr("<MISSING>")!;
			continue;
		}
		any current = anys[variant_index++];
		switch (c)
		{
			case 'd':
				base = 10;
				self.flags.hash = false;
			case 'X' :
				self.flags.uppercase = true;
				nextcase;
			case 'x' :
				base = 16;
			case 'O':
				self.flags.uppercase = true;
				nextcase;
			case 'o' :
				base = 8;
			case 'B':
				self.flags.uppercase = true;
				nextcase;
			case 'b' :
				base = 2;
			case 'A':
				self.flags.uppercase = true;
				nextcase;
			case 'a':
				total_len += @wrap_bad(self, self.atoa(float_from_any(current)))!;
				continue;
			case 'F' :
				self.flags.uppercase = true;
				nextcase;
			case 'f':
				total_len += @wrap_bad(self, self.ftoa(float_from_any(current)))!;
				continue;
			case 'E':
				self.flags.uppercase = true;
				nextcase;
			case 'e':
				total_len += @wrap_bad(self, self.etoa(float_from_any(current)))!;
				continue;
			case 'G':
				self.flags.uppercase = true;
				nextcase;
			case 'g':
				total_len += @wrap_bad(self, self.gtoa(float_from_any(current)))!;
				continue;
			case 'c':
				total_len += self.out_char(current)!;
				continue;
			case 'H':
				self.flags.uppercase = true;
				nextcase;
			case 'h':
				char[] out @noinit;
				switch (current.type)
				{
					case char[]:
					case ichar[]:
						out = *(char[]*)current;
					default:
						if (current.type.kindof == ARRAY && (current.type.inner == char.typeid || current.type.inner == ichar.typeid))
						{
							out = ((char*)current.ptr)[:current.type.sizeof];
							break;
						}
						if (current.type.kindof == POINTER)
						{
							out = ((*(char**)current.ptr))[:current.type.inner.sizeof];
							break;
						}
						total_len += self.out_substr("<INVALID>")!;
						continue;
				}
				if (self.flags.left)
				{
					usz len = print_hex_chars(self, out, self.flags.uppercase)!;
					total_len += len;
					total_len += self.pad(' ', self.width, len)!;
					continue;
				}
				if (self.width)
				{
					total_len += self.pad(' ', self.width, out.len * 2)!;
				}
				total_len += print_hex_chars(self, out, self.flags.uppercase)!;
				continue;
			case 's':
				if (self.flags.left)
				{
					usz len = self.out_str(current)!;
					total_len += len;
					total_len += self.pad(' ', self.width, len)!;
					continue;
				}
				if (self.width)
				{
					OutputFn out_fn = self.out_fn;
					self.out_fn = (OutputFn)&out_null_fn;
					usz len = self.out_str(current)!;
					self.out_fn = out_fn;
					total_len += self.pad(' ', self.width, len)!;
				}
				total_len += self.out_str(current)!;
				continue;
			case 'p':
				self.flags.zeropad = true;
				self.flags.hash = true;
				base = 16;
			default:
				self.first_err(INVALID_FORMAT);
				total_len += self.out_substr("<BAD FORMAT>")!;
				continue;
		}
		if (base != 10)
		{
			self.flags.plus = false;
			self.flags.space = false;
		}
		// ignore '0' flag when precision is given
		if (self.flags.precision) self.flags.zeropad = false;

		bool is_neg;
		total_len += @wrap_bad(self, self.ntoa(int_from_any(current, &is_neg), is_neg, base))!;
	}
	// termination
	//	out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);

	// return written chars without terminating \0
	if (self.first_fault) return self.first_fault~;
	return total_len;
}


fn usz? Formatter.print(&self, String str)
{
	if (!self.out_fn)
	{
		// use null output function
		self.out_fn = &out_null_fn;
	}
	foreach (c : str) self.out(c)!;
	return str.len;
}
