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

/////////////////////////////////////
//~ nbr: Codegen TODOs
//
// [ ] Prefix output of property values with __PROPERTIES so we don't get name clashes

Output_Language :: enum {
	HLSL;
	GLSL; // @Incomplete
	MLSL; // @Incomplete
	// SPIRV; // @Incomplete: Should we do this?
}

Codegen_State :: struct {
	path : string;
	
	current_scope : Scope_Handle;

	output_language : Output_Language;

	builder : String_Builder;

	result : *Compiler_Context;
}

Reserved_HLSL_Words :: string.[
	"texture",
	"sampler",
	"matrix",
	"line",
	"precise",
	"shared",
	"triangle",
	"triangleadj",
];

Reserved_MLSL_Words :: string.[
	""
];

Reserved_GLSL_Words :: string.[
	""
];

init_codegen_state :: (state : *Codegen_State, result : *Compiler_Context, output_language : Output_Language) {
	state.current_scope   = cast(Scope_Handle)1;
	state.output_language = output_language;
	init_string_builder(*state.builder);
}

indent :: (state : *Codegen_State, indentation : int) {
	for 1..indentation append(*state.builder, "    ");
}

hlsl_type_to_string :: (type_variable : Type_Variable) -> string {
	if type_variable.type == {
		case .Invalid;
		return "{{invalid}}";
		case .Unit;
		return "()";
		case .Int; {
			return "int";
		}
		case .Half; {
			return "half";
		}
		case .Float; {
			return "float";
		}
		case .Double; {
			return "double";
		}
		case .Sampler; {
			return "SamplerState";
		}
		case .Texture2D; {
			return "Texture2D";
		}
		case .Function; #through;
		case .Struct; {
			return type_variable.typename;
		}
		case .Array;
		return "array";
	}

	return "";
}

emit_field :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	find_result := find_symbol(state.result.scope_stack, node.name, state.current_scope);

	field := from_handle(state.result.type_variables, find_result.type_variable);

	indent(state, indentation);

	print_to_builder(*state.builder, "% ", hlsl_type_to_string(field));

	if field.struct_field_parent {
		parent_tv := from_handle(state.result.type_variables, field.struct_field_parent.type_variable);
		
		if parent_tv.typename == "properties" {
			append(*state.builder, "__PROPERTIES__");
		}
	}
	print_to_builder(*state.builder, "%", node.name);

	if field.type == .Sampler {
		print_to_builder(*state.builder, " : register(s%)", field.resource_index);
	}

	if field.type == .Texture2D {
		print_to_builder(*state.builder, " : register(t%)", field.resource_index);
	}

	if node.children.count == 1 {
		child := node.children[0];

		print_to_builder(*state.builder, " = ");
		emit_node(state, child, 0);		
	}

	if node.parent.kind == .Block {
		append(*state.builder, ";");
	}

	for i :0..field.children.count - 1 {
		child := from_handle(state.result.type_variables, field.children[i]);
		emit_node(state, child.source_node, 0);
	}

	for hint : node.hint_tokens {
		if lookup_hint(hint.ident_value) == .Position {
			append(*state.builder, " : POSITION");
		} else if lookup_hint(hint.ident_value) == .UV {
			append(*state.builder, " : TEXCOORD0");
		} else if lookup_hint(hint.ident_value) == .Output_Position {
			append(*state.builder, " : SV_POSITION");
		}
	}
}

emit_block :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	for statement : node.children {
		emit_node(state, statement, indentation);

		if it_index < node.children.count {
			append(*state.builder, "\n");
		}
	}
}

emit_call :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	indent(state, indentation);

	if node.name == "sample" {
		assert(node.children.count > 0);
		args := node.children[0];

		emit_node(state, args.children[0], 0);
		append(*state.builder, ".");
		print_to_builder(*state.builder, "Sample(");

		for i : 1..args.children.count - 1 {
			child := args.children[i];

			emit_node(state, child, 0);
			
			if i != args.children.count - 1 {
				append(*state.builder, ", ");
			}
		}
	} else if starts_with(node.name, "float") && node.children[0].children.count == 1 {
		args := node.children[0];

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

		number : string;
		number.data = *node.name.data[5];
		number.count = node.name.count - 5;
		count := parse_int(*number, s32);

		for i : 0..count - 1 {
			child := args.children[0];
			emit_node(state, child, 0);
			
			if i != count - 1 {
				append(*state.builder, ", ");
			}
		}
		
	} else {
		print_to_builder(*state.builder, "%(", node.name);
		
		if node.children.count > 0 {
			args := node.children[0];

			for child : args.children {
				emit_node(state, child, 0);
				
				if it_index != args.children.count - 1 {
					append(*state.builder, ", ");
				}
			}
		}

	}
	
	append(*state.builder, ")");
}

emit_properties :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	find_result := find_symbol(state.result.scope_stack, ifx node.name.count > 0 then node.name else "properties", state.current_scope);

	if !find_result {
		message : Compiler_Message;
		message.message_kind = .Internal_Error;
		message.path = state.path;
		message.message = "Attempting to generate undeclared properties buffer. This should never happen at this stage.";
		array_add(*state.result.messages, message);		
	}
	assert(find_result != null, "Attempting to generate undeclared properties buffer. This should never happen at this stage.");

	variable := from_handle(state.result.type_variables, find_result.type_variable);

	print_to_builder(*state.builder, "cbuffer __PROPERTIES : register(b%) \n{\n", variable.resource_index);

	previous_scope := state.current_scope;
	state.current_scope = variable.scope;

	resources : Static_Array(*AST_Node, 8);

	for child : node.children {
		if child.kind == .FieldList {
			for field : child.children {
				tv := from_handle(state.result.type_variables, field.type_variable);
				if tv.type == .Sampler || tv.type == .Texture2D {
					array_add(*resources, field);
					continue;
				}
				emit_node(state, field, 1);

				append(*state.builder, ";\n");
			}
		}
	}

	append(*state.builder, "}\n\n");

	for i : 0..resources.count - 1 {
		resource := resources[i];
		emit_node(state, resource, 0);

		append(*state.builder, ";\n");
	}

	append(*state.builder, "\n");

	state.current_scope = previous_scope;
}

emit_function :: (state : *Codegen_State, node : *AST_Node, indentation : int, emit_body := true) {
	name := get_actual_function_name(node);
	find_result := find_symbol(state.result.scope_stack, name, state.current_scope);

	assert(find_result != null, "Attempting to generate undeclared function. This should never happen at this stage.");
	if !find_result {
		message : Compiler_Message;
		message.message_kind = .Internal_Error;
		message.path = state.path;
		message.message = "Attempting to generate undeclared function. This should never happen at this stage.";
		array_add(*state.result.messages, message);
	}

	for func : find_result.functions {
		function_variable := from_handle(state.result.type_variables, func.type_variable);

		indent(state, indentation);

		if function_variable.return_type_variable {
			return_variable := from_handle(state.result.type_variables, function_variable.return_type_variable);
			print_to_builder(*state.builder, "% ", hlsl_type_to_string(return_variable));
		} else {
			append(*state.builder, "void ");
		}
		
		print_to_builder(*state.builder, "%", node.name);

		previous_scope := state.current_scope;
		state.current_scope = function_variable.scope;

		append(*state.builder, "(");

		if node.children.count > 0 && node.children[0].kind == .FieldList {
			params := node.children[0];

			for child : params.children {
				emit_node(state, child, 0);

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

		append(*state.builder, ")");

		for hint : node.hint_tokens {
			if hint.ident_value == "position" {
				// @Incomplete(nb): Should be a lookup table somewhere
				append(*state.builder, " : SV_POSITION");
			}
			
			if starts_with(hint.ident_value, "target") {
				// @Incomplete(nb): Should be a lookup table somewhere
				append(*state.builder, " : SV_TARGET");
			}
		}

		if emit_body {
			append(*state.builder, "\n{\n");


			if node.children.count > 1 {
				emit_block(state, node.children[1], indentation + 1);
			}

			append(*state.builder, "}\n");
		} else {
			append(*state.builder, ";");
		}

		append(*state.builder, "\n");
		

		state.current_scope = previous_scope;
	}
	
}

emit_operator :: (state : *Codegen_State, op_kind : Token_Kind) {
	if op_kind == {
		case .TOKEN_PLUS; {
			append(*state.builder, "+");
		}
		case .TOKEN_MINUS; {
			append(*state.builder, "-");
		}           
		case .TOKEN_STAR; {
			append(*state.builder, "*");
		}			 
		case .TOKEN_SLASH; {
			append(*state.builder, "/");
		}
		case .TOKEN_MINUSEQUALS; {
			append(*state.builder, "-=");
		}
		case .TOKEN_PLUSEQUALS; {
			append(*state.builder,  "+=");
		}
		case .TOKEN_DIVEQUALS; {
			append(*state.builder,  "/=");
		}
		case .TOKEN_TIMESEQUALS; {
			append(*state.builder,  "*=");
		}
		case .TOKEN_MODEQUALS; {
			append(*state.builder,  "%=");
		}
		case .TOKEN_ISEQUAL; {
			append(*state.builder, "==");
		}		 
		case .TOKEN_ASSIGN; {
			append(*state.builder, "=");
		}			 
		case .TOKEN_ISNOTEQUAL; {
			append(*state.builder, "!=");
		}		 
		case .TOKEN_LOGICALOR; {
			append(*state.builder, "||");
		}		 
		case .TOKEN_LOGICALAND; {
			append(*state.builder, "&&");
		}		 
		case .TOKEN_LESS; {
			append(*state.builder, "<");
		}			 
		case .TOKEN_LESSEQUALS; {
			append(*state.builder, "<=");
		}		 
		case .TOKEN_GREATER; {
			append(*state.builder, ">");
		}		 
		case .TOKEN_GREATEREQUALS; {
			append(*state.builder, ">=");
		}
	}															 
}
	
emit_node :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	if node.kind == {
		case .Integer; {
			print_to_builder(*state.builder, "%", node.integer_value);
		}
		case .Float; {
			print_to_builder(*state.builder, "%f", formatFloat(node.float_value, zero_removal=.ONE_ZERO_AFTER_DECIMAL));
		}
		case .Properties; {

		}
		case .Field; {
			emit_field(state, node, indentation);
		}
		case .Block; {

			assert(false, "Not implemented yet: block");
		}
		case .Variable; {
			indent(*state.builder, indentation);

			type_var := from_handle(state.result.type_variables, node.type_variable);
			is_properties := type_var.typename == "properties";

			if !is_properties {
				if type_var.struct_field_parent {
					parent_tv := from_handle(state.result.type_variables, type_var.struct_field_parent.type_variable);
					
					if parent_tv.typename == "properties" {
						append(*state.builder, "__PROPERTIES__");
					}
				}
				print_to_builder(*state.builder, "%", node.name);
			}

			if node.children.count > 0 {
				if !is_properties {
					append(*state.builder, ".");
				}
				emit_node(state, node.children[0], 0);
			}
		}
		case .Binary; {
			indent(*state.builder, indentation);

			if node.token.kind != .TOKEN_ASSIGN {
				append(*state.builder, "(");
			}
			
			lhs := node.children[0];
			rhs := node.children[1];
			emit_node(state, lhs, 0);

			append(*state.builder, " ");
			emit_operator(state, node.token.kind);
			append(*state.builder, " ");
			emit_node(state, rhs, 0);
			if node.token.kind != .TOKEN_ASSIGN {
				append(*state.builder, ")");
			}			
		}
		case .Unary; {
			indent(*state.builder, indentation);

			emit_operator(state, node.token.kind);
			emit_node(state, node.children[0], 0);
		}
		case .Expression_Statement; {
			emit_node(state, node.children[0], indentation);
			append(*state.builder, ";");
		}
		case .Call; {
			emit_call(state, node, indentation);
		}
		case .Return; {
			indent(*state.builder, indentation);
			append(*state.builder, "return ");
			emit_node(state, node.children[0], 0);
			append(*state.builder, ";");
		}
		case .For; {
			if node.parent.kind != .For {
				indent(*state.builder, indentation);
			}

			append(*state.builder, "for ");

			loop_ident := node.token.ident_value;
			begin_val  := node.children[0].integer_value;
			end_val    := node.children[1].integer_value;
			print_to_builder(*state.builder, "(int % = %; % < %; %++)\n", loop_ident, begin_val, loop_ident, end_val, loop_ident);

			indent(*state.builder, indentation);
			append(*state.builder, "{\n");

			emit_block(state, node.children[2], indentation + 1);

			indent(*state.builder, indentation);
			append(*state.builder, "}\n");
		}
		case .If; {
			if node.parent.kind != .If {
				indent(*state.builder, indentation);
			} 

			append(*state.builder, "if ");

			cond := node.children[0];
			emit_node(state, cond, 0);

			body := node.children[1];
			append(*state.builder, "\n");
			indent(*state.builder, indentation);
			append(*state.builder, "{\n");

			emit_block(state, body, indentation + 1);

			indent(*state.builder, indentation);
			append(*state.builder, "}\n");

			if node.children.count == 3 {
				emit_else(state, node.children[2], indentation);
			}
		}
	}
}

emit_else :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	indent(*state.builder, indentation);
	append(*state.builder, "else ");

	if node.kind == .If {		
		emit_node(state, node, indentation);
	} else if node.kind == .Block {
		append(*state.builder, "\n");
		indent(*state.builder, indentation);
		append(*state.builder, "{\n");

		emit_block(state, node, indentation + 1);

		indent(*state.builder, indentation);
		append(*state.builder, "}");
	}
}

emit_field_list :: (state : *Codegen_State, field_list : *AST_Node, indentation : int) {
	for child : field_list.children {
		emit_node(state, child, 1);

		if it_index < field_list.children.count {
			append(*state.builder, ";\n");
		}
	}
}

emit_struct :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	print_to_builder(*state.builder, "struct %", node.name);

	current_scope := state.current_scope;
	state.current_scope = from_handle(state.result.type_variables, node.type_variable).scope;

	field_list := node.children[0];

	if field_list.children.count > 0 {
		append(*state.builder, "\n{\n");
	} else {
		append(*state.builder, " {");
	}
	
	emit_field_list(state, field_list, indentation);
	
	append(*state.builder, "};\n\n");
	state.current_scope = current_scope;
}

emit_cbuffer :: (state : *Codegen_State, node : *AST_Node, indentation : int) {
	variable := from_handle(state.result.type_variables, node.type_variable);
	print_to_builder(*state.builder, "cbuffer % : register(b%)", variable.name, variable.resource_index);
	
	current_scope := state.current_scope;
	state.current_scope = from_handle(state.result.type_variables, node.type_variable).scope;

	field_list := node.children[0];
	
	if field_list.children.count > 0 {
		append(*state.builder, "\n{\n");
	} else {
		append(*state.builder, " {");
	}

	emit_field_list(state, field_list, indentation);
	
	append(*state.builder, "}\n\n");
	state.current_scope = current_scope;
}

emit_declaration :: (state : *Codegen_State, node : *AST_Node) {
	if node.kind == {
		case .Function; {
			emit_function(state, node, 0);
		}
		case .Properties; {
			emit_properties(state, node, 0);
		}
		case .CBuffer; {		
			emit_cbuffer(state, node, 0);
		}
		case .Struct; {
			emit_struct(state, node, 0);			
		}
	}
}

codegen :: (result : *Compiler_Context, allocator := temp) {
	codegen(result, .HLSL);
}

codegen :: (result : *Compiler_Context, output_language : Output_Language, allocator := temp) {
	if result.had_error {
		return;
	}

	new_context := context;
	new_context.allocator = allocator;
	push_context new_context {
		init_context_allocators();
		defer clear_context_allocators();
		
		state : Codegen_State;
		state.result          = result;
		state.current_scope   = cast(Scope_Handle)1;
		state.output_language = output_language;
		init_string_builder(*state.builder);

		codegen(*state);
	}
}

#scope_file
codegen :: (state : *Codegen_State) {
	found_function : bool = false;
	// found_struct   : bool = false;

	// for variable : state.result.type_variables {
	// 	if variable.type == .Struct && variable.kind == .Declaration && !variable.builtin {
	// 		if variable.source_node.kind == .Properties continue;
	// 		if variable.source_node.kind == .Meta continue;
	// 		print_to_builder(*state.builder, "struct %;\n", variable.source_node.name);
	// 		found_struct = true;
	// 	}
	// }

	// if found_struct {
	// 	append(*state.builder, "\n");
	// }
	
	for variable : state.result.type_variables {
		if variable.type == .Function && !variable.builtin
			&& !variable.source_node.vertex_entry_point && !variable.source_node.pixel_entry_point {
				emit_function(state, variable.source_node, 0, false);
				found_function = true;
		}
	}
	if found_function {
		append(*state.builder, "\n");
	}
	
	for declaration : state.result.root.children {
		if declaration.foreign_declaration {
			continue;
		}
		emit_declaration(state, declaration);
	}

	state.result.codegen_result_text = builder_to_string(*state.builder);
}

#scope_module
#import "ncore";
