@tool
extends StaticBody3D

@export var on: bool = false
@export var remove_objects: bool = false
@export var spawn_only_object: bool = false
@export var can_generate_terrain: bool = false

@export var size: int = 64
@export var subdivide: int = 8
@export var amplitude: int = 4
@export var group_size: int = 8
@export var hill_chance: float = 0.0
@export var noise: Noise = FastNoiseLite.new()

@export var spawn_rate: float = 0.02
@export var object_list: Array[Resource]
@export var object_ratios: Array[int]
@export var can_be_up: Array[bool]

var player: Player

var can_spawn_objects: bool = false
var already_spawned: bool = false

var generated_terain

func _ready():
	randomize()
	player = get_tree().get_nodes_in_group("Player").back()
	noise.set("seed", randi())
	on = true
	generated_terain = false


func _physics_process(_delta):
	if on:
		generate_terrain()
		on = false
	elif can_spawn_objects:
		move_player()
		spawn_objects()
		generated_terain = true
		can_spawn_objects = false
		already_spawned = true
	elif not already_spawned:
		can_spawn_objects = true


func _process(_delta):
	if remove_objects:
		for child in $SpawnedObjects.get_children():
			child.queue_free()
		remove_objects = false
	
	if spawn_only_object:
		spawn_objects()
		spawn_only_object = false
	
	if can_generate_terrain:
		noise.set("seed", randi())
		generate_terrain()
		can_generate_terrain = false


func generate_terrain():
	var plane_mesh = PlaneMesh.new()
	plane_mesh.size = Vector2(size, size)
	plane_mesh.subdivide_depth = subdivide
	plane_mesh.subdivide_width = subdivide
	
	var surface_tool = SurfaceTool.new()
	surface_tool.create_from(plane_mesh, 0)
	var data = surface_tool.commit_to_arrays()
	var vertices = data[ArrayMesh.ARRAY_VERTEX]
	
	@warning_ignore("narrowing_conversion")
	var side_len: int = sqrt(vertices.size())
	for y in side_len / group_size:
		for x in side_len / group_size:			
			var realx: int = group_size * x
			var realy: int = group_size * y
			var vertex = vertices[realy * side_len + realx]
			var value: float = (float (amplitude)) if noise.get_noise_2d(vertex.x, vertex.z) < hill_chance else 0.0
			if y <= 1 or x <= 1 or y >= (side_len / group_size - 2) or x >= (side_len / group_size - 2):
				value = 2.0 * amplitude
			set_group(realx, realy, side_len, vertices, value)
	
	var array_mesh = ArrayMesh.new()
	array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, data)
	
	surface_tool.create_from(array_mesh, 0)
	surface_tool.generate_normals()
	
	$MeshInstance3D.mesh = surface_tool.commit()
	$CollisionShape3D.shape = array_mesh.create_trimesh_shape()
	
	var parent = get_parent()
	if parent is NavigationRegion3D:
		parent.bake_navigation_mesh()


func set_group(x: int, y: int, side_len: int, vertices, value: float):
	for i in group_size:
		for j in group_size:
			var xx: int = x + j
			var yy: int = y + i
			vertices[yy * side_len + xx].y = value


func spawn_objects():
	for child in $SpawnedObjects.get_children():
		child.queue_free()
	var summed_ratios = 0
	for ratio in object_ratios:
		summed_ratios += ratio
	var cummulative_ratios: Array[float] = []
	for i in object_ratios.size():
		if i == 0:
			cummulative_ratios.append(object_ratios[i] / (float (summed_ratios)))
		else:
			cummulative_ratios.append(cummulative_ratios.back() + object_ratios[i] / (float (summed_ratios)))
	for z in range(-size / 2, size / 2):
		for x in range(-size / 2, size / 2):
			if randf() >= spawn_rate:
				continue
			if player and Vector2(x, z).distance_to(Vector2(player.position.x, player.position.z)) <= 4.0:
				continue;
			var x_displacement = randf_range(-0.4, 0.4);
			var z_displacement = randf_range(-0.4, 0.4);
			var chosen_obj = randf()
			for i in cummulative_ratios.size():
				if chosen_obj < cummulative_ratios[i]:
					spawn_object(object_list[i], x + x_displacement, z + z_displacement, can_be_up[i])
					break
					

func spawn_object(obj: Resource, x: float, z: float, can_be_placed_up: bool):
	var from = Vector3(x, 2.0 * amplitude + 2.0, z)
	var to = Vector3(x, -2.0, z)
	
	var query = PhysicsRayQueryParameters3D.create(from, to)
	query.exclude = []
	query.hit_back_faces = true
	query.hit_from_inside = true
	var result = get_world_3d().direct_space_state.intersect_ray(query)
	var y: float = result.position.y if result else 0.0
	
	if not can_be_placed_up and y > 0.5:
		return
		
	var object = obj.instantiate()
	$SpawnedObjects.add_child(object)
	object.position = Vector3(x, y, z)
	#object.scale = Vector3(2.0, 2.0, 2.0)
	object.rotation.y = randf_range(0.0, 2.0 * 3.14);


func move_player():	
	while true:
		var query = PhysicsRayQueryParameters3D.create(player.position, Vector3(player.position.x, -1, player.position.z))
		var result = get_world_3d().direct_space_state.intersect_ray(query)
		if result:
			if result.position.y > 0.01:
				player.position.x = randf_range(-size/2.0 + 2.0 * group_size, size/2.0 - 2.0 * group_size)
				player.position.z = randf_range(-size/2.0 + 2.0 * group_size, size/2.0 - 2.0 * group_size)
			else:
				return
		else:
			return
