/////////////////////////////////////
//~ nbr: 
//

/////////////////////////////////////
//~ nbr: Error reporting TODOs
//
// [ ] Add and error for using keywords as names, or rename the dx11 keywords in the resulting hlsl shader.
// [ ] Add missing scope in for loop for loop iterator
// [x] Improve error reporting on mismatched overloads when types don't match, but arity does.
// [x] Improve error reporting for type mismatches in general. It seems like the expect node is not always correct.

VERTEX_MAIN_FUNCTION_PREFIX :: "vertex";
PIXEL_MAIN_FUNCTION_PREFIX  :: "pixel";
PROPERTIES_PREFIX           :: "properties";

Type_Kind :: enum {	
	Invalid :: 128;

	Int       :: 0;
	Half      :: 1;
	Float     :: 2;
	Double    :: 3;
	Texture2D :: 4;
	Sampler   :: 5;

	Bool      :: 6;

	Max_Builtin :: Sampler + 1;

	Unit;
	Function;
	Call;
	
	Unresolved_Variable;
	Unresolved_Expression;
	
	Struct;
	CBuffer;
	Array;
}

Source_Kind :: enum {
	Expression;
	Declaration; // struct, cbuffers, function, etc.
}

Typenames :: string.[
	"int"      ,
	"half"     ,
	"float"    ,
	"double"   ,
	"Texture2D",
	"Sampler"  ,
	"bool"     ,
];

//@Incomplete(niels): We can vastly simplify type handling
//                    There's really no reason to have these type variables anymore now that we don't do union find
//                    We can just have the built-in types and then we can declare structs, functions, buffers etc. as one time things.
Type_Variable :: struct {
	type : Type_Kind;
	source_kind : Source_Kind;
	builtin : bool;

	name : string;

	//@Note(niels) For functions
	return_type_variable : Type_Variable_Handle;

	//@Note(niels) The scope this variable creates (function, struct, global)
	scope : Scope_Handle;

	//@Note(niels): For struct members
	struct_field_parent : *AST_Node;

	typename : string;
	// is_array : bool;
	element_type     : Type_Kind;
	element_typename : string;
	
	MAX_TYPE_VARIABLE_CHILDREN :: 32;
	children : Static_Array(Type_Variable_Handle, MAX_TYPE_VARIABLE_CHILDREN);

	//@Note(niels): For constant buffers
	resource_index : u32;

	source_node : *AST_Node;
}

Type_Variable_Handle   :: #type, distinct u32;

Scope_Stack :: struct {
	allocator : Allocator;
	stack : [..]Scope;
}

Defined_Symbol :: struct {
	name : string;

	type_variable : Type_Variable_Handle;
	source_node : *AST_Node;

	functions : [..]Defined_Symbol; 
	
	builtin : bool;
}

Scope_Kind :: enum {
	Global;
	Function;
	Struct;
}

Scope :: struct {
	table : Table(string, Defined_Symbol);

	//@Note(nb): Only for pretty printing
	longest_key_length : int;

	name     : string;
	children : [..]Scope_Handle;
	parent   : Scope_Handle;

	builtin  : bool;

	kind : Scope_Kind;

	allocator : Allocator;
}

Scope_Handle :: #type, distinct u32;

Checker_State :: enum {
	Type_Checking;
	Adding_Builtins;
}

Checker :: struct {
	program_root : *AST_Node;
	path     : string;

	state : Checker_State;

	current_scope : Scope_Handle;

	ctx : *Compiler_Context;

	current_buffer_index   : u32 = 0;
	current_sampler_index  : u32 = 0;
	current_texture_index  : u32 = 0;

	had_error         : bool;
}

record_error :: (checker : *Checker, message : string, source_location : Source_Range, report_source_location : bool = true) {
	locations : [1]Source_Range;
	locations[0] = source_location;
	record_error(checker, message, locations, report_source_location);
}

invalid_symbol_name :: (checker : *Checker, node : *AST_Node, type : string) {
	record_error(checker, tprint("Invalid % name '%'", type, node.name), node.source_location);
}

symbol_redeclaration :: (checker : *Checker, redeclared_node : *AST_Node, symbol : *Defined_Symbol) {
	/*

    Redeclaration of '%':

       foo : int;
       ^^^

    Here is the first redeclaration of 'foo':
    
       foo : int;
       ^^^

*/

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Redeclaration of '%'\n", symbol.name);

	cyan(*builder);
	indent(*builder, 1);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, redeclared_node.source_location));
	
	indent(*builder, 1);
	print_token_pointer(*builder, redeclared_node.source_location.main_token);
	append(*builder, "\n\n");

	white(*builder);
	path := checker.path;
	if path.count > 0 {
		print_to_builder(*builder, "%:", checker.path);
	} else {
		append(*builder, "internal:");
	}

	if symbol.source_node {
		location := symbol.source_node.source_location;
		print_to_builder(*builder, "%,%: info: ", location.begin.line, location.begin.column);
		print_to_builder(*builder, "Here is the first declaration of '%'\n", symbol.name);

		cyan(*builder);
		indent(*builder, 1);
		print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, location));
		indent(*builder, 1);
		print_token_pointer(*builder, symbol.source_node.source_location.main_token);
	}


	message := builder_to_string(*builder);
	
	record_error(checker, message, redeclared_node.source_location, false);
}

symbol_undeclared :: (checker : *Checker, node : *AST_Node) {
	/*
Use of undeclard symbol '%'.
   b = f;
   ^
*/

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Use of undeclared symbol '%'\n", node.name);

	cyan(*builder);
	indent(*builder, 1);

	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, node.source_location));
	indent(*builder, 1);
	print_token_pointer(*builder, node.source_location.main_token);

	message := builder_to_string(*builder);
	
	record_error(checker, message, node.source_location, false);
}

no_matching_overload_found :: (checker : *Checker, call : *AST_Node, overloads : *Defined_Symbol, arg_node : *AST_Node = null) {
	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Procedure call did not match any of the possible overloads for '%'\n", overloads.name);

	cyan(*builder);
	indent(*builder, 1);
	append(*builder, "found:\n");
	indent(*builder, 2);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, call.source_location));
	indent(*builder, 2);
	print_token_pointer(*builder, call.source_location.main_token);
	newline(*builder);

	if arg_node {
		newline(*builder);

		white(*builder);
		
		location := arg_node.source_location;
		indent(*builder, 1);

		field_list := arg_node.parent;
		index : s64 = -1;
		for child : field_list.children {
			if child == arg_node {
				index = it_index + 1;
				break;
			}
		}
		print_to_builder(*builder, "While matching argument % in function call.\n", index);

		cyan(*builder);
		indent(*builder, 2);
		print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, location));
		indent(*builder, 2);
		print_token_pointer(*builder, arg_node.source_location.main_token);
		newline(*builder);
	} 

	white(*builder);
	indent(*builder, 1);
	append(*builder, "Possible overloads:\n");

	for func : overloads.functions {
		func_var := from_handle(checker, func.type_variable);

		cyan(*builder);
		// foo :: (f : float) {} (file_path:line_num)
		func_location := func_var.source_node.source_location;
		indent(*builder, 2);

		if func.builtin {
			print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, func_location));
		} else {
			print_to_builder(*builder, "% (%:%)\n", print_from_source_location(checker.ctx, func_location), checker.path,
							 func_location.main_token.line);
		}
		// @Incomplete(niels): We need a way to properly save the path of the declaration
		

		if !arg_node {
			white(*builder);
			
			arg_list := call.children[0];
			indent(*builder, 2);
			
			func_var := from_handle(checker, func.type_variable);

			if arg_list.children.count != func_var.children.count {
				print_to_builder(*builder, "Not enough arguments: Wanted %, got %.\n\n", func_var.children.count, arg_list.children.count);
			}
		}
	}

	locations : [1]Source_Range;
	locations[0] = call.source_location;
	message := builder_to_string(*builder);
	record_error(checker, message, locations, false);
}


not_all_control_paths_return_value :: (checker : *Checker, node : *AST_Node) {
	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Not all control paths return a value.\n\n");

	cyan(*builder);

	indent(*builder, 1);

	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, node.source_location));
	indent(*builder, 1);
	print_token_pointer(*builder, node.token);

	white(*builder);

	message := builder_to_string(*builder);
	
	record_error(checker, message, node.source_location, false);
}

mismatched_arguments :: (checker : *Checker, call : *AST_Node, symbol : *Defined_Symbol, args_call : int, args_def : int) {
	builder : String_Builder;
	init_string_builder(*builder,, temp);
	
	if args_call > args_def {
		print_to_builder(*builder, "Too many arguments: Wanted %, got %\n", args_def, args_call);
	} else {
		print_to_builder(*builder, "Too few arguments: Wanted %, got %\n", args_def, args_call);
	}

	/*
Too many arguments: Expected %, got %.

     found
            foo(2, 3)
            ^^^

     expected
            foo :: (x : int, y : int, z : int)

*/
	cyan(*builder);
	indent(*builder, 1);
	append(*builder, "found:\n");
	indent(*builder, 2);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, call.source_location));
	indent(*builder, 2);
	print_token_pointer(*builder, call.source_location.main_token);
	append(*builder, "\n\n");
	indent(*builder, 1);
	append(*builder, "expected:\n");

	indent(*builder, 2);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, symbol.source_node.source_location));
	
	locations : [1]Source_Range;
	locations[0] = call.source_location;

	message := builder_to_string(*builder);
	record_error(checker, message, locations, false);
}

function_undeclared :: (checker : *Checker, node : *AST_Node) {
	/*
Error: Undeclared identifier 'name'.

       name();
       ^^^^

*/
	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Attempt to call undeclared function '%'.\n\n", node.name);
	cyan(*builder);
	indent(*builder, 1);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, node.source_location));
	indent(*builder, 1);
	print_token_pointer(*builder, node.source_location.main_token);
	newline(*builder);
	newline(*builder);
	message := builder_to_string(*builder);
	record_error(checker, message, node.source_location, false);
}

field_not_defined_on_struct :: (checker : *Checker, node : *AST_Node, struct_symbol : *Defined_Symbol) {
	/*
Field '%' is not defined in struct '%'.
    b.t = f;
      ^

    declaration:
          Bar :: struct {
               f : Foo;
          }
*/	

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	print_to_builder(*builder, "Field '%' is not defined in struct '%'.\n", node.name, struct_symbol.name);

	cyan(*builder);
	indent(*builder, 1);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, node.source_location));
	indent(*builder, 1);
	print_token_pointer(*builder, node.source_location.main_token);
	newline(*builder);
	newline(*builder);

	indent(*builder, 1);
	append(*builder, "declaration:\n");
	print_from_source_location(checker.ctx, *builder, struct_symbol.source_node.source_location, 2);

	message := builder_to_string(*builder);
	record_error(checker, message, node.source_location, false);
}

field_access_on_primitive_type :: (checker : *Checker, node : *AST_Node, handle : Type_Variable_Handle) {
	/*
Attempting to access a field on a primitive type '%'.
        x.d = 5;
         ^^

        declaration:
                 x : int = 5;
*/

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	variable := from_handle(checker, handle);
	print_to_builder(*builder, "Attempting to access a field on a primitive type '%'.\n", proper_type_to_string(checker.ctx, checker.ctx.type_variables, variable));

	indent(*builder, 1);
	cyan(*builder);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, node.source_location));
	indent(*builder, 1);

	node_variable := from_handle(checker, node.type_variable);

	for 0..node.name.count - 1 {
		append(*builder, " ");
	}
	print_token_pointer(*builder, node.source_location.begin);
	
	append(*builder, "\n");

	indent(*builder, 1);
	append(*builder, "declaration:\n");

	indent(*builder, 2);
	print_to_builder(*builder, "%", print_from_source_location(checker.ctx, variable.source_node.source_location));

	message := builder_to_string(*builder,, temp);
	record_error(checker, message, node.source_location, false);

	white(*builder);
}

if_condition_has_to_be_boolean_type :: (checker : *Checker, usage_site : *AST_Node, handle : Type_Variable_Handle) {
	/*
    Type of expression in if condition has to be bool.
       if 100.0
          ^^^^^^^

       100.0 has type float
    */

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	variable := from_handle(checker, handle);
	append(*builder, "Type of expression in if condition has to be bool.\n");

	indent(*builder, 1);
	cyan(*builder);

	location := usage_site.source_location;
	
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, location));
	indent(*builder, 1);

	print_token_pointer(*builder, usage_site.children[0].source_location.begin);
	append(*builder, "\n");	

	indent(*builder, 1);

	var := from_handle(checker, handle);

	usage_child := usage_site.children[0];
	usage_loc   := usage_child.source_location;
	
	print_to_builder(*builder, "% has type %\n", print_from_source_location(checker.ctx, *usage_loc), proper_type_to_string(checker.ctx, checker.ctx.type_variables, var));
	
	message := builder_to_string(*builder,, temp);
	record_error(checker, message, usage_site.source_location, false);

	white(*builder);
}

type_mismatch :: (checker : *Checker, usage_site : *AST_Node, expect_node : *AST_Node, expect : Type_Variable_Handle, got : Type_Variable_Handle) {
	expect_var := from_handle(checker, expect);
	got_var := from_handle(checker, got);

	builder : String_Builder;
	init_string_builder(*builder,, temp);

	if got_var.builtin {
		print_to_builder(*builder, "% :: (", got_var.name);

		for i: 0..got_var.children.count - 1{
			child_handle := got_var.children[i];
			child := from_handle(checker, child_handle);

			print_to_builder(*builder, "% : %", child.name, type_to_string(child));
		}
	}

	print_to_builder(*builder, "Type mismatch. Expected % got %\n", type_to_string(expect_var), type_to_string(got_var));

	cyan(*builder);
	location := usage_site.source_location;
	indent(*builder, 1);
	append(*builder, "found:\n");
	indent(*builder, 2);
	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, location));
	indent(*builder, 2);

	print_token_pointer(*builder, location.main_token);
	append(*builder, "\n");

	indent(*builder, 1);
	print_to_builder(*builder, "expected:\n");
	indent(*builder, 2);
	proper_type_to_string(checker.ctx, *builder, checker.ctx.type_variables, expect_var);
	append(*builder, "\n");
	
	// indent(*builder, 2);

	{
		// expect_location := expect_var.source_node.source_location;
		// // token.length = expect_location.end.index + token.length - token.index + expect_location.end.length - 1;

		// print_token_pointer(*builder, expect_location.main_token);
		append(*builder, "\n");
	}

	indent(*builder, 1);
	print_to_builder(*builder, "got:\n");

	indent(*builder, 2);

	print_to_builder(*builder, "%\n", print_from_source_location(checker.ctx, got_var.source_node.source_location));

	message := builder_to_string(*builder);

	record_error(checker, message, Source_Range.[usage_site.source_location], false);
}

record_error :: (checker : *Checker, error_string : string, locations : []Source_Range, report_source_location : bool = true) {
	error : Compiler_Message;
	error.message_kind = .Error;
	error.report_source_location = report_source_location;

	for location : locations {
		array_add(*error.source_locations, location);
	}
	
	error.path = checker.path;
	error.message = error_string;
	
	checker.had_error = true;
 	array_add(*checker.ctx.messages, error);
}

is_proper :: (var : Type_Variable) -> bool {
	if var.type == {
		case .Function; #through;
		case .Int;      #through;
		case .Struct;   #through;
		case .Float;    {
			return true;
		}
	}
	return false;
}

use_scope :: (checker : *Checker, handle : Scope_Handle) -> Scope_Handle {
	assert(handle > 0, "Invalid scope handle: %", handle);
	previous_scope := checker.current_scope;
	checker.current_scope = handle;

	return previous_scope;
}

push_scope :: (checker : *Checker, name := "", kind : Scope_Kind = .Global) -> *Scope, Scope_Handle {
	new_scope : Scope;
	array_add(*checker.ctx.scope_stack.stack, new_scope);

	count := checker.ctx.scope_stack.stack.count;
	scope := *checker.ctx.scope_stack.stack[count - 1];
	scope.allocator = make_arena(Kilobytes(512));
	scope.table.allocator = scope.allocator;
	scope.parent = checker.current_scope;
	scope.name   = name;
	scope.kind   = kind;
	if checker.state == .Adding_Builtins {
		scope.builtin = true;
	}

	scope.children.allocator = checker.ctx.scope_stack.stack.allocator;

	if checker.current_scope {
		scope := get_current_scope(checker);
		array_add(*scope.children, xx count);
	}
	
	checker.current_scope = xx count;
	return scope, xx count;
}

pop_scope :: (checker : *Checker) -> Scope_Handle {
	scope := get_scope(checker, checker.current_scope);
	if !scope.parent {
		return 0;
	}
	
	checker.current_scope = scope.parent;

	return checker.current_scope;
}

peek_scope :: (checker : *Checker) -> *Scope, Scope_Handle {
	if checker.ctx.scope_stack.stack.count == 0 {
		return null, 0;
	}

	count := checker.ctx.scope_stack.stack.count;
	scope := *checker.ctx.scope_stack.stack[count - 1];
	return scope, xx count;
}

get_current_scope :: (checker : *Checker) -> *Scope {
	return get_scope(checker, checker.current_scope);
}

get_scope :: (scope_stack : Scope_Stack, handle : Scope_Handle) -> *Scope {
	if handle == 0 {
		return null;
	}

	return *scope_stack.stack[handle - 1];
}

get_scope :: (checker : *Checker, handle : Scope_Handle) -> *Scope {
	return get_scope(*checker.ctx.scope_stack, handle);
}

add_symbol_to_scope :: (state : Checker_State, scope_stack : *Scope_Stack, scope_handle : Scope_Handle, name : string, symbol : Defined_Symbol) -> *Defined_Symbol {
	scope := get_scope(scope_stack.*, scope_handle);
	scope.longest_key_length = max(scope.longest_key_length, name.count);

	symbol_to_add : Defined_Symbol;
	symbol_to_add.name          = symbol.name;
	symbol_to_add.type_variable = symbol.type_variable;
	symbol_to_add.source_node   = symbol.source_node;
	symbol_to_add.functions     = symbol.functions;

	if state == .Adding_Builtins {
		symbol_to_add.builtin = true;
	}

	return table_set(*scope.table, name, symbol_to_add);
}

new_type_variable :: (checker : *Checker) -> *Type_Variable, Type_Variable_Handle {
	variable : Type_Variable;
	handle := cast(Type_Variable_Handle)checker.ctx.type_variables.count + 1;
	array_add(*checker.ctx.type_variables, variable);
	
	return from_handle(checker, handle), handle;
}

new_builtin_type_variable :: (checker : *Checker, type : Type_Kind, source : Source_Kind, name : string, typename : string = "") -> *Type_Variable, Type_Variable_Handle {
	tv, handle := new_type_variable(checker);

	tv.name        = name;
	tv.type        = type;
	tv.source_kind = source;
	tv.builtin     = true;
	tv.typename    = typename;

	return tv, handle;
}

Arg :: struct {
	name     : string;
	typename : string;
}

new_builtin_struct :: (checker : *Checker, name : string, members : []Arg) -> *Type_Variable, Type_Variable_Handle {
	tv, handle := new_builtin_type_variable(checker, .Struct, .Declaration, name, name);

	builtin_node := new_builtin_struct_node(checker.ctx, name, members);
	
	symbol : Defined_Symbol;
	symbol.name = name;
	symbol.source_node = builtin_node;
	symbol.builtin = true;
	symbol.type_variable = handle;
	add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, name, symbol);

	tv.source_node = builtin_node;

	field_list := get_field_list(builtin_node);

	scope, scope_handle := push_scope(checker, name, .Struct);
	tv.scope = scope_handle;

	for member : members {
		typename : string;
		kind := lookup_type(checker, checker.current_scope, member.typename, *typename);

		member_var, member_handle := new_builtin_type_variable(checker, kind, .Expression, member.name);
		member_var.scope = tv.scope;

		member_symbol : Defined_Symbol;
		member_symbol.name = member.name;
		member_symbol.type_variable = member_handle;
		add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, member.name, member_symbol);

		field_list.children[it_index].type_variable = member_handle;
		member_var.source_node = field_list.children[it_index];

		add_child(checker, handle, member_handle);
	}

	pop_scope(checker);

	return from_handle(checker, handle), handle;
}

new_builtin_function :: (checker : *Checker, name : string, args : []Arg, return_arg : Arg) -> *Type_Variable, Type_Variable_Handle {
	tv, handle := new_builtin_type_variable(checker, .Function, .Declaration, name);

	builtin_node := new_builtin_function_node(checker.ctx, name, args, return_arg);

	function : Defined_Symbol;
	function.name = name;
	function.source_node = builtin_node;
	function.type_variable = handle;
	function.builtin = true;

	find_result := find_symbol(checker, name, checker.current_scope);
	if !find_result {
		symbol : Defined_Symbol;
		symbol.name = name;
		symbol.source_node = builtin_node;
		symbol.builtin = true;
		symbol.type_variable = 0;
		symbol.functions.allocator = get_current_scope(checker).allocator;
		array_add(*symbol.functions, function);
		add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, name, symbol);
	} else {
		array_add(*find_result.functions, function);
	}

	tv.source_node = builtin_node;

	field_list := get_field_list(builtin_node);

	for arg : args {
		typename : string;
		kind := lookup_type(checker, checker.current_scope, arg.typename, *typename);

		arg_var, arg_handle := new_builtin_type_variable(checker, kind, .Expression, arg.name);
		arg_var.scope    = tv.scope;
		arg_var.type     = kind;
		arg_var.typename = typename;

		field_list.children[it_index].type_variable = arg_handle;
		arg_var.source_node = field_list.children[it_index];

		add_child(checker, handle, arg_handle);
	}

	if return_arg.typename.count > 0 {
		return_var, return_handle := new_type_variable(checker);
		return_var.type = lookup_type(checker, checker.current_scope, return_arg.typename, *return_var.typename);
		from_handle(checker, handle).return_type_variable = return_handle;
	}

	return from_handle(checker, handle), handle;
}

add_child :: (variable : *Type_Variable, child : Type_Variable_Handle) {
	assert(variable.children.count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
	array_add(*variable.children, child);
	// variable.children[variable.children.count] = child;
	// variable.children.count += 1;
}

add_child :: (checker : *Checker, handle : Type_Variable_Handle, child : Type_Variable_Handle) {
	variable := from_handle(checker, handle);
	assert(variable.children.count < Type_Variable.MAX_TYPE_VARIABLE_CHILDREN);
	array_add(*variable.children, child);
}

init_semantic_checker :: (checker : *Checker, root : *AST_Node, path : string) {
	checker.program_root = root;
	checker.path         = path;

	array_reserve(*checker.ctx.type_variables, 2048);

	checker.ctx.scope_stack.stack.allocator = checker.ctx.scope_stack.allocator;
	array_reserve(*checker.ctx.scope_stack.stack, 256);

	global_scope, global_handle := push_scope(checker, kind = .Global);
	array_reserve(*global_scope.children, 2048);
}

find_symbol :: (scope_stack : Scope_Stack, name : string, current_scope : Scope_Handle, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
	handle := current_scope;
	current := get_scope(scope_stack, current_scope);

	while current {
		table := current.table;
		info := table_find_pointer(*table, name);
		if info {
			if containing_scope {
				containing_scope.* = handle;
			}
			return info;
		}
		handle = current.parent;
		current = get_scope(scope_stack, current.parent);
	}
	return null;
}

find_symbol :: (checker : *Checker, name : string, current_scope : Scope_Handle, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
	return find_symbol(checker.ctx.scope_stack, name, current_scope, containing_scope);
}

find_symbol :: (name : string, checker : *Checker, containing_scope : *Scope_Handle = null) -> *Defined_Symbol {
	return find_symbol(checker, name, checker.current_scope, containing_scope);
}

from_handle :: (variables : []Type_Variable, handle : Type_Variable_Handle) -> *Type_Variable {
	// assert(handle > 0 && xx handle <= variables.count, tprint("Invalid handle: %. Range is: 1-%", handle, variables.count - 1));
	return *variables[handle - 1];
}

from_handle :: (checker : *Checker, handle : Type_Variable_Handle) -> *Type_Variable {
	return from_handle(checker.ctx.type_variables, handle);
}

proper_type_to_string :: (ctx : *Compiler_Context, builder : *String_Builder, variables : []Type_Variable, var : Type_Variable) {
	if var.type == {
		case .Int; #through;
		case .Half; #through;
		case .Float; #through;
		case .Double; {
			print_to_builder(builder, "%", Typenames[var.type]);
		}
		case .Struct; {
			print_to_builder(builder, "%", var.typename);
		}
		case .Function; {

			append(builder, "(");

			for child : var.source_node.children {
				if child.kind == .FieldList {
					for field : child.children {
						var := field.type_variable;
						print_type_variable(ctx, builder, variables, var);

						if it_index != child.children.count - 1 {
							append(builder, ", ");
						}
					}
				}
			}
			
			append(builder, ")");

			if var.return_type_variable > 0 {
				append(builder, " -> ", );
				return_var := from_handle(variables, var.return_type_variable);
				if is_proper(return_var) {
					proper_type_to_string(ctx, builder, variables, return_var);
				} else {
					append(builder, "[[");
					print_type_variable(ctx, builder, variables, var.return_type_variable);
					append(builder, "]]", );
				}
			} else {
				append(builder, " -> unit");
			}
		}
		case; {
			append(builder, "______not proper type______");
		}
	}
}

proper_type_to_string :: (ctx : *Compiler_Context, variables : []Type_Variable, var : Type_Variable, allocator := context.allocator) -> string {
	if var.type == {
		case .Int; #through;
		case .Half; #through;
		case .Float; #through;
		case .Double; {
			return Typenames[var.type];
		}
		case .Function; {
			builder : String_Builder;
			init_string_builder(*builder,, allocator);

			proper_type_to_string(ctx, *builder, variables, var);
			return builder_to_string(*builder,, allocator);
		}
		case .Struct; {
			return var.typename;
		}
	}

	return "______not proper type______";
}

lookup_type :: (checker : *Checker, scope : Scope_Handle, type_string : string, typename : *string = null) -> Type_Kind {
	if type_string == {
		case Typenames[Type_Kind.Int];       return .Int;
		case Typenames[Type_Kind.Half];      return .Half;
		case Typenames[Type_Kind.Float];     return .Float; 
		case Typenames[Type_Kind.Double];    return .Double;		
		case Typenames[Type_Kind.Sampler];   return .Sampler;
		case Typenames[Type_Kind.Texture2D]; return .Texture2D;
	}
	
	symbol := find_symbol(checker, type_string, scope);
	if symbol {
		symbol_var := from_handle(checker, symbol.type_variable);
		if symbol_var.type == .Struct {
			if typename {
				typename.* = symbol_var.typename;
			}
			return symbol_var.type;
		}
	} else {
		return .Unresolved_Variable;
	}

	return .Invalid;
}

lookup_type :: (checker : *Checker, scope : Scope_Handle, node : *AST_Node, typename : *string = null) -> Type_Kind {
	type_string := node.token.ident_value;
	return lookup_type(checker, scope, type_string, typename);
}

check_block :: (checker : *Checker, node : *AST_Node) {
	for child : node.children {
		check_node(checker, child);
	}
}

declare_struct :: (checker : *Checker, node : *AST_Node, name : string) -> Type_Variable_Handle {
	variable, handle := new_type_variable(checker);
	variable.type        = .Struct;
	variable.source_kind = .Declaration;
	variable.name        = name;
	variable.source_node = node;
	variable.typename    = name;
	node.type_variable = handle;

	find_result := find_symbol(checker, name, checker.current_scope);

	if !find_result {
		symbol : Defined_Symbol;
		symbol.name = name;
		symbol.source_node = node;
		symbol.type_variable = handle;
		add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, name, symbol);
	} else {
		symbol_redeclaration(checker, node, find_result);
		return 0;
	}

	scope, scope_handle := push_scope(checker, name, .Struct);
	variable.scope = scope_handle;

	for child : node.children {
		if child.kind == .FieldList {
			for field : child.children {
				type_var := check_field(checker, field);

				if type_var > 0 {
					from_handle(checker, type_var).scope = scope_handle;
					add_child(checker, handle, type_var);
				}
			}
		}
	}

	pop_scope(checker);
	
	return handle;
}

declare_struct :: (checker : *Checker, node : *AST_Node) -> Type_Variable_Handle {
	return declare_struct(checker, node, node.name);
}

declare_cbuffer :: (checker : *Checker, node : *AST_Node) -> Type_Variable_Handle {
	type_var := declare_struct(checker, node);
	var := from_handle(checker, type_var);
	var.type = .CBuffer;
	var.resource_index = checker.current_buffer_index;
	checker.current_buffer_index += 1;
	array_add(*checker.ctx.constant_buffers, type_var);
	return type_var;
}

get_actual_function_name :: (node : *AST_Node) -> string {
	name_to_check := node.name;
	if node.vertex_entry_point {
		name_to_check = sprint("%__%", VERTEX_MAIN_FUNCTION_PREFIX, node.name);
	} else if node.pixel_entry_point {
		name_to_check = sprint("%__%", PIXEL_MAIN_FUNCTION_PREFIX, node.name);
	}
	return name_to_check;
}

declare_foreign_function :: (checker : *Checker, node : *AST_Node) -> Type_Variable_Handle {
	return declare_function(checker, node, true);
}

declare_function :: (checker : *Checker, node : *AST_Node, builtin : bool = false) -> Type_Variable_Handle {
	if !node.foreign_declaration && !can_declare(checker, node.name) {
		invalid_symbol_name(checker, node, "function");
		return 0;
	}
	
	variable, handle := new_type_variable(checker);
	variable.type        = .Function;
	variable.source_kind = .Declaration;
	variable.name        = node.name;
	variable.source_node = node;
	variable.builtin     = builtin;
	node.type_variable = handle;

	name_to_check := get_actual_function_name(node);

	if node.vertex_entry_point {
		checker.ctx.vertex_entry_point.node = node;
	}

	if node.pixel_entry_point {
		checker.ctx.pixel_entry_point.node = node;
	}
	find_result := find_symbol(checker, name_to_check, checker.current_scope);

	if !find_result {
		function : Defined_Symbol;
		function.name = name_to_check;
		function.source_node = node;
		function.type_variable = handle;

		symbol : Defined_Symbol;
		symbol.name = name_to_check;
		symbol.source_node = node;
		symbol.type_variable = 0;
		symbol.functions.allocator = get_current_scope(checker).allocator;
		array_reserve(*symbol.functions, 32);
		array_add(*symbol.functions, function);

		add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, name_to_check, symbol);
	} else {
		//@Note(niels): This is some ugly code, but it's probably fine for now.
		field_list := node.children[0];

		for function : find_result.functions {
			func_var := from_handle(checker, function.type_variable);
			if func_var.source_node.children[0].children.count != field_list.children.count {
				continue;
			}

			all_same : bool = true;
			for i : 0..func_var.children.count - 1 {
				arg := func_var.children[i];
				node_child := field_list.children[i];

				typename : string;
				arg_type := lookup_type(checker, checker.current_scope, node_child, *typename);
				other_arg := from_handle(checker, arg);

				if arg_type != other_arg.type {
					all_same = false;
					break;
				} else {
					if arg_type == .Struct && other_arg.type == .Struct {
						if typename != other_arg.typename {
							all_same = false;
							break;
						}
					}
				}
			}

			if all_same {
				symbol_redeclaration(checker, node, find_result);
				return 0;
			} else {
				function : Defined_Symbol;
				function.name = name_to_check;
				function.source_node = node;
				function.type_variable = handle;

				array_add(*find_result.functions, function);
				break;
			}
		}

		function : Defined_Symbol;
		function.name = name_to_check;
		function.source_node = node;
		function.type_variable = handle;
		
		array_add(*find_result.functions, function);
	}

	if !builtin {
		scope, scope_handle := push_scope(checker, name_to_check, .Function);
		variable.scope = scope_handle;
	}

	for child : node.children {
		if child.kind == .FieldList {
			for field : child.children {
				type_var := check_node(checker, field);
				if type_var > 0 {
					if builtin {
						var := from_handle(checker, type_var);
						var.builtin = true;
					}
					add_child(checker, handle, type_var);
				}
			}
		}
	}

	if builtin && node.token.ident_value.count > 0 {
		return_var, return_handle := new_type_variable(checker);
		return_var.type = lookup_type(checker, checker.current_scope, node, *return_var.typename);
		from_handle(checker, handle).return_type_variable= return_handle;
	}

	if !builtin {
		pop_scope(checker);
	}

	return handle;
}

check_function :: (checker : *Checker, node : *AST_Node) {
	name_to_check := get_actual_function_name(node);
	find_result := find_symbol(checker, name_to_check, checker.current_scope);

	if !find_result {
		assert(false, "Compiler error. Functions should all be declared at this point in time.");
	}

	handle := find_result.type_variable;
	if node.foreign_declaration {
		return;
	}

	for function : find_result.functions {
		variable := from_handle(checker, function.type_variable);

		assert(variable.scope > 0, "Declared function is missing scope.");

		previous_scope := use_scope(checker, variable.scope);

		for child : node.children {
			if child.kind == .Block {
				for statement : child.children {
					if statement.kind == .Return {
						result_var := check_node(checker, statement);
						if result_var > 0 {
							variable.return_type_variable = result_var;
						}
					} else {
						result_var := check_node(checker, statement);
						if result_var > 0 {
							stm := from_handle(checker, result_var);
							add_child(variable, result_var);
						}

						if statement.kind == .If_Directive {
							break;
						}
					}
				}
			}
		}

		if node.token.ident_value.count > 0 && !variable.return_type_variable{
			not_all_control_paths_return_value(checker, node);
		}

		use_scope(checker, previous_scope);
	}
}

check_variable :: (checker : *Checker, node : *AST_Node, struct_field_parent : *AST_Node = null) -> Type_Variable_Handle {
	find_result := find_symbol(checker, node.name, checker.current_scope);
	// x : int;
	// x.d = 5;

	if find_result {
		node.type_variable = find_result.type_variable;
		variable := from_handle(checker, find_result.type_variable);
		variable.struct_field_parent = struct_field_parent;

		if get_scope(checker, checker.current_scope).kind == .Struct {
			variable.scope = checker.current_scope;
		}

		if node.children.count > 0 {
			if variable.type != .Struct && variable.type != .CBuffer {
				field_access_on_primitive_type(checker, node, find_result.type_variable);
				return 0;
			} else if variable.type == .Array {
				
			} else {
				lookup_name : string = variable.typename;
				struct_symbol := find_symbol(checker, lookup_name, checker.current_scope);
				type_variable := from_handle(checker, struct_symbol.type_variable);

				previous_scope := use_scope(checker, type_variable.scope);
				child := node.children[0];
				var := find_symbol(checker, child.name, type_variable.scope);
				if var == null {
					field_not_defined_on_struct(checker, child, struct_symbol);
					return 0;
				}
				access := check_variable(checker, child, node);
				use_scope(checker, previous_scope);
				return access;
			}
		}
		return find_result.type_variable;
	}
	
	symbol_undeclared(checker, node);
	return 0;
}

can_declare :: (checker : *Checker, name : string) -> bool {
	max_value := Type_Kind.Max_Builtin;

	for i : 0..max_value - 1 {
		kind := cast(Type_Kind)i;
		if name == Typenames[kind] {
			return false;
		}
	}
	return true;
}

//@Incomplete(niels): Handle meta stuff here
check_field :: (checker : *Checker, node : *AST_Node) -> Type_Variable_Handle {
	variable, handle := new_type_variable(checker);
	variable.name = node.name;
	typename : string;


	// variable.is_array = node.array_field;
	if node.array_field {
		variable.type = .Array;
		variable.element_type = lookup_type(checker, checker.current_scope, node, *variable.element_typename);
	} else {
		variable.type = lookup_type(checker, checker.current_scope, node, *typename);
	}

	if variable.type == .Array {
		size_node := node.children[0];
		size_var := check_node(checker, size_node);
		if from_handle(checker, size_var).type != .Int {
			//@Incomplete(niels): Type mismatch here. With integral type required message.
		}
	}
	
	variable.typename = typename;
	variable.source_node = node;
	variable.scope = checker.current_scope;
	node.type_variable = handle;

	if node.kind != .Unnamed_Field {
		if !can_declare(checker, node.name) {
			invalid_symbol_name(checker, node, "variable");
			return 0;
		}

		find_result := find_symbol(checker, node.name, checker.current_scope);

		if !find_result {
			symbol : Defined_Symbol;
			symbol.name = node.name;
			symbol.source_node = node;
			symbol.type_variable = handle;
			add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, node.name, symbol);
			variable.source_kind = .Declaration;
		} else {
			symbol_redeclaration(checker, node, find_result);
			return 0;
		}
	}

	if variable.source_kind == .Declaration && variable.type == .Sampler {
		variable.resource_index = checker.current_sampler_index;
		checker.current_sampler_index += 1;
	}

	if variable.source_kind == .Declaration && variable.type == .Texture2D {
		variable.resource_index = checker.current_texture_index;
		checker.current_texture_index += 1;
	}

	if variable.type != .Array && node.children.count > 0 || variable.type == .Array && node.children.count > 1 {
		rhs : Type_Variable_Handle;
		assert(node.children.count == 1);
		start_index := 0;
		if variable.type == .Array {
			start_index += 1;
		}
		
		for i : start_index..node.children.count - 1 {
			child := node.children[i];
			rhs = check_node(checker, child);
		}

		if handle == 0 || rhs == 0 {
			return handle;
		}

		l := from_handle(checker, handle);
		r := from_handle(checker, rhs);
		assert(l.type != .Unresolved_Expression && r.type != .Unresolved_Expression);

		if l.type == .Unresolved_Variable {
			l.type = r.type;
			l.typename = r.typename;
		} else {
			if !types_compatible(checker, handle, rhs) {
				type_mismatch(checker, l.source_node, r.source_node, rhs, handle);
				return 0;
			}
		}
	}

	return handle;
}

check_call :: (checker : *Checker, node : *AST_Node, type_var : Type_Variable_Handle) -> error : bool {
	find_result := find_symbol(checker, node.name, checker.current_scope);

	if !find_result {
		function_undeclared(checker, node);
		return true;
	}

	overload_found := false;
	arg_node : *AST_Node = null;

	Result :: struct {
		var : Type_Variable_Handle;
		node : *AST_Node;
	}
	arg_vars : [..]Result;

	// @incomplete(niels): Should be some kind of scratch allocator instead probably?
	arg_vars.allocator = temp;
	arg_count := 0;

	for child : node.children {
		if child.kind == {
			case .ArgList; {
				arg_count = child.children.count;
				for arg_child : child.children {
					arg_var := check_node(checker, arg_child);
					if arg_var != 0 {
						array_add(*arg_vars, .{ arg_var, arg_child });
					}
				}
			}
		}
	}

	if arg_vars.count != arg_count {
		return true;
	}

	Type_Mismatch_Data :: struct {
		lhs : Result;
		rhs : Result;
	}

	mismatches : [..]Type_Mismatch_Data;
	mismatches.allocator = temp;

	for *func : find_result.functions {
		if overload_found {
			break;
		}
		
		function := from_handle(checker, func.type_variable);

		field_list := function.source_node.children[0];
		if arg_count != field_list.children.count {
			continue;
		}

		if node.children.count == 0 && function.children.count == 0 {
			overload_found = true;
		}

		if overload_found && function.return_type_variable == 0 {
			break;
		}

		all_args_match : bool = true;

		for arg : arg_vars {
			function_param := function.children[it_index];

			if !types_compatible(checker, arg.var, function_param, true) {
				if all_args_match {
					arg_node = arg.node;
				}
				fun_param := from_handle(checker, function_param);
				mismatch : Type_Mismatch_Data;
				mismatch.lhs = arg;
				mismatch.rhs = .{ function_param, fun_param.source_node };
				array_add(*mismatches, mismatch);
				
				all_args_match = false;
				overload_found = false;
			} else {
				overload_found = all_args_match;
			}
		}

		if overload_found {
			if function.return_type_variable > 0 {
				return_var := from_handle(checker, function.return_type_variable);
				constrained_var := from_handle(checker, type_var);
				constrained_var.type     = return_var.type;
				constrained_var.typename = return_var.typename;
			}
		}
	}

	if !overload_found {
		no_matching_overload_found(checker, node, find_result, arg_node);

		for mismatch : mismatches {
			type_mismatch(checker, mismatch.lhs.node, mismatch.rhs.node, mismatch.rhs.var, mismatch.lhs.var);
		}

		return true;
	}

	return false;
}

check_node :: (checker : *Checker, node : *AST_Node) -> Type_Variable_Handle {
	if node.kind == {
		case .Function; {
			check_function(checker, node);
			return 0;
		}
		case. Field; {
			field_var := check_field(checker, node);		
			return field_var;
		}
		case .Unnamed_Field; {
			field_var := check_field(checker, node);
			return field_var;
		}
		case .Unary; {
			var := check_node(checker, node.children[0]);
			variable, handle := new_type_variable(checker);
			type := from_handle(checker, var);
			variable.type = type.type;
			variable.typename = type.typename;
			variable.scope = type.scope;
			variable.source_node = node;
			node.type_variable = handle;
			add_child(variable, var);

			return handle;
		}
		case .Binary; {
			lhs_var := check_node(checker, node.children[0]);
			if lhs_var == 0 {
				return 0;
			}
			rhs_var := check_node(checker, node.children[1]);
			if rhs_var == 0 {
				return 0;
			}

			variable, handle := new_type_variable(checker);
			lhs_type := from_handle(checker, lhs_var);
			rhs_type := from_handle(checker, rhs_var);

			variable.type     = lhs_type.type;
			variable.typename = lhs_type.typename;
			variable.scope    = lhs_type.scope;
			variable.source_node = node;
			node.type_variable = handle;
			add_child(variable, lhs_var);
			add_child(variable, rhs_var);

			if node.token.kind == {
				case .TOKEN_PLUS;  #through;
				case .TOKEN_MINUS; #through;
				case .TOKEN_STAR;  #through;
				case .TOKEN_SLASH; {
					if !types_compatible(checker, lhs_var, rhs_var) {
						type_mismatch(checker, node, node.children[1], lhs_var, rhs_var);
						return 0;
					}

					if lhs_type.type == .Struct {
						variable.type     = lhs_type.type;
						variable.typename = lhs_type.typename;
						variable.scope    = lhs_type.scope;
					} else if rhs_type.type == .Struct {
						variable.type     = rhs_type.type;
						variable.typename = rhs_type.typename;
						variable.scope    = rhs_type.scope;
					}
				}
				case .TOKEN_ASSIGN; {
					if !types_compatible(checker, lhs_var, rhs_var) {
						type_mismatch(checker, node.parent, node.children[1], lhs_var, rhs_var);
						return 0;
					}
				}
				case .TOKEN_GREATER;       #through;
				case .TOKEN_GREATEREQUALS; #through;
				case .TOKEN_LESS;          #through;
				case .TOKEN_LESSEQUALS;    #through;
				case .TOKEN_LOGICALOR;     #through;
				case .TOKEN_ISEQUAL;       #through;
				case .TOKEN_ISNOTEQUAL;    #through;
				case .TOKEN_LOGICALAND; {
					variable.type = .Bool;
					variable.typename = Typenames[variable.type];
				}
			}
			return handle;
		}
		case .Return; {
			return check_node(checker, node.children[0]);
		}
		case .For; {
			loop_iterator := node.token;
			// @Incomplete: Missing scope here?

			symbol : Defined_Symbol;
			symbol.name = loop_iterator.ident_value;
			symbol.source_node = node;
			variable, handle := new_type_variable(checker);
			variable.name = symbol.name;
			typename : string;
			variable.type = .Int;
			symbol.type_variable = handle;
			add_symbol_to_scope(checker.state, *checker.ctx.scope_stack, checker.current_scope, symbol.name, symbol);

			begin_iter := check_node(checker, node.children[0]);
			begin_var  := from_handle(checker, begin_iter);
			if begin_var.type != .Int {

			}

			end_iter := check_node(checker, node.children[1]);
			end_var  := from_handle(checker, end_iter);
			if end_var.type != .Int {

			}

			check_block(checker, node.children[2]);
		}
		case .If; {
			cond_var := check_node(checker, node.children[0]);

			if cond_var > 0 {
				cond := from_handle(checker, cond_var);
				if cond.type != .Bool {
					if_condition_has_to_be_boolean_type(checker, node, cond_var);
				}
			}
			
			body_var := check_block(checker, node.children[1]);

			if node.children.count == 3 {
				if node.children[2].kind == .If {
					check_node(checker, node.children[2]);
				} else {
					check_block(checker, node.children[2]);
				}
			}
		}
		case .If_Directive; {
			check_if_directive(checker, node);
		}
		case .Variable; {
			return check_variable(checker, node);
		}
		case .Integer; {
			type_variable, handle := new_type_variable(checker);
			type_variable.type = .Int;
			type_variable.source_node = node;
			node.type_variable = handle;

			return handle;
		}
		case .Float; {
			type_variable, handle := new_type_variable(checker);
			type_variable.type = .Float;
			type_variable.source_node = node;
			node.type_variable = handle;

			return handle;
		}
		case .Expression_Statement; {
			return check_node(checker, node.children[0]);
		}
		case .Call; {
			type_variable, handle := new_type_variable(checker);
			type_variable.type = .Unresolved_Expression;
			type_variable.source_node = node;
			node.type_variable = handle;
			
			if check_call(checker, node, handle) {
				return 0;
			}

			return handle;
		}
	}
	return 0;
}

is_valid_define :: (checker : *Checker, def : string) -> bool {
	for env_def : checker.ctx.environment.defines {
		if env_def == def {
			return true;
		}
	}
	return false;
}

check_env_expression :: (checker : *Checker, expr : *AST_Node, directive : *AST_Node) -> bool {
	if expr.kind == .Binary {
		lhs := expr.children[0];
		rhs := expr.children[1];

		lhs_valid := check_env_expression(checker, lhs, directive);
		rhs_valid := check_env_expression(checker, rhs, directive);

		if expr.token.kind == .TOKEN_LOGICALOR {
			return lhs_valid || rhs_valid;
		} else if expr.token.kind == .TOKEN_LOGICALAND {
			return lhs_valid && rhs_valid;
		}
	} else if expr.kind == .Variable {
		if expr.name == "Env" {
			child := expr.children[0]; // The variable in the environment

			return is_valid_define(checker, child.name);
		}
	}

	return false;
}

check_if_directive :: (checker : *Checker, directive : *AST_Node) {
	cond := directive.children[0];
	is_valid := check_env_expression(checker, cond, directive);

	if is_valid {
		parent := directive.parent;
		first_child : *AST_Node = directive.children[1];
		
		if checker.current_scope == 1 {
			traverse(checker, first_child);
		} else {
			check_block(checker, first_child);
		}

		for child : directive.children[1].children {
			add_child(parent, child);
		}

		if directive.children.count > 2 {
			directive.children.count = 0;
		}
	} else if directive.children.count > 2 {
		parent := directive.parent;
		
		else_branch := directive.children[2];
		if else_branch.kind == .If_Directive {
			check_if_directive(checker, else_branch);
		} else {
			for child : else_branch.children {
				add_child(parent, child);
			}

			if checker.current_scope == 1 {
				if else_branch.kind == .If_Directive {
					check_declaration(checker, else_branch);
				} else {
					traverse(checker, else_branch);
				}
			}
		}

		directive.children.count = 0;
	}
}

check_declaration :: (checker : *Checker, declaration : *AST_Node) {
	if declaration.kind == .Function {
		if declaration.foreign_declaration {
			fun_handle := declare_foreign_function(checker, declaration);
		} else {
			fun_handle := declare_function(checker, declaration);
		}
	} else if declaration.kind == .Struct {
		declare_struct(checker, declaration);
	} else if declaration.kind == .CBuffer {
		declare_cbuffer(checker, declaration);
	} else if declaration.kind == .If_Directive {
		check_if_directive(checker, declaration);
	}
}

traverse :: (checker : *Checker, root : *AST_Node) {
	declarations := root.children;
	for declaration : declarations {
		check_declaration(checker, declaration);
	}

	if checker.had_error {
		return;
	}

	for declaration : declarations {
		if declaration.kind == .If_Directive {
			continue;
		}
		
		check_node(checker, declaration);
	}
}

traverse :: (checker : *Checker) {
	traverse(checker, checker.program_root);
}

types_compatible :: (checker : *Checker, lhs : Type_Variable_Handle, rhs : Type_Variable_Handle, param_matching : bool = false) -> bool {
	lhs_var := from_handle(checker, lhs);
	rhs_var := from_handle(checker, rhs);

	if lhs_var.type == {
		case .Int;       #through;
		case .Half;      #through;
		case .Float;     #through;
		case .Double; {
			if !param_matching {
				if rhs_var.type == .Struct {
					if rhs_var.typename == {
						case "float2"; #through;
						case "float3"; #through;
						case "float4"; {
							return true;
						}
					}
				}
			}
			return rhs_var.type == .Int || rhs_var.type == .Half ||
				rhs_var.type == .Float || rhs_var.type == .Double;
		}
		case .Sampler;   #through;
		case .Texture2D; {
			return rhs_var.type == lhs_var.type;
		}
		case .Struct; {
			lhs_node := lhs_var.source_node;
			rhs_node := rhs_var.source_node;
			if rhs_var.type != .Struct && !param_matching {
				if lhs_var.typename == {
					case "float2"; #through;
					case "float3"; #through;
					case "float4"; {
						return rhs_var.type == .Int || rhs_var.type == .Half || rhs_var.type == .Double || rhs_var.type == .Float;
					}
				}
			}

			if rhs_var.type != .Struct {
				return false;
			}

			lhs_struct := find_symbol(checker, lhs_var.typename, xx 1);
			rhs_struct := find_symbol(checker, rhs_var.typename, xx 1);

			if !lhs_struct || !rhs_struct {
				return false;
			}

			if !lhs_struct.type_variable || !rhs_struct.type_variable {
				return false;
			}

			lhs_struct_var := from_handle(checker, lhs_struct.type_variable);
			rhs_struct_var := from_handle(checker, rhs_struct.type_variable);

			if lhs_struct_var.children.count != rhs_struct_var.children.count {
				return false;
			}

			for i : 0..lhs_struct_var.children.count - 1 {
				lhs_child := lhs_struct_var.children[i];
				rhs_child := rhs_struct_var.children[i];
				if !types_compatible(checker, lhs_child, rhs_child) {
					return false;
				}
			}

			return true;
		}
		case .Unresolved_Expression; #through;
		case .Unresolved_Variable; {
			return true;
		}
	}
	return false;
}

add_builtins_new :: (checker : *Checker) {

	checker.state = .Adding_Builtins;
	float_name := Typenames[Type_Kind.Float];
	int_name   := Typenames[Type_Kind.Int];

	arg :: (name : string = "", kind : Type_Kind) -> Arg {
		return .{ name, Typenames[kind] };
	}

	targ :: (typename : string) -> Arg {
		return .{ "", typename };
	}

	farg :: (name : string = "") -> Arg {
		return arg(name, .Float);
	}

	iarg :: (name : string = "") -> Arg {
		return arg(name, .Int);
	}
	
	float2_tv, f2h := new_builtin_struct(checker, "float2", .[farg("x"), farg("y")]);
	float3_tv, f3h := new_builtin_struct(checker, "float3", .[farg("x"), farg("y"), farg("z")]);
	float4_tv, f4h := new_builtin_struct(checker, "float4", .[farg("x"), farg("y"), farg("z"), farg("w")]);

	float4x4_members : [16]Arg;
	i := 0;
	for x : 0..3 {
		for y : 0..3 {
			float4x4_members[i] = farg(tprint("m%1%2", x + 1, y + 1));
			i += 1;
		}
	}
	
	float4x4_tv, f4x4h := new_builtin_struct(checker, "float4x4", float4x4_members);
	
	int2_tv, i2h := new_builtin_struct(checker, "int2", .[iarg("x"), iarg("y")]);
	int3_tv, i3h := new_builtin_struct(checker, "int3", .[iarg("x"), iarg("y"), iarg("z")]);
	int4_tv, i4h := new_builtin_struct(checker, "int4", .[iarg("x"), iarg("y"), iarg("z"), iarg("w")]);

	int4x4_members : [16]Arg;
	i = 0;
	for x : 0..3 {
		for y : 0..3 {
			int4x4_members[i].name = tprint("m%1%2", x + 1, y + 1);
			int4x4_members[i].typename = int_name;
			i += 1;
		}
	}

	int4x4_tv, i4x4h := new_builtin_struct(checker, "int4x4", int4x4_members);

	new_builtin_function(checker, "float2", .[farg(), farg()], targ("float2"));
	new_builtin_function(checker, "float2", .[farg()], targ("float2"));
	new_builtin_function(checker, "float2", .[targ("float2")], targ("float2"));
	
	
	new_builtin_function(checker, "float3", .[farg(), farg(), farg()], targ("float3"));
	new_builtin_function(checker, "float3", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "float3", .[targ("float2"), farg()], targ("float3"));
	new_builtin_function(checker, "float3", .[farg(), targ("float2")], targ("float3"));
	new_builtin_function(checker, "float3", .[farg()], targ("float3"));

	
	new_builtin_function(checker, "float4", .[farg(), farg(), farg(), farg()], targ("float4"));
	new_builtin_function(checker, "float4", .[targ("float2"), targ("float2")], targ("float4"));
	new_builtin_function(checker, "float4", .[targ("float2"), farg(), farg()], targ("float4"));
	new_builtin_function(checker, "float4", .[farg(), targ("float2"), farg()], targ("float4"));
	new_builtin_function(checker, "float4", .[farg(), farg(), targ("float2")], targ("float4"));
	new_builtin_function(checker, "float4", .[farg(), targ("float3")], targ("float4"));
	new_builtin_function(checker, "float4", .[targ("float3"), farg()], targ("float4"));
	new_builtin_function(checker, "float4", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "float4", .[farg()], targ("float4"));

	new_builtin_function(checker, "cross", .[targ("float3"), targ("float3")], targ("float3"));
	new_builtin_function(checker, "distance", .[targ("float2"), targ("float2")], farg());
	new_builtin_function(checker, "distance", .[targ("float3"), targ("float3")], farg());
	new_builtin_function(checker, "distance", .[targ("float4"), targ("float4")], farg());

	new_builtin_function(checker, "length", .[targ("float2")], farg());
	new_builtin_function(checker, "length", .[targ("float3")], farg());
	new_builtin_function(checker, "length", .[targ("float3")], farg());

	new_builtin_function(checker, "dot", .[targ("float2"), targ("float2")], farg());
	new_builtin_function(checker, "dot", .[targ("float3"), targ("float3")], farg());
	new_builtin_function(checker, "dot", .[targ("float4"), targ("float4")], farg());

	new_builtin_function(checker, "normalize", .[targ("float2")], farg());
	new_builtin_function(checker, "normalize", .[targ("float3")], farg());
	new_builtin_function(checker, "normalize", .[targ("float3")], farg());

	new_builtin_function(checker, "transpose", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "mul", .[targ("float2"), targ("float2")], farg());
	new_builtin_function(checker, "mul", .[targ("float3"), targ("float3")], farg());
	new_builtin_function(checker, "mul", .[targ("float4"), targ("float4")], farg());
	new_builtin_function(checker, "mul", .[targ("float4x4"), targ("float4x4")], targ("float4x4"));
	
	new_builtin_function(checker, "mul", .[farg(), targ("float2")], targ("float2"));
	new_builtin_function(checker, "mul", .[farg(), targ("float3")], targ("float3"));
	new_builtin_function(checker, "mul", .[farg(), targ("float4")], targ("float4"));

	new_builtin_function(checker, "mul", .[farg(), targ("float4x4")], targ("float4x4"));
	new_builtin_function(checker, "mul", .[targ("float4x4"), targ("float4x4")], targ("float4x4"));
	new_builtin_function(checker, "mul", .[targ("float4x4"), targ("float4")], targ("float4"));

	new_builtin_function(checker, "mul", .[targ("float2"), farg()], targ("float2"));
	new_builtin_function(checker, "mul", .[targ("float3"), farg()], targ("float3"));
	new_builtin_function(checker, "mul", .[targ("float4"), farg()], targ("float4"));

	new_builtin_function(checker, "mul", .[targ("float4"), targ("float4x4")], targ("float4"));

	new_builtin_function(checker, "abs", .[farg()], farg());
	new_builtin_function(checker, "abs", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "abs", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "abs", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "abs", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "min", .[farg()], farg());
	new_builtin_function(checker, "min", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "min", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "min", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "min", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "max", .[farg()], farg());
	new_builtin_function(checker, "max", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "max", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "max", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "max", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "ceil", .[farg()], farg());
	new_builtin_function(checker, "ceil", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "ceil", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "ceil", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "ceil", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "floor", .[farg()], farg());
	new_builtin_function(checker, "floor", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "floor", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "floor", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "floor", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "round", .[farg()], farg());
	new_builtin_function(checker, "round", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "round", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "round", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "round", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "clamp", .[farg(), farg(), farg()], farg());
	new_builtin_function(checker, "clamp", .[targ("float2"), targ("float2"), targ("float2")], targ("float2"));
	new_builtin_function(checker, "clamp", .[targ("float3"), targ("float3"), targ("float3")], targ("float3"));
	new_builtin_function(checker, "clamp", .[targ("float4"), targ("float4"), targ("float4")], targ("float4"));
	new_builtin_function(checker, "clamp", .[targ("float4x4"), targ("float4x4"), targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "log", .[farg()], farg());
	new_builtin_function(checker, "log", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "log", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "log", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "log", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "log2", .[farg()], farg());
	new_builtin_function(checker, "log2", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "log2", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "log2", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "log2", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "log10", .[farg()], farg());
	new_builtin_function(checker, "log10", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "log10", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "log10", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "log10", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "pow", .[farg(), farg(), farg()], farg());
	new_builtin_function(checker, "pow", .[targ("float2"), targ("float2"), targ("float2")], targ("float2"));
	new_builtin_function(checker, "pow", .[targ("float3"), targ("float3"), targ("float3")], targ("float3"));
	new_builtin_function(checker, "pow", .[targ("float4"), targ("float4"), targ("float4")], targ("float4"));
	new_builtin_function(checker, "pow", .[targ("float4x4"), targ("float4x4"), targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "smoothstep", .[farg(), farg(), farg()], farg());
	new_builtin_function(checker, "smoothstep", .[targ("float2"), targ("float2"), targ("float2")], targ("float2"));
	new_builtin_function(checker, "smoothstep", .[targ("float3"), targ("float3"), targ("float3")], targ("float3"));
	new_builtin_function(checker, "smoothstep", .[targ("float4"), targ("float4"), targ("float4")], targ("float4"));
	new_builtin_function(checker, "smoothstep", .[targ("float4x4"), targ("float4x4"), targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "step", .[farg()], farg());
	new_builtin_function(checker, "step", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "step", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "step", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "step", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "sqrt", .[farg()], farg());
	new_builtin_function(checker, "sqrt", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "sqrt", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "sqrt", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "sqrt", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "cos", .[farg()], farg());
	new_builtin_function(checker, "cos", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "cos", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "cos", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "cos", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "cosh", .[farg()], farg());
	new_builtin_function(checker, "cosh", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "cosh", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "cosh", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "cosh", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "acos", .[farg()], farg());
	new_builtin_function(checker, "acos", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "acos", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "acos", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "acos", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "sin", .[farg()], farg());
	new_builtin_function(checker, "sin", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "sin", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "sin", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "sin", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "sinh", .[farg()], farg());
	new_builtin_function(checker, "sinh", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "sinh", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "sinh", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "sinh", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "asin", .[farg()], farg());
	new_builtin_function(checker, "asin", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "asin", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "asin", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "asin", .[targ("float4x4")], targ("float4x4"));
	
	new_builtin_function(checker, "tan", .[farg()], farg());
	new_builtin_function(checker, "tan", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "tan", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "tan", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "tan", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "tanh", .[farg()], farg());
	new_builtin_function(checker, "tanh", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "tanh", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "tanh", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "tanh", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "atan", .[farg()], farg());
	new_builtin_function(checker, "atan", .[targ("float2")], targ("float2"));
	new_builtin_function(checker, "atan", .[targ("float3")], targ("float3"));
	new_builtin_function(checker, "atan", .[targ("float4")], targ("float4"));
	new_builtin_function(checker, "atan", .[targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "atan2", .[farg(), farg()], farg());
	new_builtin_function(checker, "atan2", .[targ("float2"), targ("float2")], farg());
	new_builtin_function(checker, "atan2", .[targ("float3"), targ("float3")], farg());
	new_builtin_function(checker, "atan2", .[targ("float4"), targ("float4")], farg());
	new_builtin_function(checker, "atan2", .[targ("float4x4"), targ("float4x4")], targ("float4x4"));

	new_builtin_function(checker, "sample", .[targ(Typenames[Type_Kind.Texture2D]), targ(Typenames[Type_Kind.Sampler])], targ("float4"));

	new_builtin_function(checker, "pow", .[farg(), farg(), farg()], farg());
	new_builtin_function(checker, "pow", .[targ("float2"), targ("float2"), farg()], targ("float2"));
	new_builtin_function(checker, "pow", .[targ("float3"), targ("float3"), farg()], targ("float3"));
	new_builtin_function(checker, "pow", .[targ("float4"), targ("float4"), farg()], targ("float4"));
	

	checker.state = .Type_Checking;
}

add_builtins :: (checker : *Checker)  {
	source_location := #location().fully_pathed_filename;
	path_array := split(source_location, "/");

	sb : String_Builder;
	for i : 0..path_array.count - 2 {
		print_to_builder(*sb, path_array[i]);
		append(*sb, "/");
	}

	append(*sb, "builtins.ink");

	path := builder_to_string(*sb);

	BUILTIN, ok := read_entire_file(path);

	if !ok {
		messages : [..]Compiler_Message;
		internal_error_message(*messages, "Error loading builtin functions.", checker.path);
		print("%\n", report_messages(checker.ctx, messages));
		assert(false);
		return;
	}
	
	checker.state = .Adding_Builtins;

	checker.ctx.file = make_file_from_string(BUILTIN);

	prev_file := checker.ctx.file;
	prev_root := checker.ctx.root;
	prev_tokens := checker.ctx.tokens;

	checker.ctx.root = null;

	tokens : [..]Token;
	scratch := get_scratch();
	defer scratch_end(scratch);
	tokens.allocator = scratch.allocator;
	array_reserve(*tokens, 1024 * 1024);
	checker.ctx.tokens = tokens;
	
	checker.ctx.tokens.count = 0;

	lex(checker.ctx);
	parse(checker.ctx);
	type_check(checker, checker.ctx.root);

	for *type_var : checker.ctx.type_variables {
		type_var.builtin = true;
	}

	checker.state = .Type_Checking;

	checker.ctx.file   = prev_file;
	checker.ctx.root   = prev_root;
	checker.ctx.tokens = prev_tokens;
}

type_check :: (checker : *Checker, root : *AST_Node) {
	traverse(checker, root);
}

check :: (ctx : *Compiler_Context, allocator : Allocator = temp) {
	if ctx.had_error {
		return;
	}

	new_context := context;
	new_context.allocator = allocator;
	push_context new_context {
		init_context_allocators();
		defer clear_context_allocators();

		checker : Checker;

		checker.ctx = ctx;

		init_semantic_checker(*checker, ctx.root, ctx.file.path);
		
		add_builtins_new(*checker);
		// add_builtins(*checker);

		type_check(*checker, ctx.root);
		
		ctx.had_error |= checker.had_error;
	}

	
}

// ===========================================================
// Pretty printing

#scope_file

type_to_string :: (type_variable : Type_Variable) -> string {
	if type_variable.type == {
		case .Invalid;
		return "{{invalid}}";
		case .Unit;
		return "()";
		case .Int;       #through;
		case .Half;	     #through;
		case .Float;	 #through;
		case .Sampler;   #through;
		case .Texture2D; #through;
		case .Double; {
			return Typenames[type_variable.type];
		}
		case .Function; #through;
		case .Struct; {
			return type_variable.typename;
		}
		case .Array;
		// return "array";
	}
	return "";
}

#scope_export

print_key :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, builder : *String_Builder, name : string) {
	scope := get_scope(scope_stack, current_scope);
	target_length := scope.longest_key_length + 1;
	missing_padding := target_length - name.count;
	str := tprint("[%]", name);
	append(builder, str);
	for 0..missing_padding - 1 {
		append(builder, " ");
	}

	append(builder, ": ");
}

pretty_print_function :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, variables : []Type_Variable, builder : *String_Builder, name : string, function : Type_Variable, indentation : int) {
	indent(builder, indentation);
	print_key(scope_stack, current_scope, builder, name);
	append(builder, "(");

	for child : function.source_node.children {
		if child.kind == .FieldList {
			for field : child.children {
				tv := from_handle(variables, field.type_variable);
				if tv.type != .Function {
					if tv.builtin {
						print_to_builder(builder, "%", tv.name);
					} else {
						print_to_builder(builder, "% : %", tv.name, type_to_string(tv));
					}
				} else {
					pretty_print_function(scope_stack, current_scope, variables, builder, "", tv, 0);
				}

				if it_index < child.children.count - 1 {
					append(builder, ", ");
				}
			}			
		}
	}

	if function.return_type_variable> 0 {
		print_to_builder(builder, ") -> %\n", type_to_string(from_handle(variables, function.return_type_variable)));
	} else {
		append(builder, ")\n");
	}
}

pretty_print_struct :: (scope_stack : *Scope_Stack, current_scope : Scope_Handle, variables : []Type_Variable, builder : *String_Builder, name : string, struct_type : Type_Variable, indentation : int) {
	indent(builder, indentation);
	print_key(scope_stack, current_scope, builder, name);
	append(builder, "{");

	for 0..struct_type.children.count - 1 {
		child_handle := struct_type.children[it];
		child := from_handle(variables, child_handle);
		print_to_builder(builder, child.name);
		append(builder, " : ");
		print_to_builder(builder, type_to_string(child));

		if it < struct_type.children.count - 1 {
			append(builder, ", ");
		}
	}
	
	append(builder, "}\n");
}

pretty_print_scope :: (ctx : *Compiler_Context, current_scope : Scope_Handle, scope_stack : Scope_Stack, variables : []Type_Variable, scope : *Scope, builder : *String_Builder, indentation : int = 0) {
	if scope.builtin {
		return;
	}
	
	table := scope.table;

	indent(builder, indentation);
	append(builder, "scope (");
	if scope.name.count > 0 {
		print_to_builder(builder, "%", scope.name);
	} else {
		append(builder, "global");
	}
	append(builder, ") [");
	if scope.table.count > 0 {
		append(builder, "\n");
	}

	for table {
		key, value := it_index, it;
		if value.builtin continue;

		if value.functions.count > 0 {
			for func : value.functions {
				type_variable := from_handle(variables, func.type_variable);
				if type_variable.type == {
					case .Function; {
						pretty_print_function(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
					}
					case .CBuffer;    #through;
					case .Struct; {
						if type_variable.typename.count > 0 && type_variable.source_kind != .Declaration {
							indent(builder, indentation + 1);
							print_key(*scope_stack, current_scope, builder, key);
							print_type_variable(ctx, builder, variables, type_variable);
							append(builder, "\n");
							// print_to_builder(builder, "%\n", type_variable.typename);
						} else {
							pretty_print_struct(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
						}
					}
					case; {
						indent(builder, indentation + 1);
						print_key(*scope_stack, current_scope, builder, key);
						print_to_builder(builder, "%\n", type_to_string(type_variable));
					}
				}
			}
		} else {
			type_variable := from_handle(variables, value.type_variable);
			if type_variable.type == {
				case .Function; {
					pretty_print_function(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
				}
				case .CBuffer;    #through;
				case .Struct; {
					if type_variable.typename.count > 0 && type_variable.source_kind != .Declaration {
						indent(builder, indentation + 1);
						print_key(*scope_stack, current_scope, builder, key);
						print_to_builder(builder, "%\n", type_variable.typename);
					} else {
						pretty_print_struct(*scope_stack, current_scope, variables, builder, key, type_variable, 1);
					}
				}
				case; {
					indent(builder, indentation + 1);
					print_key(*scope_stack, current_scope, builder, key);
					print_to_builder(builder, "%\n", type_to_string(type_variable));
				}
			}
		}
		
	}

	for child : scope.children {
		child_scope := *scope_stack.stack[child - 1];
		pretty_print_scope(ctx, current_scope, *scope_stack, variables, child_scope, builder, indentation + 1);
	}

	if scope.table.count > 0 {
		indent(builder, indentation);
	}
	append(builder, "]\n");
}

print_type_variable :: (ctx : *Compiler_Context, builder : *String_Builder, variables : []Type_Variable, variable : Type_Variable) {
	if variable.builtin {
		if variable.type != .Function || variable.type != .Struct {
			print_to_builder(builder, "%", type_to_string(variable));
		} else {
			print_to_builder(builder, "%", variable.name);
		}
	} else {
		node := variable.source_node;
		if node {
			if node.kind == {
				case .Function;   #through;
				case .Struct;     #through;
				case .CBuffer;    #through;
				case .Field; {
					if variable.struct_field_parent {
						print_to_builder(builder, "%.", variable.struct_field_parent.name);
					}

					print_to_builder(builder, "%", node.name);
				}
				case .Binary; {
					left_most := node.children[0];

					while left_most.kind == .Binary {
						left_most = left_most.children[0];
					}

					right_most := node.children[1];
					while right_most.kind == .Binary {
						right_most = right_most.children[1];
					}

					source_location : Source_Range;
					source_location.begin = left_most.source_location.main_token;
					source_location.end = right_most.source_location.main_token;
					source_location.main_token = node.source_location.main_token;

					print("%\n", print_from_source_location(ctx, builder, source_location,, temp));
				}
				case .Call; {
					if variable.return_type_variable{
						assert(false);
						print_to_builder(builder, "%", variable.typename);
						print_type_variable(ctx, builder, variables, variable.return_type_variable);
					}

					print_to_builder(builder, "%(", node.name);

					
					for child : node.children {
						if child.kind == .ArgList {
							for arg : child.children {
								print_type_variable(ctx, builder, variables, arg.type_variable);

								if it_index < child.children.count - 1 {
									append(builder, ", ");
								}
							}
						}
					}

					append(builder, ")");
				}
				case; {
					print("%\n", print_from_source_location(ctx, builder, node.source_location,, temp));
				}
			}
		}
	}
}

print_type_variable :: (ctx : *Compiler_Context, builder : *String_Builder, variables : []Type_Variable, handle : Type_Variable_Handle) {
	variable := from_handle(variables, handle);
	print_type_variable(ctx, builder, variables, variable);
}

pretty_print_symbol_table :: (checker : *Checker, allocator : Allocator) -> string {
	builder : String_Builder;
	init_string_builder(*builder,, allocator);

	pretty_print_scope(checker.ctx, xx checker.current_scope, checker.ctx.scope_stack, checker.ctx.type_variables, *checker.ctx.scope_stack.stack[0], *builder);

	return builder_to_string(*builder,, allocator);
}

pretty_print_symbol_table :: (ctx : *Compiler_Context, allocator : Allocator) -> string {
	builder : String_Builder;
	init_string_builder(*builder,, allocator);

	current_scope := cast(Scope_Handle)1;
	pretty_print_scope(ctx, current_scope, ctx.scope_stack, ctx.type_variables, *ctx.scope_stack.stack[0], *builder);
	
	return builder_to_string(*builder,, allocator);
}

#scope_module

#import "ncore";
#import "Hash_Table";
#import "String";
