@tool
class_name DestructableWall extends Node3D

enum ExtrudeDirection {
	BACK = 0, # z
	RIGHT = 1, # x
	UP = 2 # y
}

@export_category("TrechBroom Generated Values")
@export var verts2d: PackedVector2Array
@export var meshInstance3d: MeshInstance3D
@export var depth: float = 0.1
@export var depth_position_offset: Vector3 = Vector3.ZERO
@export_category("TrechBroom User Values")
@export var extrusion_direction: ExtrudeDirection
@export var hole_proximity: float
@export var edge_non_fracture: float = 0.1

var parentNode
var inner_rtree: RectangleTree

var outer_polygon: PackedVector2Array
var inner_polygons: Holes

var static_body: StaticBody3D

func _ready() -> void:
	if Engine.is_editor_hint():
		return
	
	parentNode = get_node("..")
	inner_polygons = Holes.new()
	inner_rtree = RectangleTree.new()
	outer_polygon = verts2d
	_draw()

func _exit_tree() -> void:
	if Engine.is_editor_hint():
		return
	inner_rtree.free()

func _func_godot_apply_properties(entity_properties: Dictionary) -> void:
	meshInstance3d = _find_mesh_body()
	var verticies = meshInstance3d.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
	
	extrusion_direction = entity_properties["extrude_direction"]
	hole_proximity = entity_properties["hole_proximity"]
	edge_non_fracture = entity_properties["edge_non_fractured"]
	
	var depth_min: float
	var depth_max: float
	
	var verts_2d: PackedVector2Array = []
	if extrusion_direction == ExtrudeDirection.BACK:
		for vert in verticies:
			verts_2d.append(Vector2(vert.x, vert.y))
			
			var d = vert.z
			
			if d > depth_max:
				depth_max = d
			elif d < depth_min:
				depth_min = d
		depth = -(abs(depth_min) + abs(depth_max))
		
		depth_position_offset = Vector3.BACK * (depth/2)
	elif extrusion_direction == ExtrudeDirection.RIGHT:
		for vert in verticies:
			verts_2d.append(Vector2(vert.z, vert.y))
			
			var d = vert.x
			
			if d > depth_max:
				depth_max = d
			elif d < depth_min:
				depth_min = d
		depth = -(abs(depth_min) + abs(depth_max))
		
		depth_position_offset = Vector3.RIGHT * (depth/2)
	elif extrusion_direction == ExtrudeDirection.UP:
		for vert in verticies:
			verts_2d.append(Vector2(vert.x, vert.z))
			
			var d = vert.y
			
			if d > depth_max:
				depth_max = d
			elif d < depth_min:
				depth_min = d
		depth = -(abs(depth_min) + abs(depth_max))
		
		depth_position_offset = Vector3.UP * (depth/2)
	
	var outer_boudary = Geometry2D.convex_hull(verts_2d)
	verts2d = outer_boudary

func _draw():
	var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons.get_holes())
	var vectors = []
	
	vectors.append_array(outer_polygon)
	vectors.append_array(inner_polygons.get_hole_verticies())
	
	var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors, extrusion_direction, depth)
	var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0))
	meshGenerator.free()
	
	meshInstance3d.mesh = commited_mesh
	meshInstance3d.position += depth_position_offset
	meshInstance3d.create_trimesh_collision()
	static_body = _find_static_body()

func _re_draw():
	var vector_indexes = GeoPolyTriangulization.triangulate(outer_polygon, inner_polygons.get_holes())
	
	var vectors = []
	vectors.append_array(outer_polygon)
	vectors.append_array(inner_polygons.get_hole_verticies())
	
	WorkerThreadPool.add_task(_generate_mesh.bind(vector_indexes, vectors))

func _deffered_draw(mesh: Mesh, task_id: int):
	if task_id:
		WorkerThreadPool.wait_for_task_completion(task_id)
	
	meshInstance3d.remove_child(static_body)
	static_body.queue_free()
	meshInstance3d.mesh = mesh
	meshInstance3d.create_trimesh_collision()
	static_body = _find_static_body()

func _generate_mesh(vector_indexes: PackedInt32Array, vectors: PackedVector2Array):
	var meshGenerator = GeoPolyMesh.new(vector_indexes, vectors, extrusion_direction, depth)
	var commited_mesh = meshGenerator.commit_mesh(meshInstance3d.get_active_material(0))
	meshGenerator.free()
	
	self.call_deferred("_deffered_draw", commited_mesh, WorkerThreadPool.get_caller_task_id())

func hit(position: Vector3):
	var cutter = Cutter.circleCutter()
	var hole_position_offset = meshInstance3d.to_local(position)
	
	var hole_vector2_offset: Vector2
	if extrusion_direction == ExtrudeDirection.BACK:
		hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.y)
	elif extrusion_direction == ExtrudeDirection.RIGHT:
		hole_vector2_offset = Vector2(hole_position_offset.z, hole_position_offset.y)
	elif  extrusion_direction == ExtrudeDirection.UP:
		hole_vector2_offset = Vector2(hole_position_offset.x, hole_position_offset.z)
	
	# translate the cutter
	var hole_vectors = Transform2D(0, hole_vector2_offset) * cutter
	
	var draw = _add_hole(hole_vectors)
	
	if draw:
		_re_draw()

func _add_hole(new_hole: PackedVector2Array) -> bool:
	var ids_to_remove = []
	
	var holes_id = []
	for vector in new_hole:
		holes_id.append_array(inner_rtree.query(vector))
	
	# validate that the hole is valid
	var boundary_polygon = Geometry2D.offset_polygon(outer_polygon, -edge_non_fracture)
	
	var boundary_hole = Geometry2D.intersect_polygons(new_hole, boundary_polygon[0])
	if !boundary_hole:
		return false

	var merged: PackedVector2Array = boundary_hole[0]
	
	for id in holes_id:
		var result = Geometry2D.merge_polygons(merged, inner_polygons.get_hole(id).vectors)
		
		if len(result) == 1:
			merged = result[0]
			ids_to_remove.append(id)
		elif len(result) > 1:
			# need to check that they have no overlapping area if they do clip
			var overlap = Geometry2D.intersect_polygons(result[0], result[1])
			
			if len(overlap) != 0:
				merged = Geometry2D.clip_polygons(result[0], result[1])[0]
				ids_to_remove.append(id)
	
	for id in ids_to_remove:
		inner_rtree.remove(id)
		inner_polygons.remove_hole(id)
	
	var hole = inner_polygons.add_hole(merged)
	var bound_rect = UTIL.get_bounding_rect(merged)
	inner_rtree.add(bound_rect.position, bound_rect.end, hole.hole_id)
	
	var hole_prox_boundary = bound_rect.grow(hole.area * 4)
	var prox_holes_ids = inner_rtree.query_rect(hole_prox_boundary.position, hole_prox_boundary.end)
	
	# any holes that are close in proximity convex hull
	if len(prox_holes_ids) <= 1: # needs to be length 2 or more because it will always return the hole just created
		return true
	
	var proxmity_hole_vectors = []
	for id in prox_holes_ids:
		proxmity_hole_vectors.append_array(inner_polygons.get_hole(id).vectors)
		inner_rtree.remove(id)
		inner_polygons.remove_hole(id)
	
	var convex_vectors = Geometry2D.convex_hull(proxmity_hole_vectors)
	hole = inner_polygons.add_hole(convex_vectors)
	bound_rect = UTIL.get_bounding_rect(convex_vectors)
	inner_rtree.add(bound_rect.position, bound_rect.end, hole.hole_id)
	
	return true

func _find_static_body():
	for child in meshInstance3d.get_children():
		if child is StaticBody3D:
			return child

func _find_mesh_body():
	for child in self.get_children():
		if child is MeshInstance3D:
			return child
