# MIT License
#
# Copyright (c) 2023 Mark McKay
# https://github.com/blackears/cyclopsLevelBuilder
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

#@deprecated
@tool
extends RefCounted
class_name GeneralMesh


class VertexInfo extends RefCounted:
	var index:int
	var point:Vector3
	var edge_indices:Array[int] = []
	var selected:bool
	
	func _init(_index:int, _point:Vector3 = Vector3.ZERO):
		index = _index
		point = _point
		
	func _to_string():
		var s:String = "%s %s [" % [index, point]
		for i in edge_indices:
			s += "%s " %  i
		s += "]"
			
		return s

class EdgeInfo extends RefCounted:
	var index:int
	var start_index:int
	var end_index:int
	var face_indices:Array[int] = []
	var selected:bool
	
	func _init(_index:int, _start:int = 0, _end:int = 0):
		index = _index
		start_index = _start
		end_index = _end

	func _to_string():
		var s:String = "%s %s %s [" % [index, start_index, end_index]
		for i in face_indices:
			s += "%s " % i
		s += "]"
		return s

class FaceInfo extends RefCounted:
	var index:int
	var normal:Vector3
#	var vertex_indices:Array[int]
	var face_corner_indices:Array[int]
	var material_index:int
	var selected:bool
	
	func _init(_index:int, _face_corner_indices:Array[int] = [], _mat_index:int = 0):
		index = _index
		face_corner_indices = _face_corner_indices
		material_index = _mat_index

	func _to_string():
		var s:String = "%s %s %s [" % [index, normal, material_index]
		for i in face_corner_indices:
			s += "%s " % i
		s += "]"
		return s

class FaceCornerInfo extends RefCounted:
	var index:int
	var uv:Vector2
	var vertex_index:int
	var face_index:int
	var selected:bool
	
	func _init(_index:int, _vertex_index:int, _face_index:int):
		vertex_index = _vertex_index
		face_index = _face_index

	func _to_string():
		var s:String = "%s %s %s %s" % [index, uv, vertex_index, face_index]
		return s
		
	

var vertices:Array[VertexInfo] = []
var edges:Array[EdgeInfo] = []
var faces:Array[FaceInfo] = []
var face_corners:Array[FaceCornerInfo] = []
var bounds:AABB

#var points:PackedVector3Array

func _init():
	pass

func get_face_indices()->PackedInt32Array:
	var result:PackedInt32Array
	for f in faces:
		result.append(f.index)
	return result

func clear_lists():
	vertices = []
	edges = []
	faces = []
	face_corners = []
	bounds = AABB()

func init_block(block_bounds:AABB):
	var p000:Vector3 = block_bounds.position
	var p111:Vector3 = block_bounds.end
	var p001:Vector3 = Vector3(p000.x, p000.y, p111.z)
	var p010:Vector3 = Vector3(p000.x, p111.y, p000.z)
	var p011:Vector3 = Vector3(p000.x, p111.y, p111.z)
	var p100:Vector3 = Vector3(p111.x, p000.y, p000.z)
	var p101:Vector3 = Vector3(p111.x, p000.y, p111.z)
	var p110:Vector3 = Vector3(p111.x, p111.y, p000.z)
	
	init_prism([p000, p001, p011, p010], p100 - p000)
	

func init_prism(base_points:Array[Vector3], extrude_dir:Vector3):
	
	var verts:PackedVector3Array
	for p in base_points:
		verts.append(p)
	for p in base_points:
		verts.append(p + extrude_dir)

	var index_list:PackedInt32Array
	var face_len_list:PackedInt32Array
	
	var num_points:int = base_points.size()
	for i0 in num_points:
		var i1:int = wrap(i0 + 1, 0, num_points)
		
		index_list.append(i0)
		index_list.append(i1)
		index_list.append(i1 + num_points)
		index_list.append(i0 + num_points)
		face_len_list.append(4)
	
	for i0 in num_points:
#		index_list.append(i0)
		index_list.append(num_points - i0 - 1)
	face_len_list.append(num_points)
	
	for i0 in num_points:
		index_list.append(i0 + num_points)
#		index_list.append(num_points * 2 - i0 - 1)
	face_len_list.append(num_points)
	
	init_from_face_lists(verts, index_list, face_len_list)
	
	
func init_from_face_lists(verts:PackedVector3Array, index_list:PackedInt32Array, face_len_list:PackedInt32Array):
	clear_lists()

	for i in verts.size():
		var v:VertexInfo = VertexInfo.new(i, verts[i])	
		vertices.append(v)
		
		if i == 0:
			bounds = AABB(verts[0], Vector3.ZERO)
		else:
			bounds = bounds.expand(verts[i])
	
	var vertex_index_offset:int = 0
	for face_index in face_len_list.size():
		var num_face_verts = face_len_list[face_index]
#		if num_face_verts < 3:
#			continue
		
		var face_corners_local:Array[int] = []
		for i in num_face_verts:
			var face_corner_index:int = face_corners.size()
			var face_corner:FaceCornerInfo = FaceCornerInfo.new(face_corner_index, index_list[vertex_index_offset], face_index)
			face_corners.append(face_corner)
			face_corners_local.append(face_corner_index)
			vertex_index_offset += 1
		
		var face:FaceInfo = FaceInfo.new(face_index, face_corners_local)
		faces.append(face)

		#Calc normal		
		var fc0:FaceCornerInfo = face_corners[face_corners_local[0]]
#		var vidx0 = fc0.vertex_index
		var p0:Vector3 = vertices[fc0.vertex_index].point
#
		var weighted_normal:Vector3
		for i in range(1, num_face_verts - 1):
			var fc1:FaceCornerInfo = face_corners[face_corners_local[i]]
			var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]]
#			var vidx1 = fc1.vertex_index
#			var vidx2 = fc2.vertex_index
			var p1:Vector3 = vertices[fc1.vertex_index].point
			var p2:Vector3 = vertices[fc2.vertex_index].point

			var v1:Vector3 = p1 - p0
			var v2:Vector3 = p2 - p0
			weighted_normal += v2.cross(v1)
			
		face.normal = weighted_normal.normalized()
			
	#Calculate edges
	for face in faces:
		var num_corners = face.face_corner_indices.size()
		for i0 in num_corners:
			var i1:int = wrap(i0 + 1, 0, num_corners)
			var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]]
			var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]]
			
			var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index)
			if !edge:
				var edge_idx = edges.size()
				edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index)
				edges.append(edge)
			
				var v0:VertexInfo = vertices[fc0.vertex_index]
				v0.edge_indices.append(edge_idx)
				
				var v1:VertexInfo = vertices[fc1.vertex_index]
				v1.edge_indices.append(edge_idx)

			edge.face_indices.append(face.index)


func get_edge(vert_idx0:int, vert_idx1:int)->EdgeInfo:
	for e in edges:
		if e.start_index == vert_idx0 && e.end_index == vert_idx1:
			return e
		if e.start_index == vert_idx1 && e.end_index == vert_idx0:
			return e
	return null


func init_block_data(block:BlockData):
	clear_lists()

	for i in block.points.size():
		var v:VertexInfo = VertexInfo.new(i, block.points[i])	
		vertices.append(v)
		
		if i == 0:
			bounds = AABB(v.point, Vector3.ZERO)
		else:
			bounds = bounds.expand(v.point)

	var corner_index_offset:int = 0
	for face_index in block.face_vertex_count.size():
		var num_face_verts = block.face_vertex_count[face_index]
		
		var face_corners_local:Array[int] = []
		for i in num_face_verts:
			var vertex_index = block.face_vertex_indices[corner_index_offset]
			
			var face_corner:FaceCornerInfo = FaceCornerInfo.new(corner_index_offset, vertex_index, face_index)
			face_corner.uv = block.uvs[corner_index_offset]
			face_corners.append(face_corner)
			face_corners_local.append(corner_index_offset)
			corner_index_offset += 1
		
		var face:FaceInfo = FaceInfo.new(face_index, face_corners_local)
		face.material_index = block.face_material_indices[face_index]
		faces.append(face)
		
		#Calc normal		
		var fc0:FaceCornerInfo = face_corners[face_corners_local[0]]
		var p0:Vector3 = vertices[fc0.vertex_index].point
#
		var weighted_normal:Vector3
		for i in range(1, num_face_verts - 1):
			var fc1:FaceCornerInfo = face_corners[face_corners_local[i]]
			var fc2:FaceCornerInfo = face_corners[face_corners_local[i + 1]]
			var p1:Vector3 = vertices[fc1.vertex_index].point
			var p2:Vector3 = vertices[fc2.vertex_index].point

			var v1:Vector3 = p1 - p0
			var v2:Vector3 = p2 - p0
			weighted_normal += v2.cross(v1)
			
		face.normal = weighted_normal.normalized()
			
	#Calculate edges
	for face in faces:
		var num_corners = face.face_corner_indices.size()
		for i0 in num_corners:
			var i1:int = wrap(i0 + 1, 0, num_corners)
			var fc0:FaceCornerInfo = face_corners[face.face_corner_indices[i0]]
			var fc1:FaceCornerInfo = face_corners[face.face_corner_indices[i1]]
			
			var edge:EdgeInfo = get_edge(fc0.vertex_index, fc1.vertex_index)
			if !edge:
				var edge_idx = edges.size()
				edge = EdgeInfo.new(edge_idx, fc0.vertex_index, fc1.vertex_index)
				edges.append(edge)
			
				var v0:VertexInfo = vertices[fc0.vertex_index]
				v0.edge_indices.append(edge_idx)
				
				var v1:VertexInfo = vertices[fc1.vertex_index]
				v1.edge_indices.append(edge_idx)

			edge.face_indices.append(face.index)


func to_block_data()->BlockData:
	var block:BlockData = preload("res://addons/cyclops_level_builder/resources/block_data.gd").new()
#	var block:BlockData = BlockData.new()
	
	for v in vertices:
		block.points.append(v.point)
	
	for f in faces:
		block.face_vertex_count.append(f.face_corner_indices.size())
		block.face_material_indices.append(f.material_index)
		
		for fc_idx in f.face_corner_indices:
			var fc:FaceCornerInfo = face_corners[fc_idx]
			block.face_vertex_indices.append(fc.vertex_index)
			block.uvs.append(fc.uv)
	
	return block

func append_mesh(mesh:ImmediateMesh, material:Material, color:Color = Color.WHITE):
	
	for face in faces:
		mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, material)
#		print("face %s" % face.index)
		
		mesh.surface_set_normal(face.normal)
		
		var num_corners:int = face.face_corner_indices.size()
		for i in num_corners:
			var idx = (i + 1) / 2 if i & 1 else wrap(num_corners - (i / 2), 0, num_corners)
			var fc:FaceCornerInfo = face_corners[face.face_corner_indices[idx]]

			mesh.surface_set_color(color)
			mesh.surface_set_uv(fc.uv)
			mesh.surface_add_vertex(vertices[fc.vertex_index].point)
#			print ("%s %s %s" % [idx, fc.vertex_index, control_mesh.vertices[fc.vertex_index].point])
	
		mesh.surface_end()
		
func triplanar_unwrap(scale:float = 1):
	for fc in face_corners:
		var v:VertexInfo = vertices[fc.vertex_index]
		var f:FaceInfo = faces[fc.face_index]
		
		if abs(f.normal.x) > abs(f.normal.y) && abs(f.normal.x) > abs(f.normal.z):
			fc.uv = Vector2(v.point.y, v.point.z) * scale
		elif abs(f.normal.y) > abs(f.normal.z):
			fc.uv = Vector2(v.point.x, v.point.z) * scale
		else:
			fc.uv = Vector2(v.point.x, v.point.y) * scale


func get_face_points(face:FaceInfo)->PackedVector3Array:
	var points:PackedVector3Array
	for fc_idx in face.face_corner_indices:
		var fc:FaceCornerInfo = face_corners[fc_idx]
		points.append(vertices[fc.vertex_index].point)
	return points
	
func triangulate_face(face:FaceInfo)->PackedVector3Array:
	var points:PackedVector3Array = get_face_points(face)
	return MathUtil.trianglate_face(points, face.normal)
	

func intersect_ray_closest(origin:Vector3, dir:Vector3)->IntersectResults:
	if bounds.intersects_ray(origin, dir) == null:
		return null
	
	var best_result:IntersectResults
	
	for f in faces:
		var tris:PackedVector3Array = triangulate_face(f)
		for i in range(0, tris.size(), 3):
			var p0:Vector3 = tris[i]
			var p1:Vector3 = tris[i + 1]
			var p2:Vector3 = tris[i + 2]
			
			#Godot uses clockwise winding
			var tri_area_x2:Vector3 = MathUtil.triangle_area_x2(p0, p1, p2)
			
			var p_hit:Vector3 = MathUtil.intersect_plane(origin, dir, p0, tri_area_x2)
			if !p_hit.is_finite():
				continue
			
			if MathUtil.triangle_area_x2(p_hit, p0, p1).dot(tri_area_x2) < 0:
				continue
			if MathUtil.triangle_area_x2(p_hit, p1, p2).dot(tri_area_x2) < 0:
				continue
			if MathUtil.triangle_area_x2(p_hit, p2, p0).dot(tri_area_x2) < 0:
				continue
			
			#Intersection
			var dist_sq:float = (origin - p_hit).length_squared()
			if !best_result || best_result.distance_squared > dist_sq:
			
				var result:IntersectResults = IntersectResults.new()
				result.face_index = f.index
				result.normal = f.normal
				result.position = p_hit
				result.distance_squared = dist_sq
				
				best_result = result
				
	return best_result
	
func translate(offset:Vector3):
	for v in vertices:
		v.point += offset
	
func dump():
	print ("Verts")
	for v in vertices:
		print(v.to_string())	
	print ("Edges")
	for e in edges:
		print(e.to_string())	
	print ("Faces")
	for f in faces:
		print(f.to_string())	
	print ("Face Corners")
	for f in face_corners:
		print(f.to_string())	
