package reflect

import "core:runtime"
import "core:intrinsics"
import "core:mem"
_ :: mem
_ :: intrinsics

Type_Info :: runtime.Type_Info

Type_Info_Named            :: runtime.Type_Info_Named
Type_Info_Integer          :: runtime.Type_Info_Integer
Type_Info_Rune             :: runtime.Type_Info_Rune
Type_Info_Float            :: runtime.Type_Info_Float
Type_Info_Complex          :: runtime.Type_Info_Complex
Type_Info_Quaternion       :: runtime.Type_Info_Quaternion
Type_Info_String           :: runtime.Type_Info_String
Type_Info_Boolean          :: runtime.Type_Info_Boolean
Type_Info_Any              :: runtime.Type_Info_Any
Type_Info_Type_Id          :: runtime.Type_Info_Type_Id
Type_Info_Pointer          :: runtime.Type_Info_Pointer
Type_Info_Multi_Pointer    :: runtime.Type_Info_Multi_Pointer
Type_Info_Procedure        :: runtime.Type_Info_Procedure
Type_Info_Array            :: runtime.Type_Info_Array
Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
Type_Info_Dynamic_Array    :: runtime.Type_Info_Dynamic_Array
Type_Info_Slice            :: runtime.Type_Info_Slice
Type_Info_Parameters       :: runtime.Type_Info_Parameters
Type_Info_Tuple            :: runtime.Type_Info_Parameters
Type_Info_Struct           :: runtime.Type_Info_Struct
Type_Info_Union            :: runtime.Type_Info_Union
Type_Info_Enum             :: runtime.Type_Info_Enum
Type_Info_Map              :: runtime.Type_Info_Map
Type_Info_Bit_Set          :: runtime.Type_Info_Bit_Set
Type_Info_Simd_Vector      :: runtime.Type_Info_Simd_Vector
Type_Info_Relative_Pointer :: runtime.Type_Info_Relative_Pointer
Type_Info_Relative_Slice   :: runtime.Type_Info_Relative_Slice
Type_Info_Matrix           :: runtime.Type_Info_Matrix
Type_Info_Soa_Pointer      :: runtime.Type_Info_Soa_Pointer

Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value


Type_Kind :: enum {
	Invalid,

	Named,
	Integer,
	Rune,
	Float,
	Complex,
	Quaternion,
	String,
	Boolean,
	Any,
	Type_Id,
	Pointer,
	Multi_Pointer,
	Procedure,
	Array,
	Enumerated_Array,
	Dynamic_Array,
	Slice,
	Tuple,
	Struct,
	Union,
	Enum,
	Map,
	Bit_Set,
	Simd_Vector,
	Relative_Pointer,
	Relative_Slice,
	Matrix,
	Soa_Pointer,
}


@(require_results)
type_kind :: proc(T: typeid) -> Type_Kind {
	ti := type_info_of(T)
	if ti != nil {
		switch _ in ti.variant {
		case Type_Info_Named:            return .Named
		case Type_Info_Integer:          return .Integer
		case Type_Info_Rune:             return .Rune
		case Type_Info_Float:            return .Float
		case Type_Info_Complex:          return .Complex
		case Type_Info_Quaternion:       return .Quaternion
		case Type_Info_String:           return .String
		case Type_Info_Boolean:          return .Boolean
		case Type_Info_Any:              return .Any
		case Type_Info_Type_Id:          return .Type_Id
		case Type_Info_Pointer:          return .Pointer
		case Type_Info_Multi_Pointer:    return .Multi_Pointer
		case Type_Info_Procedure:        return .Procedure
		case Type_Info_Array:            return .Array
		case Type_Info_Enumerated_Array: return .Enumerated_Array
		case Type_Info_Dynamic_Array:    return .Dynamic_Array
		case Type_Info_Slice:            return .Slice
		case Type_Info_Parameters:       return .Tuple
		case Type_Info_Struct:           return .Struct
		case Type_Info_Union:            return .Union
		case Type_Info_Enum:             return .Enum
		case Type_Info_Map:              return .Map
		case Type_Info_Bit_Set:          return .Bit_Set
		case Type_Info_Simd_Vector:      return .Simd_Vector
		case Type_Info_Relative_Pointer: return .Relative_Pointer
		case Type_Info_Relative_Slice:   return .Relative_Slice
		case Type_Info_Matrix:           return .Matrix
		case Type_Info_Soa_Pointer:      return .Soa_Pointer
		}

	}
	return .Invalid
}

// TODO(bill): Better name
@(require_results)
underlying_type_kind :: proc(T: typeid) -> Type_Kind {
	return type_kind(runtime.typeid_base(T))
}

// TODO(bill): Better name
@(require_results)
backing_type_kind :: proc(T: typeid) -> Type_Kind {
	return type_kind(runtime.typeid_core(T))
}


type_info_base :: runtime.type_info_base
type_info_core :: runtime.type_info_core 
type_info_base_without_enum :: type_info_core


when !ODIN_NO_RTTI {
	typeid_base :: runtime.typeid_base
	typeid_core :: runtime.typeid_core
	typeid_base_without_enum :: typeid_core
}


@(require_results)
any_base :: proc(v: any) -> any {
	v := v
	if v != nil {
		v.id = typeid_base(v.id)
	}
	return v
}
@(require_results)
any_core :: proc(v: any) -> any {
	v := v
	if v != nil {
		v.id = typeid_core(v.id)
	}
	return v
}

@(require_results)
typeid_elem :: proc(id: typeid) -> typeid {
	ti := type_info_of(id)
	if ti == nil { return nil }

	bits := 8*ti.size

	#partial switch v in ti.variant {
	case Type_Info_Complex:
		switch bits {
		case 64:  return f32
		case 128: return f64
		}
	case Type_Info_Quaternion:
		switch bits {
		case 128: return f32
		case 256: return f64
		}
	case Type_Info_Pointer:          return v.elem.id
	case Type_Info_Multi_Pointer:    return v.elem.id
	case Type_Info_Soa_Pointer:      return v.elem.id
	case Type_Info_Array:            return v.elem.id
	case Type_Info_Enumerated_Array: return v.elem.id
	case Type_Info_Slice:            return v.elem.id
	case Type_Info_Dynamic_Array:    return v.elem.id
	}
	return id
}


@(require_results)
size_of_typeid :: proc(T: typeid) -> int {
	if ti := type_info_of(T); ti != nil {
		return ti.size
	}
	return 0
}

@(require_results)
align_of_typeid :: proc(T: typeid) -> int {
	if ti := type_info_of(T); ti != nil {
		return ti.align
	}
	return 1
}

@(require_results)
as_bytes :: proc(v: any) -> []byte {
	if v != nil {
		sz := size_of_typeid(v.id)
		return ([^]byte)(v.data)[:sz]
	}
	return nil
}

@(require_results)
any_data :: #force_inline proc(v: any) -> (data: rawptr, id: typeid) {
	return v.data, v.id
}

@(require_results)
is_nil :: proc(v: any) -> bool {
	if v == nil {
		return true
	}
	data := as_bytes(v)
	if data == nil {
		return true
	}
	for v in data {
		if v != 0 {
			return false
		}
	}
	return true
}

@(require_results)
length :: proc(val: any) -> int {
	if val == nil { return 0 }

	#partial switch a in type_info_of(val.id).variant {
	case Type_Info_Named:
		return length({val.data, a.base.id})

	case Type_Info_Pointer:
		return length({val.data, a.elem.id})

	case Type_Info_Array:
		return a.count

	case Type_Info_Enumerated_Array:
		return a.count

	case Type_Info_Slice:
		return (^runtime.Raw_Slice)(val.data).len

	case Type_Info_Dynamic_Array:
		return (^runtime.Raw_Dynamic_Array)(val.data).len

	case Type_Info_Map:
		return runtime.map_len((^runtime.Raw_Map)(val.data)^)

	case Type_Info_String:
		if a.is_cstring {
			return len((^cstring)(val.data)^)
		} else {
			return (^runtime.Raw_String)(val.data).len
		}
	}
	return 0
}

@(require_results)
capacity :: proc(val: any) -> int {
	if val == nil { return 0 }

	#partial switch a in type_info_of(val.id).variant {
	case Type_Info_Named:
		return capacity({val.data, a.base.id})

	case Type_Info_Pointer:
		return capacity({val.data, a.elem.id})

	case Type_Info_Array:
		return a.count

	case Type_Info_Enumerated_Array:
		return a.count

	case Type_Info_Dynamic_Array:
		return (^runtime.Raw_Dynamic_Array)(val.data).cap

	case Type_Info_Map:
		return runtime.map_cap((^runtime.Raw_Map)(val.data)^)
	}
	return 0
}


@(require_results)
index :: proc(val: any, i: int, loc := #caller_location) -> any {
	if val == nil { return nil }

	#partial switch a in type_info_of(val.id).variant {
	case Type_Info_Named:
		return index({val.data, a.base.id}, i, loc)

	case Type_Info_Pointer:
		ptr := (^rawptr)(val.data)^
		if ptr == nil {
			return nil
		}
		return index({ptr, a.elem.id}, i, loc)

	case Type_Info_Multi_Pointer:
		ptr := (^rawptr)(val.data)^
		if ptr == nil {
			return nil
		}
		return index({ptr, a.elem.id}, i, loc)

	case Type_Info_Array:
		runtime.bounds_check_error_loc(loc, i, a.count)
		offset := uintptr(a.elem.size * i)
		data := rawptr(uintptr(val.data) + offset)
		return any{data, a.elem.id}

	case Type_Info_Enumerated_Array:
		runtime.bounds_check_error_loc(loc, i, a.count)
		offset := uintptr(a.elem.size * i)
		data := rawptr(uintptr(val.data) + offset)
		return any{data, a.elem.id}

	case Type_Info_Slice:
		raw := (^runtime.Raw_Slice)(val.data)
		runtime.bounds_check_error_loc(loc, i, raw.len)
		offset := uintptr(a.elem.size * i)
		data := rawptr(uintptr(raw.data) + offset)
		return any{data, a.elem.id}

	case Type_Info_Dynamic_Array:
		raw := (^runtime.Raw_Dynamic_Array)(val.data)
		runtime.bounds_check_error_loc(loc, i, raw.len)
		offset := uintptr(a.elem.size * i)
		data := rawptr(uintptr(raw.data) + offset)
		return any{data, a.elem.id}

	case Type_Info_String:
		if a.is_cstring { return nil }

		raw := (^runtime.Raw_String)(val.data)
		runtime.bounds_check_error_loc(loc, i, raw.len)
		offset := uintptr(size_of(u8) * i)
		data := rawptr(uintptr(raw.data) + offset)
		return any{data, typeid_of(u8)}
	}
	return nil
}

@(require_results)
deref :: proc(val: any) -> any {
	if val != nil {
		ti := type_info_base(type_info_of(val.id))
		if info, ok := ti.variant.(Type_Info_Pointer); ok {
			return any{
				(^rawptr)(val.data)^,
				info.elem.id,
			}
		}
	}
	return val
}



// Struct_Tag represents the type of the string of a struct field
//
// Through convention, tags are the concatenation of optionally space separationed key:"value" pairs.
// Each key is a non-empty string which contains no control characters other than space, quotes, and colon.
Struct_Tag :: distinct string

Struct_Field :: struct {
	name:     string,
	type:     ^Type_Info,
	tag:      Struct_Tag,
	offset:   uintptr,
	is_using: bool,
}

@(require_results)
struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		if 0 <= i && i < len(s.names) {
			field.name     = s.names[i]
			field.type     = s.types[i]
			field.tag      = Struct_Tag(s.tags[i])
			field.offset   = s.offsets[i]
			field.is_using = s.usings[i]
		}
	}
	return
}

@(require_results)
struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		for fname, i in s.names {
			if fname == name {
				field.name     = s.names[i]
				field.type     = s.types[i]
				field.tag      = Struct_Tag(s.tags[i])
				field.offset   = s.offsets[i]
				field.is_using = s.usings[i]
				break
			}
		}
	}
	return
}

@(require_results)
struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) -> any {
	if a == nil { return nil }

	ti := runtime.type_info_base(type_info_of(a.id))

	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		for name, i in s.names {
			if name == field {
				return any{
					rawptr(uintptr(a.data) + s.offsets[i]),
					s.types[i].id,
				}
			}

			if allow_using && s.usings[i] {
				f := any{
					rawptr(uintptr(a.data) + s.offsets[i]),
					s.types[i].id,
				}

				if res := struct_field_value_by_name(f, field, allow_using); res != nil {
					return res
				}
			}
		}
	}
	return nil
}

@(require_results)
struct_field_value :: proc(a: any, field: Struct_Field) -> any {
	if a == nil { return nil }
	return any {
		rawptr(uintptr(a.data) + field.offset),
		field.type.id,
	}
}

@(require_results)
struct_field_names :: proc(T: typeid) -> []string {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		return s.names
	}
	return nil
}

@(require_results)
struct_field_types :: proc(T: typeid) -> []^Type_Info {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		return s.types
	}
	return nil
}


@(require_results)
struct_field_tags :: proc(T: typeid) -> []Struct_Tag {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		return transmute([]Struct_Tag)s.tags
	}
	return nil
}

@(require_results)
struct_field_offsets :: proc(T: typeid) -> []uintptr {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		return s.offsets
	}
	return nil
}

@(require_results)
struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) {
	ti := runtime.type_info_base(type_info_of(T))
	if s, ok := ti.variant.(runtime.Type_Info_Struct); ok {
		return soa_zip(
			name     = s.names,
			type     = s.types,
			tag      = transmute([]Struct_Tag)s.tags,
			offset   = s.offsets,
			is_using = s.usings,
		)
	}
	return nil
}



@(require_results)
struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: Struct_Tag) {
	value, _ = struct_tag_lookup(tag, key)
	return
}

@(require_results)
struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: Struct_Tag, ok: bool) {
	for t := tag; t != ""; /**/ {
		i := 0
		for i < len(t) && t[i] == ' ' { // Skip whitespace
			i += 1
		}
		t = t[i:]
		if len(t) == 0 {
			break
		}

		i = 0
		loop: for i < len(t) {
			switch t[i] {
			case ':', '"':
				break loop
			case 0x00 ..< ' ', 0x7f ..= 0x9f: // break if control character is found
				break loop
			}
			i += 1
		}

		if i == 0 {
			break
		}
		if i+1 >= len(t) {
			break
		}

		if t[i] != ':' || t[i+1] != '"' {
			break
		}
		name := string(t[:i])
		t = t[i+1:]

		i = 1
		for i < len(t) && t[i] != '"' { // find closing quote
			if t[i] == '\\' {
				i += 1 // Skip escaped characters
			}
			i += 1
		}

		if i >= len(t) {
			break
		}

		val := string(t[:i+1])
		t = t[i+1:]

		if key == name {
			return Struct_Tag(val[1:i]), true
		}
	}
	return
}


@(require_results)
enum_string :: proc(a: any) -> string {
	if a == nil { return "" }
	ti := runtime.type_info_base(type_info_of(a.id))
	if e, ok := ti.variant.(runtime.Type_Info_Enum); ok {
		v, _ := as_i64(a)
		for value, i in e.values {
			if value == Type_Info_Enum_Value(v) {
				return e.names[i]
			}
		}
	} else {
		panic("expected an enum to reflect.enum_string")
	}

	return ""
}

// Given a enum type and a value name, get the enum value.
@(require_results)
enum_from_name :: proc($Enum_Type: typeid, name: string) -> (value: Enum_Type, ok: bool) {
	ti := type_info_base(type_info_of(Enum_Type))
	if eti, eti_ok := ti.variant.(runtime.Type_Info_Enum); eti_ok {
		for value_name, i in eti.names {
			if value_name != name {
				continue
			}
			v := eti.values[i]
			value = Enum_Type(v)
			ok = true
			return
		}
	}
	return
}

@(require_results)
enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info_Enum_Value, ok: bool) {
	ti := runtime.type_info_base(type_info_of(Enum_Type))
	if eti, eti_ok := ti.variant.(runtime.Type_Info_Enum); eti_ok {
		for value_name, i in eti.names {
			if value_name != name {
				continue
			}
			value = eti.values[i]
			ok = true
			return
		}
	}
	return
}


@(require_results)
enum_field_names :: proc(Enum_Type: typeid) -> []string {
	ti := runtime.type_info_base(type_info_of(Enum_Type))
	if eti, eti_ok := ti.variant.(runtime.Type_Info_Enum); eti_ok {
		return eti.names
	}
	return nil
}
@(require_results)
enum_field_values :: proc(Enum_Type: typeid) -> []Type_Info_Enum_Value {
	ti := runtime.type_info_base(type_info_of(Enum_Type))
	if eti, eti_ok := ti.variant.(runtime.Type_Info_Enum); eti_ok {
		return eti.values
	}
	return nil
}

Enum_Field :: struct {
	name:  string,
	value: Type_Info_Enum_Value,
}

@(require_results)
enum_fields_zipped :: proc(Enum_Type: typeid) -> (fields: #soa[]Enum_Field) {
	ti := runtime.type_info_base(type_info_of(Enum_Type))
	if eti, eti_ok := ti.variant.(runtime.Type_Info_Enum); eti_ok {
		return soa_zip(name=eti.names, value=eti.values)
	}
	return nil
}



@(require_results)
union_variant_type_info :: proc(a: any) -> ^Type_Info {
	id := union_variant_typeid(a)
	return type_info_of(id)
}

@(require_results)
type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool {
	return len(info.variants) == 1 && is_pointer(info.variants[0])
}

@(require_results)
union_variant_typeid :: proc(a: any) -> typeid {
	if a == nil { return nil }

	ti := runtime.type_info_base(type_info_of(a.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if type_info_union_is_pure_maybe(info) {
			if a.data != nil {
				return info.variants[0].id
			}
			return nil
		}

		tag_ptr := uintptr(a.data) + info.tag_offset
		tag_any := any{rawptr(tag_ptr), info.tag_type.id}

		tag: i64 = ---
		switch i in tag_any {
		case u8:   tag = i64(i)
		case i8:   tag = i64(i)
		case u16:  tag = i64(i)
		case i16:  tag = i64(i)
		case u32:  tag = i64(i)
		case i32:  tag = i64(i)
		case u64:  tag = i64(i)
		case i64:  tag = i
		case: unimplemented()
		}

		if info.no_nil {
			return info.variants[tag].id
		} else if tag != 0 {
			return info.variants[tag-1].id
		}

		return nil
	}
	panic("expected a union to reflect.union_variant_typeid")
}

@(require_results)
get_union_variant_raw_tag :: proc(a: any) -> i64 {
	if a == nil { return -1 }

	ti := runtime.type_info_base(type_info_of(a.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if type_info_union_is_pure_maybe(info) {
			return 1 if a.data != nil else 0
		}

		tag_ptr := uintptr(a.data) + info.tag_offset
		tag_any := any{rawptr(tag_ptr), info.tag_type.id}

		tag: i64 = ---
		switch i in tag_any {
		case u8:   tag = i64(i)
		case i8:   tag = i64(i)
		case u16:  tag = i64(i)
		case i16:  tag = i64(i)
		case u32:  tag = i64(i)
		case i32:  tag = i64(i)
		case u64:  tag = i64(i)
		case i64:  tag = i
		case: unimplemented()
		}

		return tag
	}
	panic("expected a union to reflect.get_union_variant_raw_tag")
}

@(require_results)
get_union_variant :: proc(a: any) -> any {
	if a == nil {
		return nil
	}
	id := union_variant_typeid(a)
	if id == nil {
		return nil
	}
	return any{a.data, id}
}

@(require_results)
get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_variants_to_pointers(T)) where intrinsics.type_is_union(T) {
	ptr := rawptr(val)
	tag := get_union_variant_raw_tag(val^)
	mem.copy(&res, &ptr, size_of(ptr))
	set_union_variant_raw_tag(res, tag)
	return
}



set_union_variant_raw_tag :: proc(a: any, tag: i64) {
	if a == nil { return }

	ti := runtime.type_info_base(type_info_of(a.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if type_info_union_is_pure_maybe(info) {
			// Cannot do anything
			return
		}

		tag_ptr := uintptr(a.data) + info.tag_offset
		tag_any := any{rawptr(tag_ptr), info.tag_type.id}

		switch &i in tag_any {
		case u8:   i = u8(tag)
		case i8:   i = i8(tag)
		case u16:  i = u16(tag)
		case i16:  i = i16(tag)
		case u32:  i = u32(tag)
		case i32:  i = i32(tag)
		case u64:  i = u64(tag)
		case i64:  i = tag
		case: unimplemented()
		}

		return
	}
	panic("expected a union to reflect.set_union_variant_raw_tag")
}

set_union_variant_typeid :: proc(a: any, id: typeid) {
	if a == nil { return }

	ti := runtime.type_info_base(type_info_of(a.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if type_info_union_is_pure_maybe(info) {
			// Cannot do anything
			return
		}

		if id == nil && !info.no_nil {
			set_union_variant_raw_tag(a, 0)
			return
		}

		for variant, i in info.variants {
			if variant.id == id {
				tag := i64(i)
				if !info.no_nil {
					tag += 1
				}
				set_union_variant_raw_tag(a, tag)
				return
			}
		}
		return
	}
	panic("expected a union to reflect.set_union_variant_typeid")
}

set_union_variant_type_info :: proc(a: any, tag_ti: ^Type_Info) {
	if a == nil { return }

	ti := runtime.type_info_base(type_info_of(a.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if type_info_union_is_pure_maybe(info) {
			// Cannot do anything
			return
		}

		if tag_ti == nil && !info.no_nil {
			set_union_variant_raw_tag(a, 0)
			return
		}

		for variant, i in info.variants {
			if variant == tag_ti {
				tag := i64(i)
				if !info.no_nil {
					tag += 1
				}
				set_union_variant_raw_tag(a, tag)
				return
			}
		}
		return
	}
	panic("expected a union to reflect.set_union_variant_type_info")
}

set_union_value :: proc(dst: any, value: any) -> bool {
	if dst == nil { return false }

	ti := runtime.type_info_base(type_info_of(dst.id))
	if info, ok := ti.variant.(runtime.Type_Info_Union); ok {
		if value.id == nil {
			intrinsics.mem_zero(dst.data, ti.size)
			return true
		}
		if ti.id == runtime.typeid_base(value.id) {
			intrinsics.mem_copy(dst.data, value.data, ti.size)
			return true
		}
		
		if type_info_union_is_pure_maybe(info) {
			if variant := info.variants[0]; variant.id == value.id {
				intrinsics.mem_copy(dst.data, value.data, variant.size)
				return true
			}
			return false
		}

		for variant, i in info.variants {
			if variant.id == value.id {
				tag := i64(i)
				if !info.no_nil {
					tag += 1
				}
				intrinsics.mem_copy(dst.data, value.data, variant.size)
				set_union_variant_raw_tag(dst, tag)
				return true
			}
		}
		return false
	}
	panic("expected a union to reflect.set_union_variant_typeid")
}



@(require_results)
as_bool :: proc(a: any) -> (value: bool, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Boolean:
		valid = true
		switch v in a {
		case bool: value = v
		case b8:   value = bool(v)
		case b16:  value = bool(v)
		case b32:  value = bool(v)
		case b64:  value = bool(v)
		case: valid = false
		}
	}

	return
}

@(require_results)
as_int :: proc(a: any) -> (value: int, valid: bool) {
	v: i64
	v, valid = as_i64(a)
	value = int(v)
	return
}

@(require_results)
as_uint :: proc(a: any) -> (value: uint, valid: bool) {
	v: u64
	v, valid = as_u64(a)
	value = uint(v)
	return
}

@(require_results)
as_i64 :: proc(a: any) -> (value: i64, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Integer:
		valid = true
		switch v in a {
		case i8:      value = i64(v)
		case i16:     value = i64(v)
		case i32:     value = i64(v)
		case i64:     value =      v
		case i128:    value = i64(v)
		case int:     value = i64(v)

		case u8:      value = i64(v)
		case u16:     value = i64(v)
		case u32:     value = i64(v)
		case u64:     value = i64(v)
		case u128:    value = i64(v)
		case uint:    value = i64(v)
		case uintptr: value = i64(v)

		case u16le:   value = i64(v)
		case u32le:   value = i64(v)
		case u64le:   value = i64(v)
		case u128le:  value = i64(v)

		case i16le:   value = i64(v)
		case i32le:   value = i64(v)
		case i64le:   value = i64(v)
		case i128le:  value = i64(v)

		case u16be:   value = i64(v)
		case u32be:   value = i64(v)
		case u64be:   value = i64(v)
		case u128be:  value = i64(v)

		case i16be:   value = i64(v)
		case i32be:   value = i64(v)
		case i64be:   value = i64(v)
		case i128be:  value = i64(v)
		case: valid = false
		}

	case Type_Info_Rune:
		r := a.(rune)
		value = i64(r)
		valid = true

	case Type_Info_Float:
		valid = true
		switch v in a {
		case f32:   value = i64(v)
		case f64:   value = i64(v)
		case f32le: value = i64(v)
		case f64le: value = i64(v)
		case f32be: value = i64(v)
		case f64be: value = i64(v)
		case: valid = false
		}

	case Type_Info_Boolean:
		valid = true
		switch v in a {
		case bool: value = i64(v)
		case b8:   value = i64(v)
		case b16:  value = i64(v)
		case b32:  value = i64(v)
		case b64:  value = i64(v)
		case: valid = false
		}

	case Type_Info_Complex:
		switch v in a {
		case complex64:
			if imag(v) == 0 {
				value = i64(real(v))
				valid = true
			}
		case complex128:
			if imag(v) == 0 {
				value = i64(real(v))
				valid = true
			}
		}

	case Type_Info_Quaternion:
		switch v in a {
		case quaternion128:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = i64(real(v))
				valid = true
			}
		case quaternion256:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = i64(real(v))
				valid = true
			}
		}
	}

	return
}

@(require_results)
as_u64 :: proc(a: any) -> (value: u64, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Integer:
		valid = true
		switch v in a {
		case i8:     value = u64(v)
		case i16:    value = u64(v)
		case i32:    value = u64(v)
		case i64:    value = u64(v)
		case i128:   value = u64(v)
		case int:    value = u64(v)

		case u8:     value = u64(v)
		case u16:    value = u64(v)
		case u32:    value = u64(v)
		case u64:    value =    (v)
		case u128:   value = u64(v)
		case uint:   value = u64(v)
		case uintptr:value = u64(v)

		case u16le:  value = u64(v)
		case u32le:  value = u64(v)
		case u64le:  value = u64(v)
		case u128le: value = u64(v)

		case i16le:  value = u64(v)
		case i32le:  value = u64(v)
		case i64le:  value = u64(v)
		case i128le: value = u64(v)

		case u16be:  value = u64(v)
		case u32be:  value = u64(v)
		case u64be:  value = u64(v)
		case u128be: value = u64(v)

		case i16be:  value = u64(v)
		case i32be:  value = u64(v)
		case i64be:  value = u64(v)
		case i128be: value = u64(v)
		case: valid = false
		}

	case Type_Info_Rune:
		r := a.(rune)
		value = u64(r)
		valid = true

	case Type_Info_Float:
		valid = true
		switch v in a {
		case f16:   value = u64(v)
		case f32:   value = u64(v)
		case f64:   value = u64(v)
		case f32le: value = u64(v)
		case f64le: value = u64(v)
		case f32be: value = u64(v)
		case f64be: value = u64(v)
		case: valid = false
		}

	case Type_Info_Boolean:
		valid = true
		switch v in a {
		case bool: value = u64(v)
		case b8:   value = u64(v)
		case b16:  value = u64(v)
		case b32:  value = u64(v)
		case b64:  value = u64(v)
		case: valid = false
		}

	case Type_Info_Complex:
		switch v in a {
		case complex64:
			if imag(v) == 0 {
				value = u64(real(v))
				valid = true
			}
		case complex128:
			if imag(v) == 0 {
				value = u64(real(v))
				valid = true
			}
		}

	case Type_Info_Quaternion:
		switch v in a {
		case quaternion128:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = u64(real(v))
				valid = true
			}
		case quaternion256:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = u64(real(v))
				valid = true
			}
		}
	}

	return
}


@(require_results)
as_f64 :: proc(a: any) -> (value: f64, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Integer:
		valid = true
		switch v in a {
		case i8:    value = f64(v)
		case i16:   value = f64(v)
		case i32:   value = f64(v)
		case i64:   value = f64(v)
		case i128:  value = f64(v)

		case u8:    value = f64(v)
		case u16:   value = f64(v)
		case u32:   value = f64(v)
		case u64:   value = f64(v)
		case u128:  value = f64(v)

		case u16le: value = f64(v)
		case u32le: value = f64(v)
		case u64le: value = f64(v)
		case u128le:value = f64(v)

		case i16le: value = f64(v)
		case i32le: value = f64(v)
		case i64le: value = f64(v)
		case i128le:value = f64(v)

		case u16be: value = f64(v)
		case u32be: value = f64(v)
		case u64be: value = f64(v)
		case u128be:value = f64(v)

		case i16be: value = f64(v)
		case i32be: value = f64(v)
		case i64be: value = f64(v)
		case i128be:value = f64(v)
		case: valid = false
		}

	case Type_Info_Rune:
		r := a.(rune)
		value = f64(i32(r))
		valid = true

	case Type_Info_Float:
		valid = true
		switch v in a {
		case f16:   value = f64(v)
		case f32:   value = f64(v)
		case f64:   value =    (v)
		case f32le: value = f64(v)
		case f64le: value = f64(v)
		case f32be: value = f64(v)
		case f64be: value = f64(v)
		case: valid = false
		}

	case Type_Info_Boolean:
		valid = true
		switch v in a {
		case bool: value = f64(i32(v))
		case b8:   value = f64(i32(v))
		case b16:  value = f64(i32(v))
		case b32:  value = f64(i32(v))
		case b64:  value = f64(i32(v))
		case: valid = false
		}

	case Type_Info_Complex:
		switch v in a {
		case complex64:
			if imag(v) == 0 {
				value = f64(real(v))
				valid = true
			}
		case complex128:
			if imag(v) == 0 {
				value = real(v)
				valid = true
			}
		}

	case Type_Info_Quaternion:
		switch v in a {
		case quaternion128:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = f64(real(v))
				valid = true
			}
		case quaternion256:
			if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
				value = real(v)
				valid = true
			}
		}
	}

	return
}


@(require_results)
as_string :: proc(a: any) -> (value: string, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_String:
		valid = true
		switch v in a {
		case string:  value = v
		case cstring: value = string(v)
		case: valid = false
		}
	}

	return
}

@(require_results)
relative_pointer_to_absolute :: proc(a: any) -> rawptr {
	if a == nil { return nil }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Relative_Pointer:
		return relative_pointer_to_absolute_raw(a.data, info.base_integer.id)
	}
	return nil
}


@(require_results)
relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr {
	_handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) {
		if ptr^ == 0 {
			return nil
		}
		when intrinsics.type_is_unsigned(T) {
			return rawptr(uintptr(ptr) + uintptr(ptr^))
		} else {
			return rawptr(uintptr(ptr) + uintptr(i64(ptr^)))
		}
	}

	ptr_any := any{data, base_integer_id}
	ptr: rawptr
	switch &i in ptr_any {
	case u8:    ptr = _handle(&i)
	case u16:   ptr = _handle(&i)
	case u32:   ptr = _handle(&i)
	case u64:   ptr = _handle(&i)
	case i8:    ptr = _handle(&i)
	case i16:   ptr = _handle(&i)
	case i32:   ptr = _handle(&i)
	case i64:   ptr = _handle(&i)
	case u16le: ptr = _handle(&i)
	case u32le: ptr = _handle(&i)
	case u64le: ptr = _handle(&i)
	case i16le: ptr = _handle(&i)
	case i32le: ptr = _handle(&i)
	case i64le: ptr = _handle(&i)
	case u16be: ptr = _handle(&i)
	case u32be: ptr = _handle(&i)
	case u64be: ptr = _handle(&i)
	case i16be: ptr = _handle(&i)
	case i32be: ptr = _handle(&i)
	case i64be: ptr = _handle(&i)
	}
	return ptr

}



@(require_results)
as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_Pointer:
		valid = true
		value = a.data

	case Type_Info_String:
		valid = true
		switch v in a {
		case cstring: value = rawptr(v)
		case: valid = false
		}

	case Type_Info_Relative_Pointer:
		valid = true
		value = relative_pointer_to_absolute_raw(a.data, info.base_integer.id)
	}

	return
}


@(require_results)
as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) {
	if a == nil { return }
	a := a
	ti := runtime.type_info_core(type_info_of(a.id))
	a.id = ti.id

	#partial switch info in ti.variant {
	case Type_Info_String:
		valid = true
		switch v in a {
		case string:  value = raw_data(v)
		case cstring: value = rawptr(v) // just in case
		case: valid = false
		}

	case Type_Info_Array:
		valid = true
		value = a.data

	case Type_Info_Slice:
		valid = true
		value = (^runtime.Raw_Slice)(a.data).data

	case Type_Info_Dynamic_Array:
		valid = true
		value = (^runtime.Raw_Dynamic_Array)(a.data).data
	}

	return
}

eq :: equal
ne :: not_equal

DEFAULT_EQUAL_MAX_RECURSION_LEVEL :: 32

@(require_results)
not_equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool {
	return !equal(a, b, including_indirect_array_recursion, recursion_level)
}
@(require_results)
equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_level := 0) -> bool {
	if a == nil && b == nil {
		return true
	}

	if a.id != b.id {
		return false
	}

	if a.data == b.data {
		return true
	}
	
	including_indirect_array_recursion := including_indirect_array_recursion
	if recursion_level >= DEFAULT_EQUAL_MAX_RECURSION_LEVEL {
		including_indirect_array_recursion = false
	} 

	t := type_info_of(a.id)
	if .Comparable not_in t.flags && !including_indirect_array_recursion {
		return false
	}

	if t.size == 0 {
		return true
	}

	if .Simple_Compare in t.flags {
		return runtime.memory_compare(a.data, b.data, t.size) == 0
	}
	
	t = runtime.type_info_core(t)

	switch v in t.variant {
	case Type_Info_Named:
		unreachable()
	case Type_Info_Parameters:
		unreachable()
	case Type_Info_Any:
		if !including_indirect_array_recursion {
			return false
		}
		va := (^any)(a.data)
		vb := (^any)(b.data)
		return equal(va, vb, including_indirect_array_recursion, recursion_level+1) 
	case Type_Info_Map:
		return false
	case Type_Info_Relative_Slice:
		return false
	case 
		Type_Info_Boolean,
		Type_Info_Integer, 
		Type_Info_Rune,
		Type_Info_Float,
		Type_Info_Complex,
		Type_Info_Quaternion,
		Type_Info_Type_Id,
		Type_Info_Pointer,
		Type_Info_Multi_Pointer,
		Type_Info_Procedure,
		Type_Info_Bit_Set,
		Type_Info_Enum,
		Type_Info_Simd_Vector,
		Type_Info_Relative_Pointer,
		Type_Info_Soa_Pointer,
		Type_Info_Matrix:
		return runtime.memory_compare(a.data, b.data, t.size) == 0
		
	case Type_Info_String:
		if v.is_cstring {
			x := string((^cstring)(a.data)^)
			y := string((^cstring)(b.data)^)
			return x == y
		} else {
			x := (^string)(a.data)^
			y := (^string)(b.data)^
			return x == y
		}
		return true
	case Type_Info_Array:
		for i in 0..<v.count {
			x := rawptr(uintptr(a.data) + uintptr(v.elem_size*i))
			y := rawptr(uintptr(b.data) + uintptr(v.elem_size*i))
			if !equal(any{x, v.elem.id}, any{y, v.elem.id}, including_indirect_array_recursion, recursion_level) {
				return false
			}
		}
		return true
	case Type_Info_Enumerated_Array:
		for i in 0..<v.count {
			x := rawptr(uintptr(a.data) + uintptr(v.elem_size*i))
			y := rawptr(uintptr(b.data) + uintptr(v.elem_size*i))
			if !equal(any{x, v.elem.id}, any{y, v.elem.id}, including_indirect_array_recursion, recursion_level) {
				return false
			}
		}
		return true
	case Type_Info_Struct:
		if v.equal != nil {
			return v.equal(a.data, b.data)
		} else {
			for offset, i in v.offsets {
				x := rawptr(uintptr(a.data) + offset)
				y := rawptr(uintptr(b.data) + offset)
				id := v.types[i].id
				if !equal(any{x, id}, any{y, id}, including_indirect_array_recursion, recursion_level) {
					return false
				}
			}
			return true
		}
	case Type_Info_Union:
		if v.equal != nil {
			return v.equal(a.data, b.data)
		}
		return false
	case Type_Info_Slice:
		if !including_indirect_array_recursion {
			return false
		}
		array_a := (^runtime.Raw_Slice)(a.data)
		array_b := (^runtime.Raw_Slice)(b.data)
		if array_a.len != array_b.len {
			return false
		}
		if array_a.data == array_b.data {
			return true
		}
		for i in 0..<array_a.len {
			x := rawptr(uintptr(array_a.data) + uintptr(v.elem_size*i))
			y := rawptr(uintptr(array_b.data) + uintptr(v.elem_size*i))
			if !equal(any{x, v.elem.id}, any{y, v.elem.id}, including_indirect_array_recursion, recursion_level+1) {
				return false
			}	
		}
		return true
	case Type_Info_Dynamic_Array:
		if !including_indirect_array_recursion {
			return false
		}
		array_a := (^runtime.Raw_Dynamic_Array)(a.data)
		array_b := (^runtime.Raw_Dynamic_Array)(b.data)
		if array_a.len != array_b.len {
			return false
		}
		if array_a.data == array_b.data {
			return true
		}
		if .Simple_Compare in v.elem.flags {
			return runtime.memory_compare((^byte)(array_a.data), (^byte)(array_b.data), array_a.len * v.elem.size) == 0
		}
		
		for i in 0..<array_a.len {
			x := rawptr(uintptr(array_a.data) + uintptr(v.elem_size*i))
			y := rawptr(uintptr(array_b.data) + uintptr(v.elem_size*i))
			if !equal(any{x, v.elem.id}, any{y, v.elem.id}, including_indirect_array_recursion, recursion_level+1) {
				return false
			}	
		}
		return true
	}
	
	runtime.print_typeid(a.id)
	runtime.print_string("\n")
	return true
}
