// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
//
// Type layout information (32 bits)
// flag (8 bits) | nr_muls (8 bits) | idx (16 bits)
// pack: (int(flag)<<24) | (nr_muls<<16) | u16(idx)
// unpack:
// flag: (int(type)>>24) & 0xff
// nr_muls: (int(type)>>16) & 0xff
// idx:  u16(type) & 0xffff
module ast

import strings
import v.pref

pub type Type = int

pub type TypeInfo = Aggregate
	| Alias
	| Array
	| ArrayFixed
	| Chan
	| Enum
	| FnType
	| GenericInst
	| Interface
	| Map
	| MultiReturn
	| Struct
	| SumType
	| Thread

pub enum Language {
	v
	c
	js
	amd64 // aka x86_64
	i386
	arm64 // 64-bit arm
	arm32 // 32-bit arm
	rv64 // 64-bit risc-v
	rv32 // 32-bit risc-v
}

pub fn pref_arch_to_table_language(pref_arch pref.Arch) Language {
	return match pref_arch {
		.amd64 {
			Language.amd64
		}
		.arm64 {
			Language.arm64
		}
		.arm32 {
			Language.arm32
		}
		.rv64 {
			Language.rv64
		}
		.rv32 {
			Language.rv32
		}
		.i386 {
			Language.i386
		}
		._auto, ._max {
			Language.v
		}
	}
}

// Represents a type that only needs an identifier, e.g. int, array_int.
// A pointer type `&T` would have a TypeSymbol `T`.
// Note: For a Type, use:
// * Table.type_to_str(typ) not TypeSymbol.name.
// * Table.type_kind(typ) not TypeSymbol.kind.
// Each TypeSymbol is entered into `Table.types`.
// See also: Table.get_type_symbol.

pub struct TypeSymbol {
pub:
	parent_idx int
pub mut:
	info      TypeInfo
	kind      Kind
	name      string // the internal & source name of the type, i.e. `[5]int`.
	cname     string // the name with no dots for use in the generated C code
	methods   []Fn
	mod       string
	is_public bool
	language  Language
	idx       int
}

// max of 8
pub enum TypeFlag {
	optional
	variadic
	generic
	shared_f
	atomic_f
}

/*
To save precious TypeFlag bits the 4 possible ShareTypes are coded in the two
	bits `shared` and `atomic_or_rw` (see sharetype_from_flags() below).
*/
pub enum ShareType {
	mut_t
	shared_t
	atomic_t
}

pub fn (t ShareType) str() string {
	match t {
		.mut_t { return 'mut' }
		.shared_t { return 'shared' }
		.atomic_t { return 'atomic' }
	}
}

// <atomic.h> defines special typenames
pub fn (t Type) atomic_typename() string {
	idx := t.idx()
	match idx {
		ast.u32_type_idx { return 'atomic_uint' }
		ast.int_type_idx { return 'atomic_int' }
		ast.u64_type_idx { return 'atomic_ullong' }
		ast.i64_type_idx { return 'atomic_llong' }
		else { return 'unknown_atomic' }
	}
}

pub fn sharetype_from_flags(is_shared bool, is_atomic bool) ShareType {
	return ShareType((int(is_atomic) << 1) | int(is_shared))
}

pub fn (t Type) share() ShareType {
	return sharetype_from_flags(t.has_flag(.shared_f), t.has_flag(.atomic_f))
}

// return TypeSymbol idx for `t`
[inline]
pub fn (t Type) idx() int {
	return u16(t) & 0xffff
}

[inline]
pub fn (t Type) is_void() bool {
	return t == ast.void_type
}

[inline]
pub fn (t Type) is_full() bool {
	return t != 0 && t != ast.void_type
}

// return nr_muls for `t`
[inline]
pub fn (t Type) nr_muls() int {
	return (int(t) >> 16) & 0xff
}

// return true if `t` is a pointer (nr_muls>0)
[inline]
pub fn (t Type) is_ptr() bool {
	// any normal pointer, i.e. &Type, &&Type etc;
	// NB: voidptr, charptr and byteptr are NOT included!
	return (int(t) >> 16) & 0xff > 0
}

[inline]
pub fn (t Type) is_any_kind_of_pointer() bool {
	return (int(t) >> 16) & 0xff > 0 || (u16(t) & 0xffff) in ast.pointer_type_idxs
}

// set nr_muls on `t` and return it
[inline]
pub fn (t Type) set_nr_muls(nr_muls int) Type {
	if nr_muls < 0 || nr_muls > 255 {
		panic('set_nr_muls: nr_muls must be between 0 & 255')
	}
	return int(t) & 0xff00ffff | (nr_muls << 16)
}

// increments nr_nuls on `t` and return it
[inline]
pub fn (t Type) to_ptr() Type {
	nr_muls := (int(t) >> 16) & 0xff
	if nr_muls == 255 {
		panic('to_ptr: nr_muls is already at max of 255')
	}
	return int(t) & 0xff00ffff | ((nr_muls + 1) << 16)
}

// decrement nr_muls on `t` and return it
[inline]
pub fn (t Type) deref() Type {
	nr_muls := (int(t) >> 16) & 0xff
	if nr_muls == 0 {
		panic('deref: type `$t` is not a pointer')
	}
	return int(t) & 0xff00ffff | ((nr_muls - 1) << 16)
}

// set `flag` on `t` and return `t`
[inline]
pub fn (t Type) set_flag(flag TypeFlag) Type {
	return int(t) | (1 << (int(flag) + 24))
}

// clear `flag` on `t` and return `t`
[inline]
pub fn (t Type) clear_flag(flag TypeFlag) Type {
	return int(t) & ~(1 << (int(flag) + 24))
}

// clear all flags
[inline]
pub fn (t Type) clear_flags() Type {
	return int(t) & 0xffffff
}

// return true if `flag` is set on `t`
[inline]
pub fn (t Type) has_flag(flag TypeFlag) bool {
	return int(t) & (1 << (int(flag) + 24)) > 0
}

// debug returns a verbose representation of the information in ts, useful for tracing/debugging
pub fn (ts TypeSymbol) debug() []string {
	mut res := []string{}
	ts.dbg_common(mut res)
	res << 'info: $ts.info'
	res << 'methods ($ts.methods.len): ' + ts.methods.map(it.str()).join(', ')
	return res
}

// same as .debug(), but without the verbose .info and .methods fields
pub fn (ts TypeSymbol) dbg() []string {
	mut res := []string{}
	ts.dbg_common(mut res)
	return res
}

fn (ts TypeSymbol) dbg_common(mut res []string) {
	res << 'idx: 0x$ts.idx.hex()'
	res << 'parent_idx: 0x$ts.parent_idx.hex()'
	res << 'mod: $ts.mod'
	res << 'name: $ts.name'
	res << 'cname: $ts.cname'
	res << 'kind: $ts.kind'
	res << 'is_public: $ts.is_public'
	res << 'language: $ts.language'
}

pub fn (t Type) str() string {
	return 'ast.Type(0x$t.hex() = ${u32(t)})'
}

pub fn (t &Table) type_str(typ Type) string {
	sym := t.get_type_symbol(typ)
	return sym.name
}

// debug returns a verbose representation of the information in the type `t`, useful for tracing/debugging
pub fn (t Type) debug() []string {
	mut res := []string{}
	res << 'idx: 0x${t.idx().hex():-8}'
	res << 'type: 0x${t.hex():-8}'
	res << 'nr_muls: $t.nr_muls()'
	if t.has_flag(.optional) {
		res << 'optional'
	}
	if t.has_flag(.variadic) {
		res << 'variadic'
	}
	if t.has_flag(.generic) {
		res << 'generic'
	}
	if t.has_flag(.shared_f) {
		res << 'shared_f'
	}
	if t.has_flag(.atomic_f) {
		res << 'atomic_f'
	}
	return res
}

// copy flags & nr_muls from `t_from` to `t` and return `t`
[inline]
pub fn (t Type) derive(t_from Type) Type {
	return (0xffff0000 & t_from) | u16(t)
}

// copy flags from `t_from` to `t` and return `t`
[inline]
pub fn (t Type) derive_add_muls(t_from Type) Type {
	return Type((0xff000000 & t_from) | u16(t)).set_nr_muls(t.nr_muls() + t_from.nr_muls())
}

// return new type with TypeSymbol idx set to `idx`
[inline]
pub fn new_type(idx int) Type {
	if idx < 1 || idx > 65535 {
		panic('new_type: idx must be between 1 & 65535')
	}
	return idx
}

// return new type with TypeSymbol idx set to `idx` & nr_muls set to `nr_muls`
[inline]
pub fn new_type_ptr(idx int, nr_muls int) Type {
	if idx < 1 || idx > 65535 {
		panic('new_type_ptr: idx must be between 1 & 65535')
	}
	if nr_muls < 0 || nr_muls > 255 {
		panic('new_type_ptr: nr_muls must be between 0 & 255')
	}
	return (nr_muls << 16) | u16(idx)
}

[inline]
pub fn (typ Type) is_pointer() bool {
	// builtin pointer types (voidptr, byteptr, charptr)
	return typ.idx() in ast.pointer_type_idxs
}

[inline]
pub fn (typ Type) is_real_pointer() bool {
	return typ.is_ptr() || typ.is_pointer()
}

[inline]
pub fn (typ Type) is_float() bool {
	return typ.clear_flags() in ast.float_type_idxs
}

[inline]
pub fn (typ Type) is_int() bool {
	return typ.clear_flags() in ast.integer_type_idxs
}

[inline]
pub fn (typ Type) is_int_valptr() bool {
	return typ.idx() in ast.integer_type_idxs
}

[inline]
pub fn (typ Type) is_float_valptr() bool {
	return typ.idx() in ast.float_type_idxs
}

[inline]
pub fn (typ Type) is_pure_int() bool {
	return int(typ) in ast.integer_type_idxs
}

[inline]
pub fn (typ Type) is_pure_float() bool {
	return int(typ) in ast.float_type_idxs
}

[inline]
pub fn (typ Type) is_signed() bool {
	return typ.idx() in ast.signed_integer_type_idxs
}

[inline]
pub fn (typ Type) is_unsigned() bool {
	return typ.idx() in ast.unsigned_integer_type_idxs
}

[inline]
pub fn (typ Type) is_int_literal() bool {
	return int(typ) == ast.int_literal_type_idx
}

[inline]
pub fn (typ Type) is_number() bool {
	return typ.clear_flags() in ast.number_type_idxs
}

[inline]
pub fn (typ Type) is_string() bool {
	return typ.idx() in ast.string_type_idxs
}

[inline]
pub fn (typ Type) is_bool() bool {
	return typ.idx() == ast.bool_type_idx
}

pub const (
	void_type_idx          = 1
	voidptr_type_idx       = 2
	byteptr_type_idx       = 3
	charptr_type_idx       = 4
	i8_type_idx            = 5
	i16_type_idx           = 6
	int_type_idx           = 7
	i64_type_idx           = 8
	isize_type_idx         = 9
	byte_type_idx          = 10
	u16_type_idx           = 11
	u32_type_idx           = 12
	u64_type_idx           = 13
	usize_type_idx         = 14
	f32_type_idx           = 15
	f64_type_idx           = 16
	char_type_idx          = 17
	bool_type_idx          = 18
	none_type_idx          = 19
	string_type_idx        = 20
	rune_type_idx          = 21
	array_type_idx         = 22
	map_type_idx           = 23
	chan_type_idx          = 24
	size_t_type_idx        = 25
	any_type_idx           = 26
	float_literal_type_idx = 27
	int_literal_type_idx   = 28
	thread_type_idx        = 29
	error_type_idx         = 30
	u8_type_idx            = 31
)

pub const (
	integer_type_idxs          = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx,
		byte_type_idx, u8_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, isize_type_idx,
		usize_type_idx, int_literal_type_idx, rune_type_idx]
	signed_integer_type_idxs   = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx,
		isize_type_idx,
	]
	unsigned_integer_type_idxs = [byte_type_idx, u16_type_idx, u32_type_idx, u64_type_idx,
		usize_type_idx,
	]
	float_type_idxs            = [f32_type_idx, f64_type_idx, float_literal_type_idx]
	number_type_idxs           = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx,
		byte_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, isize_type_idx, usize_type_idx,
		f32_type_idx, f64_type_idx, int_literal_type_idx, float_literal_type_idx, rune_type_idx]
	pointer_type_idxs          = [voidptr_type_idx, byteptr_type_idx, charptr_type_idx]
	string_type_idxs           = [string_type_idx]
)

pub const (
	void_type          = new_type(void_type_idx)
	ovoid_type         = new_type(void_type_idx).set_flag(.optional) // the return type of `fn () ?`
	voidptr_type       = new_type(voidptr_type_idx)
	byteptr_type       = new_type(byteptr_type_idx)
	charptr_type       = new_type(charptr_type_idx)
	i8_type            = new_type(i8_type_idx)
	int_type           = new_type(int_type_idx)
	i16_type           = new_type(i16_type_idx)
	i64_type           = new_type(i64_type_idx)
	isize_type         = new_type(isize_type_idx)
	byte_type          = new_type(byte_type_idx)
	u8_type            = new_type(u8_type_idx)
	u16_type           = new_type(u16_type_idx)
	u32_type           = new_type(u32_type_idx)
	u64_type           = new_type(u64_type_idx)
	usize_type         = new_type(usize_type_idx)
	f32_type           = new_type(f32_type_idx)
	f64_type           = new_type(f64_type_idx)
	char_type          = new_type(char_type_idx)
	bool_type          = new_type(bool_type_idx)
	none_type          = new_type(none_type_idx)
	string_type        = new_type(string_type_idx)
	rune_type          = new_type(rune_type_idx)
	array_type         = new_type(array_type_idx)
	map_type           = new_type(map_type_idx)
	chan_type          = new_type(chan_type_idx)
	any_type           = new_type(any_type_idx)
	float_literal_type = new_type(float_literal_type_idx)
	int_literal_type   = new_type(int_literal_type_idx)
	thread_type        = new_type(thread_type_idx)
	error_type         = new_type(error_type_idx)
	charptr_types      = [charptr_type, new_type(char_type_idx).set_nr_muls(1)]
	byteptr_types      = [byteptr_type, new_type(byte_type_idx).set_nr_muls(1)]
	voidptr_types      = [voidptr_type, new_type(voidptr_type_idx).set_nr_muls(1)]
	cptr_types         = merge_types(voidptr_types, byteptr_types, charptr_types)
)

pub fn merge_types(params ...[]Type) []Type {
	mut res := []Type{}
	for types in params {
		res << types
	}
	return res
}

pub const (
	// must be in the same order as the idx consts above
	builtin_type_names = ['void', 'voidptr', 'byteptr', 'charptr', 'i8', 'i16', 'int', 'i64', 'isize',
		'byte', 'u16', 'u32', 'u64', 'usize', 'f32', 'f64', 'char', 'bool', 'none', 'string', 'rune',
		'array', 'map', 'chan', 'size_t', 'any', 'float_literal', 'int_literal', 'thread', 'Error',
		'u8',
	]
)

pub struct MultiReturn {
pub mut:
	types []Type
}

pub struct FnType {
pub mut:
	is_anon  bool
	has_decl bool
	func     Fn
}

// returns TypeSymbol kind only if there are no type modifiers
pub fn (t &Table) type_kind(typ Type) Kind {
	if typ.nr_muls() > 0 || typ.has_flag(.optional) {
		return Kind.placeholder
	}
	return t.get_type_symbol(typ).kind
}

pub enum Kind {
	placeholder
	void
	voidptr
	byteptr
	charptr
	i8
	i16
	int
	i64
	isize
	byte
	u8
	u16
	u32
	u64
	usize
	f32
	f64
	char
	size_t
	rune
	bool
	none_
	string
	array
	array_fixed
	map
	chan
	any
	struct_
	generic_inst
	multi_return
	sum_type
	alias
	enum_
	function
	interface_
	float_literal
	int_literal
	aggregate
	thread
}

pub fn (t &TypeSymbol) str() string {
	return t.name
}

[inline]
pub fn (t &TypeSymbol) enum_info() Enum {
	match mut t.info {
		Enum { return t.info }
		else { panic('TypeSymbol.enum_info(): no enum info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) mr_info() MultiReturn {
	match mut t.info {
		MultiReturn { return t.info }
		else { panic('TypeSymbol.mr_info(): no multi return info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) array_info() Array {
	match mut t.info {
		Array { return t.info }
		else { panic('TypeSymbol.array_info(): no array info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) array_fixed_info() ArrayFixed {
	match mut t.info {
		ArrayFixed { return t.info }
		else { panic('TypeSymbol.array_fixed(): no array fixed info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) chan_info() Chan {
	match mut t.info {
		Chan { return t.info }
		else { panic('TypeSymbol.chan_info(): no chan info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) thread_info() Thread {
	match mut t.info {
		Thread { return t.info }
		else { panic('TypeSymbol.thread_info(): no thread info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) map_info() Map {
	match mut t.info {
		Map { return t.info }
		else { panic('TypeSymbol.map_info(): no map info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) struct_info() Struct {
	match mut t.info {
		Struct { return t.info }
		else { panic('TypeSymbol.struct_info(): no struct info for type: $t.name') }
	}
}

[inline]
pub fn (t &TypeSymbol) sumtype_info() SumType {
	match mut t.info {
		SumType { return t.info }
		else { panic('TypeSymbol.sumtype_info(): no sumtype info for type: $t.name') }
	}
}

pub fn (t &TypeSymbol) is_heap() bool {
	if t.kind == .struct_ {
		info := t.info as Struct
		return info.is_heap
	} else {
		return false
	}
}

/*
pub fn (t TypeSymbol) str() string {
	return t.name
}
*/
pub fn (mut t Table) register_builtin_type_symbols() {
	// reserve index 0 so nothing can go there
	// save index check, 0 will mean not found
	t.register_type_symbol(kind: .placeholder, name: 'reserved_0')
	t.register_type_symbol(kind: .void, name: 'void', cname: 'void', mod: 'builtin')
	t.register_type_symbol(kind: .voidptr, name: 'voidptr', cname: 'voidptr', mod: 'builtin')
	t.register_type_symbol(kind: .byteptr, name: 'byteptr', cname: 'byteptr', mod: 'builtin')
	t.register_type_symbol(kind: .charptr, name: 'charptr', cname: 'charptr', mod: 'builtin')
	t.register_type_symbol(kind: .i8, name: 'i8', cname: 'i8', mod: 'builtin')
	t.register_type_symbol(kind: .i16, name: 'i16', cname: 'i16', mod: 'builtin')
	t.register_type_symbol(kind: .int, name: 'int', cname: 'int', mod: 'builtin')
	t.register_type_symbol(kind: .i64, name: 'i64', cname: 'i64', mod: 'builtin')
	t.register_type_symbol(kind: .isize, name: 'isize', cname: 'isize', mod: 'builtin')
	t.register_type_symbol(kind: .byte, name: 'byte', cname: 'byte', mod: 'builtin')
	t.register_type_symbol(kind: .u16, name: 'u16', cname: 'u16', mod: 'builtin')
	t.register_type_symbol(kind: .u32, name: 'u32', cname: 'u32', mod: 'builtin')
	t.register_type_symbol(kind: .u64, name: 'u64', cname: 'u64', mod: 'builtin')
	t.register_type_symbol(kind: .usize, name: 'usize', cname: 'usize', mod: 'builtin')
	t.register_type_symbol(kind: .f32, name: 'f32', cname: 'f32', mod: 'builtin')
	t.register_type_symbol(kind: .f64, name: 'f64', cname: 'f64', mod: 'builtin')
	t.register_type_symbol(kind: .char, name: 'char', cname: 'char', mod: 'builtin')
	t.register_type_symbol(kind: .bool, name: 'bool', cname: 'bool', mod: 'builtin')
	t.register_type_symbol(kind: .none_, name: 'none', cname: 'none', mod: 'builtin')
	t.register_type_symbol(kind: .string, name: 'string', cname: 'string', mod: 'builtin')
	t.register_type_symbol(kind: .rune, name: 'rune', cname: 'rune', mod: 'builtin')
	t.register_type_symbol(kind: .array, name: 'array', cname: 'array', mod: 'builtin')
	t.register_type_symbol(kind: .map, name: 'map', cname: 'map', mod: 'builtin')
	t.register_type_symbol(kind: .chan, name: 'chan', cname: 'chan', mod: 'builtin')
	t.register_type_symbol(kind: .size_t, name: 'size_t', cname: 'size_t', mod: 'builtin')
	t.register_type_symbol(kind: .any, name: 'any', cname: 'any', mod: 'builtin')
	t.register_type_symbol(
		kind: .float_literal
		name: 'float literal'
		cname: 'float_literal'
		mod: 'builtin'
	)
	t.register_type_symbol(
		kind: .int_literal
		name: 'int literal'
		cname: 'int_literal'
		mod: 'builtin'
	)
	t.register_type_symbol(
		kind: .thread
		name: 'thread'
		cname: '__v_thread'
		mod: 'builtin'
		info: Thread{
			return_type: ast.void_type
		}
	)
	t.register_type_symbol(kind: .interface_, name: 'IError', cname: 'IError', mod: 'builtin')
	t.register_type_symbol(kind: .u8, name: 'zu8', cname: 'zu8', mod: 'builtin')
}

[inline]
pub fn (t &TypeSymbol) is_pointer() bool {
	return t.kind in [.byteptr, .charptr, .voidptr]
}

[inline]
pub fn (t &TypeSymbol) is_int() bool {
	res := t.kind in [.i8, .i16, .int, .i64, .isize, .byte, .u16, .u32, .u64, .usize, .int_literal,
		.rune,
	]
	if !res && t.kind == .alias {
		return (t.info as Alias).parent_type.is_number()
	}
	return res
}

[inline]
pub fn (t &TypeSymbol) is_float() bool {
	return t.kind in [.f32, .f64, .float_literal]
}

[inline]
pub fn (t &TypeSymbol) is_string() bool {
	return t.kind == .string
}

[inline]
pub fn (t &TypeSymbol) is_number() bool {
	return t.is_int() || t.is_float()
}

[inline]
pub fn (t &TypeSymbol) is_primitive() bool {
	return t.is_number() || t.is_pointer() || t.is_string()
}

[inline]
pub fn (t &TypeSymbol) is_builtin() bool {
	return t.mod == 'builtin'
}

// for debugging/errors only, perf is not an issue
pub fn (k Kind) str() string {
	k_str := match k {
		.placeholder { 'placeholder' }
		.void { 'void' }
		.voidptr { 'voidptr' }
		.charptr { 'charptr' }
		.byteptr { 'byteptr' }
		.struct_ { 'struct' }
		.int { 'int' }
		.i8 { 'i8' }
		.i16 { 'i16' }
		.i64 { 'i64' }
		.isize { 'isize' }
		.byte { 'byte' }
		.u8 { 'u8' }
		.u16 { 'u16' }
		.u32 { 'u32' }
		.u64 { 'u64' }
		.usize { 'usize' }
		.int_literal { 'int_literal' }
		.f32 { 'f32' }
		.f64 { 'f64' }
		.float_literal { 'float_literal' }
		.string { 'string' }
		.char { 'char' }
		.bool { 'bool' }
		.size_t { 'size_t' }
		.none_ { 'none' }
		.array { 'array' }
		.array_fixed { 'array_fixed' }
		.map { 'map' }
		.chan { 'chan' }
		.multi_return { 'multi_return' }
		.sum_type { 'sum_type' }
		.alias { 'alias' }
		.enum_ { 'enum' }
		.any { 'any' }
		.function { 'function' }
		.interface_ { 'interface' }
		.generic_inst { 'generic_inst' }
		.rune { 'rune' }
		.aggregate { 'aggregate' }
		.thread { 'thread' }
	}
	return k_str
}

pub fn (kinds []Kind) str() string {
	mut kinds_str := ''
	for i, k in kinds {
		kinds_str += k.str()
		if i < kinds.len - 1 {
			kinds_str += '_'
		}
	}
	return kinds_str
}

pub struct Struct {
pub:
	attrs []Attr
pub mut:
	embeds         []Type
	fields         []StructField
	is_typedef     bool // C. [typedef]
	is_union       bool
	is_heap        bool
	is_generic     bool
	generic_types  []Type
	concrete_types []Type
	parent_type    Type
}

// instantiation of a generic struct
pub struct GenericInst {
pub mut:
	parent_idx     int    // idx of the base generic struct
	concrete_types []Type // concrete types, e.g. <int, string>
}

pub struct Interface {
pub mut:
	types   []Type // all types that implement this interface
	fields  []StructField
	methods []Fn
	ifaces  []Type
	// `I1 is I2` conversions
	conversions map[int][]Type
	// generic interface support
	is_generic     bool
	generic_types  []Type
	concrete_types []Type
	parent_type    Type
}

pub struct Enum {
pub:
	vals             []string
	is_flag          bool
	is_multi_allowed bool
}

pub struct Alias {
pub:
	parent_type Type
	language    Language
	is_import   bool
}

pub struct Aggregate {
mut:
	fields []StructField // used for faster lookup inside the module
pub:
	types []Type
}

/*
pub struct Field {
pub:
	name string
pub mut:
	typ              Type
	default_expr     Expr
	has_default_expr bool
	default_expr_typ Type
	default_val      string
	attrs            []Attr
	is_pub           bool
	is_mut           bool
	is_global        bool
}
*/

pub fn (f &StructField) equals(o &StructField) bool {
	// TODO: f.is_mut == o.is_mut was removed here to allow read only access
	// to (mut/not mut), but otherwise equal fields; some other new checks are needed:
	// - if node is declared mut, and we mutate node.stmts, all stmts fields must be mutable
	// - same goes for pub and global, if we call the field from another module
	return f.name == o.name && f.typ == o.typ && f.is_pub == o.is_pub && f.is_global == o.is_global
}

pub struct Array {
pub:
	nr_dims int
pub mut:
	elem_type Type
}

pub struct ArrayFixed {
pub:
	size      int
	size_expr Expr // used by fmt for e.g. ´[my_const]byte´
pub mut:
	elem_type Type
}

pub struct Chan {
pub mut:
	elem_type Type
	is_mut    bool
}

pub struct Thread {
pub mut:
	return_type Type
}

pub struct Map {
pub mut:
	key_type   Type
	value_type Type
}

pub struct SumType {
pub:
	variants []Type
pub mut:
	fields       []StructField
	found_fields bool
	// generic sumtype support
	is_generic     bool
	generic_types  []Type
	concrete_types []Type
	parent_type    Type
}

// human readable type name
pub fn (t &Table) type_to_str(typ Type) string {
	return t.type_to_str_using_aliases(typ, map[string]string{})
}

// type name in code (for builtin)
pub fn (mytable &Table) type_to_code(t Type) string {
	match t {
		ast.int_literal_type, ast.float_literal_type { return mytable.get_type_symbol(t).kind.str() }
		else { return mytable.type_to_str_using_aliases(t, map[string]string{}) }
	}
}

// import_aliases is a map of imported symbol aliases 'module.Type' => 'Type'
pub fn (t &Table) type_to_str_using_aliases(typ Type, import_aliases map[string]string) string {
	sym := t.get_type_symbol(typ)
	mut res := sym.name
	// Note, that the duplication of code in some of the match branches here
	// is VERY deliberate. DO NOT be tempted to use `else {}` instead, because
	// that strongly reduces the usefullness of the exhaustive checking that
	// match does.
	//    Using else{} here led to subtle bugs in vfmt discovered *months*
	// after the original code was written.
	//    It is important that each case here is handled *explicitly* and
	// *clearly*, and that when a new kind is added, it should also be handled
	// explicitly.
	match sym.kind {
		.int_literal, .float_literal {
			res = sym.name
		}
		.byteptr {
			res = '&byte'
		}
		.charptr {
			res = '&char'
		}
		.i8, .i16, .int, .i64, .isize, .byte, .u8, .u16, .u32, .u64, .usize, .f32, .f64, .char,
		.rune, .string, .bool, .none_, .voidptr {
			// primitive types
			res = sym.kind.str()
		}
		.array {
			if typ == ast.array_type {
				return 'array'
			}
			if typ.has_flag(.variadic) {
				res = t.type_to_str_using_aliases(t.value_type(typ), import_aliases)
			} else {
				if sym.info is Array {
					elem_str := t.type_to_str_using_aliases(sym.info.elem_type, import_aliases)
					res = '[]$elem_str'
				} else {
					res = 'array'
				}
			}
		}
		.array_fixed {
			info := sym.info as ArrayFixed
			elem_str := t.type_to_str_using_aliases(info.elem_type, import_aliases)
			if info.size_expr is EmptyExpr {
				res = '[$info.size]$elem_str'
			} else {
				res = '[$info.size_expr]$elem_str'
			}
		}
		.chan {
			// TODO currently the `chan` struct in builtin is not considered a struct but a chan
			if sym.mod != 'builtin' && sym.name != 'chan' {
				info := sym.info as Chan
				mut elem_type := info.elem_type
				mut mut_str := ''
				if info.is_mut {
					mut_str = 'mut '
					elem_type = elem_type.set_nr_muls(elem_type.nr_muls() - 1)
				}
				elem_str := t.type_to_str_using_aliases(elem_type, import_aliases)
				res = 'chan $mut_str$elem_str'
			}
		}
		.function {
			info := sym.info as FnType
			if !t.is_fmt {
				res = t.fn_signature(info.func, type_only: true)
			} else {
				if res.starts_with('fn (') {
					// fn foo ()
					has_names := info.func.params.any(it.name.len > 0)
					res = t.fn_signature_using_aliases(info.func, import_aliases,
						type_only: !has_names
					)
				} else {
					// FnFoo
					res = t.shorten_user_defined_typenames(res, import_aliases)
				}
			}
		}
		.map {
			if int(typ) == ast.map_type_idx {
				return 'map'
			}
			info := sym.info as Map
			key_str := t.type_to_str_using_aliases(info.key_type, import_aliases)
			val_str := t.type_to_str_using_aliases(info.value_type, import_aliases)
			res = 'map[$key_str]$val_str'
		}
		.multi_return {
			res = '('
			info := sym.info as MultiReturn
			for i, typ2 in info.types {
				if i > 0 {
					res += ', '
				}
				res += t.type_to_str_using_aliases(typ2, import_aliases)
			}
			res += ')'
		}
		.struct_, .interface_, .sum_type {
			if typ.has_flag(.generic) {
				match sym.info {
					Struct, Interface, SumType {
						res += '<'
						for i, gtyp in sym.info.generic_types {
							res += t.get_type_symbol(gtyp).name
							if i != sym.info.generic_types.len - 1 {
								res += ', '
							}
						}
						res += '>'
					}
					else {}
				}
			} else {
				res = t.shorten_user_defined_typenames(res, import_aliases)
			}
		}
		.generic_inst {
			info := sym.info as GenericInst
			res = sym.name.all_before('<')
			res += '<'
			for i, ctyp in info.concrete_types {
				res += t.get_type_symbol(ctyp).name
				if i != info.concrete_types.len - 1 {
					res += ', '
				}
			}
			res += '>'
			res = t.shorten_user_defined_typenames(res, import_aliases)
		}
		.void {
			if typ.has_flag(.optional) {
				return '?'
			}
			return 'void'
		}
		.thread {
			rtype := sym.thread_info().return_type
			if rtype != 1 {
				res = 'thread ' + t.type_to_str_using_aliases(rtype, import_aliases)
			}
		}
		.alias, .any, .size_t, .aggregate, .placeholder, .enum_ {
			res = t.shorten_user_defined_typenames(res, import_aliases)
		}
	}
	mut nr_muls := typ.nr_muls()
	if typ.has_flag(.shared_f) {
		nr_muls--
		res = 'shared ' + res
	}
	if nr_muls > 0 && !typ.has_flag(.variadic) {
		res = strings.repeat(`&`, nr_muls) + res
	}
	if typ.has_flag(.optional) {
		res = '?' + res
	}
	return res
}

fn (t Table) shorten_user_defined_typenames(originalname string, import_aliases map[string]string) string {
	mut res := originalname
	if t.cmod_prefix.len > 0 && res.starts_with(t.cmod_prefix) {
		// cur_mod.Type => Type
		res = res.replace_once(t.cmod_prefix, '')
	} else if res in import_aliases {
		res = import_aliases[res]
	} else {
		// types defined by the user
		// mod.submod.submod2.Type => submod2.Type
		mut parts := res.split('.')
		if parts.len > 1 {
			ind := parts.len - 2
			if t.is_fmt {
				// Rejoin the module parts for correct usage of aliases
				parts[ind] = parts[..ind + 1].join('.')
			}
			if parts[ind] in import_aliases {
				parts[ind] = import_aliases[parts[ind]]
			}

			res = parts[ind..].join('.')
		} else {
			res = parts[0]
		}
	}
	return res
}

pub struct FnSignatureOpts {
	skip_receiver bool
	type_only     bool
}

pub fn (t &Table) fn_signature(func &Fn, opts FnSignatureOpts) string {
	return t.fn_signature_using_aliases(func, map[string]string{}, opts)
}

pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string]string, opts FnSignatureOpts) string {
	mut sb := strings.new_builder(20)
	if !opts.skip_receiver {
		sb.write_string('fn ')
		// TODO write receiver
	}
	if !opts.type_only {
		sb.write_string('$func.name')
	}
	sb.write_string('(')
	start := int(opts.skip_receiver)
	for i in start .. func.params.len {
		if i != start {
			sb.write_string(', ')
		}
		param := func.params[i]
		mut typ := param.typ
		if param.is_mut {
			typ = typ.deref()
			sb.write_string('mut ')
		}
		if !opts.type_only {
			sb.write_string('$param.name ')
		}
		styp := t.type_to_str_using_aliases(typ, import_aliases)
		if i == func.params.len - 1 && func.is_variadic {
			sb.write_string('...$styp')
		} else {
			sb.write_string('$styp')
		}
	}
	sb.write_string(')')
	if func.return_type != ast.void_type {
		sb.write_string(' ${t.type_to_str_using_aliases(func.return_type, import_aliases)}')
	}
	return sb.str()
}

pub fn (t &TypeSymbol) embed_name() string {
	// main.Abc<int> => Abc<int>
	mut embed_name := t.name.split('.').last()
	// remove generic part from name
	// Abc<int> => Abc
	if embed_name.contains('<') {
		embed_name = embed_name.split('<')[0]
	}
	return embed_name
}

pub fn (t &TypeSymbol) has_method(name string) bool {
	t.find_method(name) or { return false }
	return true
}

pub fn (t &TypeSymbol) find_method(name string) ?Fn {
	for method in t.methods {
		if method.name == name {
			return method
		}
	}
	return none
}

pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn {
	if m := t.find_method(name) {
		return m
	}
	mut table := global_table
	match t.info {
		Struct, Interface, SumType {
			if t.info.parent_type.has_flag(.generic) {
				parent_sym := table.get_type_symbol(t.info.parent_type)
				if x := parent_sym.find_method(name) {
					match parent_sym.info {
						Struct, Interface, SumType {
							mut method := x
							generic_names := parent_sym.info.generic_types.map(table.get_type_symbol(it).name)
							if rt := table.resolve_generic_to_concrete(method.return_type,
								generic_names, t.info.concrete_types)
							{
								method.return_type = rt
							}
							method.params = method.params.clone()
							for mut param in method.params {
								if pt := table.resolve_generic_to_concrete(param.typ,
									generic_names, t.info.concrete_types)
								{
									param.typ = pt
								}
							}
							method.generic_names.clear()
							return method
						}
						else {}
					}
				} else {
				}
			}
		}
		else {}
	}
	return none
}

pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
	mut has_str_method := false
	mut expects_ptr := false
	mut nr_args := 0
	if sym_str_method := t.find_method('str') {
		has_str_method = true
		nr_args = sym_str_method.params.len
		if nr_args > 0 {
			expects_ptr = sym_str_method.params[0].typ.is_ptr()
		}
	}
	return has_str_method, expects_ptr, nr_args
}

pub fn (t &TypeSymbol) find_field(name string) ?StructField {
	match t.info {
		Aggregate { return t.info.find_field(name) }
		Struct { return t.info.find_field(name) }
		Interface { return t.info.find_field(name) }
		SumType { return t.info.find_field(name) }
		else { return none }
	}
}

fn (a &Aggregate) find_field(name string) ?StructField {
	for field in a.fields {
		if field.name == name {
			return field
		}
	}
	return none
}

pub fn (i &Interface) find_field(name string) ?StructField {
	for field in i.fields {
		if field.name == name {
			return field
		}
	}
	return none
}

pub fn (i &Interface) find_method(name string) ?Fn {
	for method in i.methods {
		if method.name == name {
			return method
		}
	}
	return none
}

pub fn (i &Interface) has_method(name string) bool {
	if _ := i.find_method(name) {
		return true
	}
	return false
}

pub fn (s Struct) find_field(name string) ?StructField {
	for field in s.fields {
		if field.name == name {
			return field
		}
	}
	return none
}

pub fn (s Struct) get_field(name string) StructField {
	if field := s.find_field(name) {
		return field
	}
	panic('unknown field `$name`')
}

pub fn (s &SumType) find_field(name string) ?StructField {
	for field in s.fields {
		if field.name == name {
			return field
		}
	}
	return none
}

pub fn (i Interface) defines_method(name string) bool {
	for method in i.methods {
		if method.name == name {
			return true
		}
	}
	return false
}
