tool
extends Spatial

export(bool) var generate_on_ready : bool = true
export(PropData) var start_room : PropData
export(Array, PropData) var rooms : Array
export(PropData) var plug : PropData
export(bool) var generate : bool setget set_generate, get_generate
export(bool) var spawn_mobs : bool = true

export(int) var min_level : int = 1
export(int) var max_level : int = 2

export(int) var dungeon_seed : int = 0

#todo calc aabbs and store in PropData during prop conversion
var room_hulls : Dictionary
var portal_map : Dictionary
var portals : Array

var current_aabbs : Array

var debug : bool = true

var _rng : RandomNumberGenerator = RandomNumberGenerator.new()

func _enter_tree() -> void:
	if not Engine.editor_hint && generate_on_ready:
		call_deferred("generate")

func set_up_room_data() -> void:
	clear_room_data()
	
	process_prop(start_room)
	
	for r in rooms:
		process_prop(r)
		
	process_portals()
	
func process_prop(room : PropData) -> void:
	if !room:
		return
		
	if !room.is_room:
		return
		
	if room in room_hulls:
		return
		
	var ps : PoolVector3Array = room.room_bounds
	
	#Convert the bounds to 2d
	#This should probably be done in 3d later
	#I found no simple way to do it for now
	#Will look later
		
	var points : PoolVector2Array = PoolVector2Array()
		
	for p in ps:
		points.push_back(Vector2(p.x, p.z))
	
	points = Geometry.convex_hull_2d(points)
	
	room_hulls[room] = points

	for i in range(room.props.size()):
		var pe : PropDataEntry = room.props[i]
		
		if pe is PropDataPortal:
			portals.append([pe, room, i])
				
		
func process_portals() -> void:
	for i in range(portals.size()):
		var pe = portals[i]
		
		var portal : PropDataPortal = pe[0]
		var room : PropData = pe[1]
		
		var portal_points : PoolVector2Array = portal.points
		
		if portal in portal_map:
			continue
			
		var map_data : Array = Array()
		
		for j in range(portals.size()):
			if i == j:
				continue
				
			var pp : Array = portals[j]
			var cportal : PropDataPortal = pp[0]
			
			var cportal_points : PoolVector2Array = cportal.points
			
			if (cportal_points.size() != portal_points.size()):
				continue
				
			var eq : bool = true
				
			for k in range(portal_points.size()):
				var p1 : Vector2 = portal_points[k]
				var p2 : Vector2 = cportal_points[k]
				
				if !p1.is_equal_approx(p2):
					eq = false
					break
			
			if !eq:
				continue
				
			var croom : PropData = pp[1]
			var cj : int = pp[2]

			map_data.append([ croom, cportal, cj ])
			
		portal_map[portal] = map_data
		

func clear_room_data() -> void:
	portal_map.clear()
	portals.clear()
	room_hulls.clear()
	current_aabbs.clear()

func generate() -> void:
	_rng.seed = dungeon_seed
	
	clear()
	set_up_room_data()
	
	spawn_room(Transform(), start_room)
	
func spawn_room(room_lworld_transform : Transform, room : PropData, level : int = 0, current_portal : PropDataPortal = null) -> void:
	if level > 4:
		var plugi : PropInstanceMerger = PropInstanceMerger.new()
		plugi.prop_data = plug
		plugi.first_lod_distance_squared = 4000
		add_child(plugi)
		plugi.transform = room_lworld_transform
		
		#test_spawn_pos(room_lworld_transform)
		
		return

	var orig_room_lworld_transform : Transform = room_lworld_transform
	
	if current_portal:
		var lworld_curr_portal : Transform = current_portal.transform
		#portal center should be precalculated
		#this will only work with the current portals
		lworld_curr_portal = lworld_curr_portal.translated(Vector3(-0.5, 0, 0))
		lworld_curr_portal.basis = lworld_curr_portal.basis.rotated(Vector3(0, 1, 0), PI)
		room_lworld_transform = room_lworld_transform * lworld_curr_portal.inverse()

	var sr : PropInstanceMerger = PropInstanceMerger.new()
	sr.prop_data = room
	sr.first_lod_distance_squared = 4000
	add_child(sr)
	sr.transform = room_lworld_transform
	
	var cab : PoolVector2Array = room_hulls[room]
	var ctfab : PoolVector2Array = PoolVector2Array()
			
	for a in cab:
		var v : Vector3 = Vector3(a.x, 0, a.y)
		v = room_lworld_transform.xform(v)
		ctfab.push_back(Vector2(v.x, v.z))
		#v.y = 0
		#test_spawn_pos(Transform(Basis(), v))
	
	current_aabbs.push_back(ctfab)
	
	if spawn_mobs && level > 0 && ctfab.size() > 0:
		if _rng.randi() % 3 == 0:
			var v2 : Vector2 = ctfab[0]
			
			for i in range(1, ctfab.size()):
				v2 = v2.linear_interpolate(ctfab[i], 0.5)
				
			var gt : Transform = global_transform
			var scale : Vector3 = gt.basis.get_scale()
			v2 *= Vector2(scale.x, scale.z)
				
			ESS.entity_spawner.spawn_mob(0, min_level + (_rng.randi() % (max_level - min_level)), Vector3(v2.x, gt.origin.y, v2.y))
	
	#if Engine.editor_hint and debug:
	#	sr.owner = get_tree().edited_scene_root
		
	for pe in room.props:
		if pe is PropDataPortal:
			if pe == current_portal:
				continue
			
			var current_portal_lworld_position : Transform =  room_lworld_transform * pe.transform
			
			var d : Array = portal_map[pe]
			
			if d.size() == 0:
				continue
				
			var new_room_data = d[_rng.randi() % d.size()]
			
			#[ croom, cportal, cj ]
			var new_room : PropData = new_room_data[0]
			var new_room_portal : PropDataPortal = new_room_data[1]
			
			#todo figure out the transforms
			var poffset : Vector3 = new_room_portal.transform.xform(Vector3())

			#middle of the current portal
			var offset_current_portal_lworld_position : Transform = current_portal_lworld_position
			#portal center should be precalculated
			#this will only work with the current portals
			offset_current_portal_lworld_position = offset_current_portal_lworld_position.translated(Vector3(-0.5, 0, 0))
			
			var ab : PoolVector2Array = room_hulls[new_room]
			var tfab : PoolVector2Array = PoolVector2Array()
			
			for a in ab:
				var v : Vector3 = Vector3(a.x, 0, a.y)
				v = offset_current_portal_lworld_position.xform(v)
				tfab.push_back(Vector2(v.x, v.z))
#				v.y = 0
#				test_spawn_pos(Transform(Basis(), v))
			
			#test_spawn_pos(offset_current_portal_lworld_position)
			
			var can_spawn : bool = true
			for saab in current_aabbs:
				var ohull : PoolVector2Array = saab
				
				var poly_int_res : Array = Geometry.intersect_polygons_2d(ohull, tfab)

				if poly_int_res.size() > 0:
					for poly in poly_int_res:
						var indices : PoolIntArray = Geometry.triangulate_polygon(poly)

						for i in range(0, indices.size(), 3):
							var p1 : Vector2 = poly[indices[i]]
							var p2 : Vector2 = poly[indices[i + 1]]
							var p3 : Vector2 = poly[indices[i + 2]]
							
							var pp1 : float = (p1.x * p2.y + p2.x * p3.y + p3.x * p1.y)
							var pp2 : float = (p2.x * p1.y + p3.x * p2.y + p1.x * p3.y)
							var area : float  = 0.5 * (pp1 - pp2)

							if area > 0.2:
								#print(area)
								#print(poly)
								#print(indices)
								
								#for p in poly:
								#	test_spawn_pos(Transform(Basis(), Vector3(p.x, 0, p.y)))
								
								can_spawn = false
								break
								
					if !can_spawn:
						break
						
				if !can_spawn:
					break
				
			if can_spawn:
				spawn_room(offset_current_portal_lworld_position, new_room, level + 1, new_room_portal)
			else:
				var plugi : PropInstanceMerger = PropInstanceMerger.new()
				plugi.prop_data = plug
				add_child(plugi)
				plugi.transform = offset_current_portal_lworld_position
				#test_spawn_pos(offset_current_portal_lworld_position)

func clear() -> void:
	if Engine.editor_hint and debug:
		#don't destroy the user's nodes
		for c in get_children():
			if c.owner == get_tree().edited_scene_root:
				c.queue_free()
	else:
		for c in get_children():
			c.queue_free()

func test_spawn_pos(lworld_position : Transform, color : Color = Color(1, 1, 1)):
	var testspat : ImmediateGeometry = ImmediateGeometry.new()
	add_child(testspat)
	testspat.transform = lworld_position
	testspat.begin(Mesh.PRIMITIVE_LINES)
	testspat.set_color(color)
	testspat.add_vertex(Vector3(0, -0.5, 0))
	testspat.set_color(color)
	testspat.add_vertex(Vector3(0, 0.5, 0))
	testspat.set_color(color)
	testspat.add_vertex(Vector3(-0.5, 0, 0))
	testspat.set_color(color)
	testspat.add_vertex(Vector3(0.5, 0, 0))
	testspat.set_color(color)
	testspat.add_vertex(Vector3(0, 0, -0.5))
	testspat.set_color(color)
	testspat.add_vertex(Vector3(0, 0, 0.5))
	testspat.end()


func set_generate(on) -> void:
	if on:
		generate()

func get_generate() -> bool:
	return false