extends Object

class_name GeoPolyMesh

var surface_array = []

var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var array_mesh = ArrayMesh.new()

var _v_indexes = []
var _v_points = []

var edges = {}

var depth_vector: Vector3
var extrude_direction: DestructableWall.ExtrudeDirection

func _init(vector_indexes: PackedInt32Array, vector_points: PackedVector2Array, extrude_direction: DestructableWall.ExtrudeDirection, depth: float = -0.1):
	assert(len(vector_indexes) % 3 == 0 && len(vector_indexes) != 0, "Number of vertex points is not divisible by 3, invalid triangle verticies")
	surface_array.resize(Mesh.ARRAY_MAX)
	
	self._v_indexes = vector_indexes
	self.extrude_direction = extrude_direction
	
	if extrude_direction == DestructableWall.ExtrudeDirection.BACK:
		for point in vector_points:
			self._v_points.append(Vector3(point.x, point.y, 0))
		depth_vector = Vector3.BACK * depth
	elif extrude_direction == DestructableWall.ExtrudeDirection.RIGHT:
		for point in vector_points:
			self._v_points.append(Vector3(0, point.y, point.x))
		depth_vector = Vector3.LEFT * depth
	elif extrude_direction == DestructableWall.ExtrudeDirection.UP:
		for point in vector_points:
			self._v_points.append(Vector3(point.x, 0, point.y))
		depth_vector = Vector3.DOWN * depth
	
	
	self._pre_process_edges()
	
	self._draw_mesh()
	self._extrude_mesh()
	self._draw_sides()

func calculate_area(mesh_vertices: PackedVector2Array) -> float:
	var result := 0.0
	var num_vertices := mesh_vertices.size()

	for q in range(num_vertices):
		var p = (q - 1 + num_vertices) % num_vertices
		result += mesh_vertices[q].cross(mesh_vertices[p])
	
	return result * 0.5

# edges are any verticies that share only on triangle
func _pre_process_edges():
	var faces = UTIL.chunk_array(self._v_indexes, 3)
	for vertices in faces:
		for v_inx in range(len(vertices)):
			var vertexA = vertices[v_inx]
			var vertexB = vertices[(v_inx+1) % 3]
			var min_index = min(vertexA, vertexB)
			var max_index = max(vertexA, vertexB)
			
			var edge = self.edges.get([min_index, max_index])
			
			if !edge:
				self.edges[[min_index, max_index]] = 1
			else:
				self.edges[[min_index, max_index]] += 1

func get_loops():
	var unvisted_edges = self.get_outline_edge()
	var loops = []
	
	while len(unvisted_edges) != 0:
		var loop = []
		loop.append_array(unvisted_edges.pop_back())
		var v_next = loop.back()
		while loop[0] != v_next:
			for ue_ind in range(len(unvisted_edges)):
				var v1 = unvisted_edges[ue_ind][0]
				var v2 = unvisted_edges[ue_ind][1]
				
				if v_next == v1:
					loop.append(v2)
					unvisted_edges.pop_at(ue_ind)
					v_next = loop.back()
					break
				elif v_next == v2:
					loop.append(v1)
					unvisted_edges.pop_at(ue_ind)
					v_next = loop.back()
					break
		loops.append(loop)
	
	var vector2_loops = []
	for loop in loops:
		var vector2_loop = []
		for ind in loop:
			var point = self._v_points[ind]
			if extrude_direction == DestructableWall.ExtrudeDirection.BACK:
				vector2_loop.append(Vector2(point.x, point.y))
			elif extrude_direction == DestructableWall.ExtrudeDirection.RIGHT:
				vector2_loop.append(Vector2(point.z, point.y))
			elif extrude_direction == DestructableWall.ExtrudeDirection.UP:
				vector2_loop.append(Vector2(point.x, point.z))
		vector2_loops.append(vector2_loop)
	
	
	var area = []
	for vector2_loop in vector2_loops:
		var result = self.calculate_area(vector2_loop)
		area.append(result)
	
	# reorder the loops where outer boundary is the first one
	var index = 0
	var index_value = area[0]
	for ind in range(len(area)):
		var val = abs(area[ind])
		if val > index_value:
			index = ind
			index_value = val
	
	var bloop = loops.pop_at(index)
	var barea = area.pop_at(index)
	
	loops.push_front(bloop)
	area.push_front(barea)
	
	# check for CW or CCW
	if area[0] > 0: # Outer boundary should be CW, if area is <0 then it is CCW
		loops[0].reverse()
	
	for area_index in range(1, len(area)):
		var a = area[area_index]
		if a < 0: # all inside loops need to rendered in CCW, if + then it is CW
			loops[area_index].reverse()
	
	return loops

func get_outline_edge():
	var outline_edges = []
	for e in self.edges:
		var value = self.edges[e]
		if value == 1:
			outline_edges.append(e)
	return outline_edges

func _calc_triangle_normal(a: Vector3, b: Vector3, c: Vector3):
	return ((b-a).cross(c-a)).normalized()

func _draw_sides():
	var loops = get_loops()
	
	for l in loops:
		var outline_edges_ordered = l
		for outline_vector_ind in range(len(outline_edges_ordered) - 1):
			var v0 = self._v_points[outline_edges_ordered[outline_vector_ind]]
			var v1 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]]
			var v2 = self._v_points[outline_edges_ordered[outline_vector_ind]] + depth_vector
			var v3 = self._v_points[outline_edges_ordered[outline_vector_ind + 1]] + depth_vector
			
			#print("v0: ", v0, " v1: ", v1, " v2: ", v2, " v3: ", v3)
			
			var n1 = self._calc_triangle_normal(v2, v1, v0)
			self.verts.append_array([v0, v1, v2, v1, v3, v2])
			_append_uvs4(v0, v1, v2, v3, n1)
			self.normals.append_array([n1, n1, n1, n1, n1, n1])

func _append_uvs4(v0: Vector3, v1: Vector3, v2: Vector3, v3: Vector3, normal: Vector3):
	if normal == Vector3.BACK or normal == Vector3.FORWARD:
		self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y), Vector2(-v1.x, -v1.y), Vector2(-v3.x, -v3.y), Vector2(-v2.x, -v2.y)])
		return
	elif normal == Vector3.RIGHT or normal == Vector3.LEFT:
		self.uvs.append_array([Vector2(-v0.z, -v0.y), Vector2(-v1.z, -v1.y), Vector2(-v2.z, -v2.y), Vector2(-v1.z, -v1.y), Vector2(-v3.z, -v3.y), Vector2(-v2.z, -v2.y)])
		return
	elif normal == Vector3.UP or normal == Vector3.DOWN:
		self.uvs.append_array([Vector2(-v0.x, -v0.z), Vector2(-v1.x, -v1.z), Vector2(-v2.x, -v2.z), Vector2(-v1.x, -v1.z), Vector2(-v3.x, -v3.z), Vector2(-v2.x, -v2.z)])
		return
	
	self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y), Vector2(-v1.x, -v1.y), Vector2(-v3.x, -v3.y), Vector2(-v2.x, -v2.y)])

func _append_uvs3(v0: Vector3, v1: Vector3, v2: Vector3, normal: Vector3):
	if normal == Vector3.BACK or normal == Vector3.FORWARD:
		self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y)])
		return
	elif normal == Vector3.RIGHT or normal == Vector3.LEFT:
		self.uvs.append_array([Vector2(-v0.z, -v0.y), Vector2(-v1.z, -v1.y), Vector2(-v2.z, -v2.y)])
		return
	elif normal == Vector3.UP or normal == Vector3.DOWN:
		self.uvs.append_array([Vector2(-v0.x, -v0.z), Vector2(-v1.x, -v1.z), Vector2(-v2.x, -v2.z)])
		return
	
	self.uvs.append_array([Vector2(-v0.x, -v0.y), Vector2(-v1.x, -v1.y), Vector2(-v2.x, -v2.y)])

# front face
func _draw_mesh():
	var vector3A = self._v_points[self._v_indexes[0]]
	var vector3B = self._v_points[self._v_indexes[1]]
	var vector3C = self._v_points[self._v_indexes[2]]
	
	var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
	
	
	self.verts.append_array([vector3A, vector3B, vector3C])
	_append_uvs3(vector3A, vector3B, vector3C, normal)

	self.normals.append_array([normal, normal, normal])
	
	# insert each front face into the mesh "clockwise"
	for index in range(3, len(self._v_indexes) - 1, 3):
		vector3A = self._v_points[self._v_indexes[index]]
		vector3B = self._v_points[self._v_indexes[index+1]]
		vector3C = self._v_points[self._v_indexes[index+2]]
		

		self.verts.append_array([vector3A, vector3B, vector3C])
		_append_uvs3(vector3A, vector3B, vector3C, normal)
		self.normals.append_array([normal, normal, normal])
#
## back face
func _extrude_mesh():
	var vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]]
	var vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]]
	var vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]]

	var normal = self._calc_triangle_normal(vector3C, vector3B, vector3A)
	vector3A = self._v_points[self._v_indexes[len(self._v_indexes) - 1]] + depth_vector
	vector3B = self._v_points[self._v_indexes[len(self._v_indexes) - 2]] + depth_vector
	vector3C = self._v_points[self._v_indexes[len(self._v_indexes) - 3]] + depth_vector
	
	self.verts.append_array([vector3A, vector3B, vector3C])
	_append_uvs3(vector3A, vector3B, vector3C, normal)

	self.normals.append_array([normal, normal, normal])
	
	# insert each back face into the mesh "counter-clockwise" extruded
	for index in range(len(self._v_indexes) - 4, -1, -3):
		vector3A = self._v_points[self._v_indexes[index]] + depth_vector
		vector3B = self._v_points[self._v_indexes[index-1]] + depth_vector
		vector3C = self._v_points[self._v_indexes[index-2]] + depth_vector
		
		self.verts.append_array([vector3A, vector3B, vector3C])
		_append_uvs3(vector3A, vector3B, vector3C, normal)
		self.normals.append_array([normal, normal, normal])

func commit_mesh(mat) -> Mesh:
	surface_array[Mesh.ARRAY_VERTEX] = verts
	surface_array[Mesh.ARRAY_NORMAL] = normals
	surface_array[Mesh.ARRAY_TEX_UV] = uvs
	
	#print("VERTS: ", self.verts)
	array_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
	array_mesh.surface_set_material(0, mat)
	
	return array_mesh
