package main

import "core:fmt"

TypeKind :: enum {
	Integer,
	Float,
	Pointer,
	Array,
	Struct,
}

StructType :: struct {
	name:     [dynamic]u8,
	bit_size: u64,
}

Type :: struct {
	kind:         TypeKind,
	bit_size:     u8,
	is_signed:    bool,
	pointer_to:   ^Type,
	array_of:     ^Type,
	array_size:   u64,
	struct_index: u64,
	struct_type:  ^StructType,
}

type_get_bit_size :: proc(type: ^Type) -> u64 {
	if type == nil {
		return 0
	}
	switch (type.kind) {
	case .Integer:
		fallthrough
	case .Float:
		return u64(type.bit_size)
	case .Pointer:
		return 64
	case .Array:
		if type.array_size == 0 {
			return 64
		} else {
			return type_get_bit_size(type.array_of) * type.array_size
		}
	case .Struct:
		return type.struct_type.bit_size
	}
	panic("type_get_bit_size: Invalid type kind, cannot compute bit size")
}

type_to_string :: proc(type: ^Type) -> string {
	if type == nil {
		return "nil (shouldnt happen lol)"
	}
	if type.kind == .Integer {
		if type.is_signed {
			return fmt.aprintf("i{}", type.bit_size)
		} else {
			return fmt.aprintf("u{}", type.bit_size)
		}
	} else if type.kind == .Float {
		return fmt.aprintf("f{}", type.bit_size)
	} else if type.kind == .Pointer {
		return fmt.aprintf("^{}", type_to_string(type.pointer_to))
	} else if type.kind == .Array {
		return fmt.aprintf("[{}]{}", type.array_size, type_to_string(type.array_of))
	} else if type.kind == .Struct {
		return fmt.aprintf("Struct`%s`(%d)", type.struct_type.name, type.struct_type.bit_size)
	}
	return "???"
}

FunctionType :: struct {
	name:            [dynamic]u8,
	return_type:     ^Type,
	parameter_types: [dynamic]^Type,
}

compare_types :: proc(a: ^Type, b: ^Type) -> (ret: bool, cast_required: bool) {
	cast_required = false

	if (a == nil && b != nil) || (a != nil && b == nil) || (a == nil && b == nil) {
		ret = false
		return
	}

	if (a.kind == .Struct && b.kind != .Struct) {
		return false, false
	}
	if (a.kind != .Struct && b.kind == .Struct) {
		return false, false
	}
	if (a.kind == .Struct && b.kind == .Struct) {
		if (compare_dyn_arrs(&a.struct_type.name, &b.struct_type.name)) {
			return true, false
		}
		return false, false
	}

	if (a.kind == .Integer || a.kind == .Float) && (a.bit_size > b.bit_size) {
		ret = true
		cast_required = true
		return
	}

	ret = a != nil && b != nil && a.kind == b.kind && a.bit_size == b.bit_size && a.is_signed == b.is_signed
	if ret == false {
		return
	}

	if a.kind == .Pointer {
		ret, _ = compare_types(a.pointer_to, b.pointer_to)
	} else if a.kind == .Array {
		ret, _ = compare_types(a.array_of, b.array_of)
		ret = a.array_size == b.array_size && ret
	}
	return
}

compare_function_types :: proc(a: ^FunctionType, b: ^FunctionType) -> (ret: bool) {
	ok, cast_ := compare_types(a.return_type, b.return_type)
	ret = a != nil && b != nil && (ok && !cast_)
	if ret {
		for &arg, i in a.parameter_types {
			ok, cast_ := compare_types(arg, b.parameter_types[i])
			if !(ok && !cast_) {
				ret = false
				break
			}
		}
	}
	return
}

function_type_create :: proc() -> (ret: ^FunctionType) {
	ret = new(FunctionType)
	return
}

type_create_integer :: proc(bit_size: u8, signed: bool) -> (ret: ^Type) {
	ret = new(Type)
	ret.kind = .Integer
	ret.bit_size = bit_size
	ret.is_signed = signed
	return
}

type_create_float :: proc(bit_size: u8) -> (ret: ^Type) {
	ret = new(Type)
	ret.kind = .Float
	ret.bit_size = bit_size
	ret.is_signed = true
	return
}

type_create_pointer :: proc(to: ^Type) -> (ret: ^Type) {
	ret = new(Type)
	ret.kind = .Pointer
	ret.pointer_to = to
	return
}

type_create_array :: proc(of: ^Type, size: u64) -> (ret: ^Type) {
	ret = new(Type)
	ret.kind = .Array
	ret.array_of = of
	ret.array_size = size
	return
}

type_create_struct :: proc(name: [dynamic]u8, bit_size: u64) -> (ret: ^Type) {
	ret = new(Type)
	ret.kind = .Struct
	ret.struct_type = new(StructType)
	ret.struct_type.name = name
	ret.struct_type.bit_size = bit_size
	return
}
