#import "os.odin";
#import "mem.odin";
#import "utf8.odin";

PRINT_BUF_SIZE :: 1<<12;

Buffer :: struct {
	data:   []byte;
	length: int;
}


fprint :: proc(fd: os.Handle, args: ...any) -> int {
	data: [PRINT_BUF_SIZE]byte;
	buf := Buffer{data[:], 0};
	bprint(^buf, ...args);
	os.write(fd, buf.data[:buf.length]);
	return buf.length;
}

fprintln :: proc(fd: os.Handle, args: ...any) -> int {
	data: [PRINT_BUF_SIZE]byte;
	buf := Buffer{data[:], 0};
	bprintln(^buf, ...args);
	os.write(fd, buf.data[:buf.length]);
	return buf.length;
}
fprintf :: proc(fd: os.Handle, fmt: string, args: ...any) -> int {
	data: [PRINT_BUF_SIZE]byte;
	buf := Buffer{data[:], 0};
	bprintf(^buf, fmt, ...args);
	os.write(fd, buf.data[:buf.length]);
	return buf.length;
}


print :: proc(args: ...any) -> int {
	return fprint(os.stdout, ...args);
}
println :: proc(args: ...any) -> int {
	return fprintln(os.stdout, ...args);
}
printf :: proc(fmt: string, args: ...any) -> int {
	return fprintf(os.stdout, fmt, ...args);
}



fprint_type :: proc(fd: os.Handle, info: ^Type_Info) {
	data: [PRINT_BUF_SIZE]byte;
	buf := Buffer{data[:], 0};
	bprint_type(^buf, info);
	os.write(fd, buf.data[:buf.length]);
}



print_byte_buffer :: proc(buf: ^Buffer, b: []byte) {
	if buf.length < buf.data.count {
		n := min(buf.data.count-buf.length, b.count);
		if n > 0 {
			copy(buf.data[buf.length:], b[:n]);
			buf.length += n;
		}
	}
}

bprint_string :: proc(buf: ^Buffer, s: string) {
	print_byte_buffer(buf, s as []byte);
}


byte_reverse :: proc(b: []byte) {
	n := b.count;
	for i : 0..<n/2 {
		b[i], b[n-1-i] = b[n-1-i], b[i];
	}
}

bprint_rune :: proc(buf: ^Buffer, r: rune) {
	b, n := utf8.encode_rune(r);
	bprint_string(buf, b[:n] as string);
}

bprint_space :: proc(buf: ^Buffer) { bprint_rune(buf, ' '); }
bprint_nl :: proc   (buf: ^Buffer) { bprint_rune(buf, '\n'); }

__NUM_TO_CHAR_TABLE := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@$";

bprint_bool :: proc(buffer: ^Buffer, b: bool) {
	bprint_string(buffer, if b { give "true" } else { give "false" });
}

bprint_pointer :: proc(buffer: ^Buffer, p: rawptr) #inline {
	bprint_string(buffer, "0x");
	bprint_u64(buffer, p as uint as u64);
}

// bprint_f16 :: proc(buffer: ^Buffer, f: f32)  #inline { print__f64(buffer, f as f64, 4); }
bprint_f32 :: proc(buffer: ^Buffer, f: f32)  #inline { print__f64(buffer, f as f64, 7); }
bprint_f64 :: proc(buffer: ^Buffer, f: f64)  #inline { print__f64(buffer, f as f64, 16); }
bprint_u64 :: proc(buffer: ^Buffer, value: u64) {
	i := value;
	buf :[20]byte;
	len := 0;
	if i == 0 {
		buf[len] = '0';
		len += 1;
	}
	while i > 0 {
		buf[len] = __NUM_TO_CHAR_TABLE[i % 10];
		len += 1;
		i /= 10;
	}
	byte_reverse(buf[:len]);
	bprint_string(buffer, buf[:len] as string);
}
bprint_i64 :: proc(buffer: ^Buffer, value: i64) {
	// TODO(bill): Cleanup printing
	i := value;
	if i < 0 {
		i = -i;
		bprint_rune(buffer, '-');
	}
	bprint_u64(buffer, i as u64);
}

/*
bprint_u128 :: proc(buffer: ^Buffer, value u128) {
	a := value transmute [2]u64;
	if a[1] != 0 {
		bprint_u64(buffer, a[1]);
	}
	bprint_u64(buffer, a[0]);
}
bprint_i128 :: proc(buffer: ^Buffer, value i128) {
	i := value;
	if i < 0 {
		i = -i;
		bprint_rune(buffer, '-');
	}
	bprint_u128(buffer, i as u128);
}
*/


print__f64 :: proc(buffer: ^Buffer, value: f64, decimal_places: int) {
	f := value;
	if f == 0 {
		bprint_rune(buffer, '0');
		return;
	}
	if f < 0 {
		bprint_rune(buffer, '-');
		f = -f;
	}

	i := f as u64;
	bprint_u64(buffer, i);
	f -= i as f64;

	bprint_rune(buffer, '.');

	mult: f64 = 10.0;
	while decimal_places >= 0 {
		i = (f * mult) as u64;
		bprint_u64(buffer, i as u64);
		f -= i as f64 / mult;
		mult *= 10;
		decimal_places -= 1;
	}
}

bprint_type :: proc(buf: ^Buffer, ti: ^Type_Info) {
	if ti == nil {
		return;
	}

	using Type_Info;
	match type info : ti {
	case Named:
		bprint_string(buf, info.name);
	case Integer:
		match {
		case ti == type_info(int):  bprint_string(buf, "int");
		case ti == type_info(uint): bprint_string(buf, "uint");
		default:
			bprint_string(buf, if info.signed { give "i" } else { give "u"});
			bprint_u64(buf, 8*info.size as u64);
		}

	case Float:
		match info.size {
		case 4: bprint_string(buf, "f32");
		case 8: bprint_string(buf, "f64");
		}
	case String:  bprint_string(buf, "string");
	case Boolean: bprint_string(buf, "bool");
	case Pointer:
		if info.elem == nil {
			bprint_string(buf, "rawptr");
		} else {
			bprint_string(buf, "^");
			bprint_type(buf, info.elem);
		}
	case Maybe:
		bprint_string(buf, "?");
		bprint_type(buf, info.elem);
	case Procedure:
		bprint_string(buf, "proc");
		if info.params == nil {
			bprint_string(buf, "()");
		} else {
			count := (info.params as ^Tuple).fields.count;
			if count == 1 { bprint_string(buf, "("); }
			bprint_type(buf, info.params);
			if count == 1 { bprint_string(buf, ")"); }
		}
		if info.results != nil {
			bprint_string(buf, " -> ");
			bprint_type(buf, info.results);
		}
	case Tuple:
		count := info.fields.count;
		if count != 1 { bprint_string(buf, "("); }
		for i : 0..<count {
			if i > 0 { bprint_string(buf, ", "); }

			f := info.fields[i];

			if f.name.count > 0 {
				bprint_string(buf, f.name);
				bprint_string(buf, ": ");
			}
			bprint_type(buf, f.type_info);
		}
		if count != 1 { bprint_string(buf, ")"); }

	case Array:
		bprint_string(buf, "[");
		bprint_i64(buf, info.count as i64);
		bprint_string(buf, "]");
		bprint_type(buf, info.elem);
	case Slice:
		bprint_string(buf, "[");
		bprint_string(buf, "]");
		bprint_type(buf, info.elem);
	case Vector:
		bprint_string(buf, "[vector ");
		bprint_i64(buf, info.count as i64);
		bprint_string(buf, "]");
		bprint_type(buf, info.elem);

	case Struct:
		bprint_string(buf, "struct ");
		if info.packed  { bprint_string(buf, "#packed "); }
		if info.ordered { bprint_string(buf, "#ordered "); }
		bprint_string(buf, "{");
		for field, i : info.fields {
			if i > 0 {
				bprint_string(buf, ", ");
			}
			bprint_any(buf, field.name);
			bprint_string(buf, ": ");
			bprint_type(buf, field.type_info);
		}
		bprint_string(buf, "}");

	case Union:
		bprint_string(buf, "union {");
		for field, i : info.fields {
			if i > 0 {
				bprint_string(buf, ", ");
			}
			bprint_any(buf, field.name);
			bprint_string(buf, ": ");
			bprint_type(buf, field.type_info);
		}
		bprint_string(buf, "}");

	case Raw_Union:
		bprint_string(buf, "raw_union {");
		for field, i : info.fields {
			if i > 0 {
				bprint_string(buf, ", ");
			}
			bprint_any(buf, field.name);
			bprint_string(buf, ": ");
			bprint_type(buf, field.type_info);
		}
		bprint_string(buf, "}");

	case Enum:
		bprint_string(buf, "enum ");
		bprint_type(buf, info.base);
		bprint_string(buf, " {}");

	}
}


make_any :: proc(type_info: ^Type_Info, data: rawptr) -> any {
	a :any;
	a.type_info = type_info;
	a.data = data;
	return a;
}

bprint_any :: proc(buf: ^Buffer, arg: any) {
	if arg.type_info == nil {
		bprint_string(buf, "<nil>");
		return;
	}

	if arg.data == nil {
		bprint_string(buf, "<nil>");
		return;
	}

	using Type_Info;
	match type info : arg.type_info {
	case Named:
		a := make_any(info.base, arg.data);
		match type b : info.base {
		case Struct:
			bprint_string(buf, info.name);
			bprint_string(buf, "{");
			for f, i : b.fields {
				if i > 0 {
					bprint_string(buf, ", ");
				}
				bprint_string(buf, f.name);
				// bprint_any(buf, f.offset);
				bprint_string(buf, " = ");
				data := arg.data as ^byte + f.offset;
				bprint_any(buf, make_any(f.type_info, data));
			}
			bprint_string(buf, "}");

		default:
			bprint_any(buf, a);
		}

	case Integer:
		match type i : arg {
		case i8:   bprint_i64(buf, i as i64);
		case u8:   bprint_u64(buf, i as u64);
		case i16:  bprint_i64(buf, i as i64);
		case u16:  bprint_u64(buf, i as u64);
		case i32:  bprint_i64(buf, i as i64);
		case u32:  bprint_u64(buf, i as u64);
		case i64:  bprint_i64(buf, i);
		case u64:  bprint_u64(buf, i);
		// case i128: bprint_i128(buf, i);
		// case u128: bprint_u128(buf, i);

		case int:  bprint_i64(buf, i as i64);
		case uint: bprint_u64(buf, i as u64);
		}

	case Float:
		match type f : arg {
		// case f16:  bprint_f64(buf, f as f64);
		case f32:  bprint_f32(buf, f);
		case f64:  bprint_f64(buf, f);
		// case f128: bprint_f64(buf, f as f64);
		}

	case String:
		match type s : arg {
		case string: bprint_string(buf, s);
		}

	case Boolean:
		match type b : arg {
		case bool: bprint_bool(buf, b);
		}

	case Pointer:
		match type p : arg {
		case ^Type_Info: bprint_type(buf, p);
		default:         bprint_pointer(buf, (arg.data as ^rawptr)^);
		}

	case Maybe:
		size := mem.size_of_type_info(info.elem);
		data := slice_ptr(arg.data as ^byte, size+1);
		if data[size] != 0 {
			bprint_any(buf, make_any(info.elem, arg.data));
		} else {
			bprint_string(buf, "nil");
		}

	case Array:
		bprintf(buf, "[%]%{", info.count, info.elem);
		defer bprint_string(buf, "}");

		for i : 0..<info.count {
			if i > 0 {
				bprint_string(buf, ", ");
			}

			data := arg.data as ^byte + i*info.elem_size;
			bprint_any(buf, make_any(info.elem, data));
		}

	case Slice:
		slice := arg.data as ^[]byte;
		bprintf(buf, "[]%{", info.elem);
		defer bprint_string(buf, "}");

		for i : 0..<slice.count {
			if i > 0 {
				bprint_string(buf, ", ");
			}

			data := slice.data + i*info.elem_size;
			bprint_any(buf, make_any(info.elem, data));
		}

	case Vector:
		is_bool :: proc(type_info: ^Type_Info) -> bool {
			match type info : type_info {
			case Named:
				return is_bool(info.base);
			case Boolean:
				return true;
			}
			return false;
		}

		bprintf(buf, "[vector %]%{", info.count, info.elem);
		defer bprint_string(buf, "}");

		if is_bool(info.elem) {
			return;
		}

		for i : 0..<info.count {
			if i > 0 {
				bprint_string(buf, ", ");
			}

			data := arg.data as ^byte + i*info.elem_size;
			bprint_any(buf, make_any(info.elem, data));
		}


	case Struct:
		bprintf(buf, "%{", arg.type_info);
		defer bprint_string(buf, "}");

		for f, i : info.fields {
			if i > 0 {
				bprint_string(buf, ", ");
			}
			bprint_string(buf, f.name);
			bprint_string(buf, " = ");
			data := arg.data as ^byte + f.offset;
			ti := f.type_info;
			bprint_any(buf, make_any(ti, data));
		}

	case Union:
		bprint_string(buf, "(union)");
	case Raw_Union:
		bprint_string(buf, "(raw_union)");

	case Enum:
		bprint_any(buf, make_any(info.base, arg.data));

	case Procedure:
		bprint_type(buf, arg.type_info);
		bprint_string(buf, " @ ");
		bprint_pointer(buf, (arg.data as ^rawptr)^);
	}
}


bprintf :: proc(buf: ^Buffer, fmt: string, args: ...any) -> int {
	is_digit :: proc(r: rune) -> bool #inline {
		return '0' <= r && r <= '9';
	}

	parse_int :: proc(s: string, offset: int) -> (int, int) {
		result := 0;

		for _ : offset..<s.count {
			c := s[offset] as rune;
			if !is_digit(c) {
				break;
			}

			result *= 10;
			result += (c - '0') as int;
		}

		return result, offset;
	}

	prev := 0;
	implicit_index := 0;

	while i := 0; i < fmt.count { defer i += 1;
		r := fmt[i] as rune;
		index := implicit_index;

		if r != '%' {
			continue;
		}

		bprint_string(buf, fmt[prev:i]);
		i += 1; // Skip %
		if i < fmt.count {
			next := fmt[i] as rune;

			if next == '%' {
				bprint_string(buf, "%");
				i += 1;
				prev = i;
				continue;
			}

			if is_digit(next) {
				index, i = parse_int(fmt, i);
			}
		}

		if 0 <= index && index < args.count {
			bprint_any(buf, args[index]);
			implicit_index = index+1;
		} else {
			// TODO(bill): Error check index out bounds
			bprint_string(buf, "<invalid>");
		}

		prev = i;
	}

	bprint_string(buf, fmt[prev:]);
	return buf.length;
}


bprint :: proc(buf: ^Buffer, args: ...any) -> int {
	is_type_string :: proc(info: ^Type_Info) -> bool {
		using Type_Info;
		if info == nil {
			return false;
		}

		match type i : type_info_base(info) {
		case String:
			return true;
		}
		return false;
	}


	prev_string := false;
	for arg, i : args {
		is_string := arg.data != nil && is_type_string(arg.type_info);
		if i > 0 && !is_string && !prev_string {
			bprint_space(buf);
		}
		bprint_any(buf, arg);
		prev_string = is_string;
	}
	return buf.length;
}

bprintln :: proc(buf: ^Buffer, args: ...any) -> int {
	for arg, i : args {
		if i > 0 {
			bprint_space(buf);
		}
		bprint_any(buf, arg);
	}
	bprint_nl(buf);
	return buf.length;
}
