extends Node3D
class_name Place

var tile_grid_size: int = 32

var load_tile: = preload("res://tiles/base_tile/base_tile.tscn")
var load_room: = preload("res://places/base_place/base_room.tscn")
var load_door: = preload("res://tiles/base_tile/base_door.tscn")
var load_actor: = preload("res://actors/base_actor/base_actor.tscn")

@onready var task_list: Node = $TaskList

#Contains all tiles in a workplace.
var lookup_place_to_tile: Dictionary = {}
#Contains all rooms in a workplace.

#Contains the tiles in the current selection drag
var selection_drag_dict: Dictionary = {}
#Contains all the tiles that have been selected in the current build action
var selection_dict: Dictionary = {}

#Tracks the previous amount of tiles counted in a selection drac
var tile_count_x_hist: int = 0
var tile_count_z_hist: int = 0

#Tracks if confirming a build is allowed
var build_confirm_allowed: bool = true

#The object that's currently being built
var current_object
#Where that object can be placed
var available_placements :Array = []
var current_placement :Vector3

#Tracks parts of the room currently being built
var current_room

@onready var map :PlaceMap = $PlaceMap

func save():
	var save_data = {
#Basics
		"id": self.get_instance_id(),
		"type": "Place",
		"scene_file_path": scene_file_path,
		"parent": get_parent().get_path(),
#Connections
#Data
	}
	return save_data


func init_grid() -> void:
	map.temp_create_map()
	init_tiles()
	
	
func init_tiles() -> void:
	for map_pos :Vector3i in map.tile_map.keys():
		var real_pos :Vector3i = map_pos / 12
		var tile :Tile = load_tile.instantiate()
		map.tile_map[map_pos]["object"] = tile
		tile.set_position(real_pos) #TEMP
		tile.map_position = map_pos
		tile.place_position = real_pos
		tile.name = str("Tile", tile.get_instance_id())
		tile.place_id = self.get_instance_id()
		tile.selection_mode = Tile.SEL_MODE.NONE
		tile.construction_mode = Tile.CON_MODE.CLOSED
		$TileContainer.add_child(tile)

func draw_tile_click(click_pos :Vector3) -> void:
#starts a selection drag
	
	var build_start_pos :Vector3i = Vector3i(click_pos.round())
	
	if lookup_place_to_tile.has(build_start_pos):
		if not lookup_place_to_tile[build_start_pos].construction_mode == Tile.CON_MODE.BUILT:
			select_tile(build_start_pos)

func init_select_drag(float_build_start_pos :Vector3, float_build_mouse_pos :Vector3) -> void:
#Creats an array of dragged tiles between mouse start and current position
	
	var select_drag_array: Array = []
	
	var build_start_pos: Vector3i = Vector3i(float_build_start_pos.round())
	var build_mouse_pos: Vector3i = Vector3i(float_build_mouse_pos.round())

	var tile_count_x: int = build_mouse_pos.x - build_start_pos.x
	var tile_count_z: int = build_mouse_pos.z - build_start_pos.z
	
	if not tile_count_x_hist == tile_count_x or not tile_count_z_hist == tile_count_z:
		
		tile_count_x_hist = tile_count_x
		tile_count_z_hist = tile_count_z
		 
		for x in range(min(0, tile_count_x), max(0, tile_count_x) + 1):
			for z in range(min(0, tile_count_z), max(0, tile_count_z) + 1):
				var select_drag_pos: Vector3i = build_start_pos + Vector3i(x, 0, z)
				if lookup_place_to_tile.has(select_drag_pos):
					if not lookup_place_to_tile[select_drag_pos].construction_mode == Tile.CON_MODE.BUILT:
						select_drag_array.append(select_drag_pos)
		
		if select_drag_array:
			draw_select_drag(select_drag_array)

func draw_select_drag(array :Array) -> void:
#Clears previous drag, then calls tile selection on all currently dragged tiles

	for i :Vector3i in lookup_place_to_tile.keys():
		if not selection_dict.has(i):
			var tile: Object = lookup_place_to_tile[i]
			tile.selection_mode = Tile.SEL_MODE.NONE
				
	selection_drag_dict.clear()
	
	for i :Vector3i in array:
		select_tile(i)

func select_tile(pos :Vector3i) -> void:
#Tells tiles to be selected

	var tile: Object = lookup_place_to_tile[pos]
	
	selection_drag_dict[pos] = tile
	
	if build_confirm_allowed:
		tile.selection_mode = Tile.SEL_MODE.BUILD
	else:
		tile.selection_mode = Tile.SEL_MODE.INVALID

func verify_room() -> bool:
#Verifies that a given selection is fully contiguous
	
	var verify_array: Array = selection_dict.keys()
	var verify_queue_array: Array = [verify_array[0]]
	var verify_checked_array: Array = []

	while verify_array:
		if not verify_queue_array:

			return false

		var verify_pos: Vector3i = verify_queue_array.pop_back()
		
		var verify_neighbor_array: Array = lookup_place_to_tile[verify_pos].direction_vector_dict.values()
		
		for n :Vector3i in verify_neighbor_array:
		
			if selection_dict.has(verify_pos + n):
				if not verify_checked_array.has(verify_pos + n):
					if not verify_queue_array.has(verify_pos + n):
						verify_queue_array.append(verify_pos + n)

		verify_checked_array.append(verify_pos)
		verify_array.erase(verify_pos)
	
	return true

func end_select_drag() -> void:
#Adds dragged tiles to the current selection on mouse-up
	if not selection_drag_dict:
		return
	
	tile_count_x_hist = 0
	tile_count_z_hist = 0

	selection_dict.merge(selection_drag_dict)
	
	if verify_room():
		build_confirm_allowed = true
		for i :Vector3i in selection_dict.keys():
			lookup_place_to_tile[i].selection_mode = Tile.SEL_MODE.BUILD
	else:
		build_confirm_allowed = false
		for i :Vector3i in selection_dict.keys():
			lookup_place_to_tile[i].selection_mode = Tile.SEL_MODE.INVALID
			
	selection_drag_dict.clear()

func build_selection() -> void:
#When the build button is clicked, changes the selected tiles to match the button's request
	
	if not build_confirm_allowed:
		return
	
	if selection_dict:
		
		create_room(selection_dict)
		
		for i :Vector3i in selection_dict.keys():
			map.tile_map[i * 12]["state"] = PlaceMap.TILE_STATE.OPEN
			for j :Vector3i in Tile.direction_vector_dict.values():
				if not selection_dict.keys().has(i - j):
					map.wall_map[(i * 12) - (j * 6)]["state"] = PlaceMap.WALL_STATE.CLOSED
		
		for i :Vector3i in selection_dict.keys():
			var tile: Object = lookup_place_to_tile[i]
			tile.selection_mode = Tile.SEL_MODE.NONE
			tile.construction_mode = Tile.CON_MODE.BUILT
		
		selection_dict.clear()

func clear_selection() -> void:
#When the clear button is clicked, it clears the selected tiles without doing anything.

	for i :Vector3i in selection_dict.keys():
		var tile: Object = selection_dict[i]
		tile.selection_mode = Tile.SEL_MODE.NONE
		build_confirm_allowed = true

	selection_dict.clear()

func create_room(selection :Dictionary) -> Room:
#Creates a room from the selected tiles.

	var room: Object = load_room.instantiate()
	
	room.position = (selection.keys().min())
	
	room.room_to_tile = selection.values()
	room.place_id = self.get_instance_id()
	
	room.name = str("Room", room.get_instance_id())
	
	$RoomContainer.add_child(room)
	
	map.room_map[room] = {
		"tiles": selection.values(),
		"doors": [],
	}

	for i :Tile in room.room_to_tile:
		i.lookup_tile_to_room = room
				
	room.init_room(selection)
	
	return room

func init_object(object) -> void:
#Instantiates the object to be placed

	match object:
		"door":
			var door: Object = load_door.instantiate()
			
			init_object_placements(door)
			
			door.place_id = self.get_instance_id()
			door.visible = false
			
			$ObjectContainer.add_child(door)
			
			current_object = door
			
		"actor":
			var actor: Actor = load_actor.instantiate()
			
			current_object = actor
			
			actor.visible = false
			
			$ActorContainer.add_child(actor)
			
		
	

func init_object_placements(object :Object) -> void:
#Gets the list of locations that the object can be placed

	match object.placement:
		GameObject.PLACEMENTS.DOOR:
			for i :Vector3i in map.wall_map:
				var tile_1 :Vector3i
				var tile_2 :Vector3i
				if map.wall_map[i]["state"] == map.WALL_STATE.CLOSED:
					if i.x % 12 == 0:
						tile_1 = i + Vector3i(0, 0, -6)
						tile_2 = i + Vector3i(0, 0, 6)
					else:
						tile_1 = i + Vector3i(-6, 0, 0)
						tile_2 = i + Vector3i(6, 0, 0)
					if map.tile_map.has(tile_1) and map.tile_map.has(tile_2):
						if map.tile_map[tile_1]["state"] == map.TILE_STATE.OPEN and map.tile_map[tile_2]["state"] == map.TILE_STATE.OPEN:
							available_placements.append(Vector3(i) / 12)

func hover_object(mouse_pos :Vector3) -> void:
#Hovers the object at the closest acceptable placement location to the mouse
	
	current_object.visible = true
	
	if available_placements:
		var closest: Vector3
		var closest_distance: float = INF

		for i :Vector3 in available_placements:
			var distance: float = mouse_pos.distance_to(i)
			if closest_distance > distance:
				closest_distance = distance
				closest = i
		
		current_placement = closest
		current_object.position = current_placement
		current_object.rotation_degrees = Vector3(0, 180 * fmod(closest.x, 1), 0)
		
	else:
		current_object.position = mouse_pos

func confirm_object() -> void:
#Places the object at the hovered location

	if current_object is Door:
		var pos :Vector3i = Vector3i(current_placement * 12)
		#map.wall_map[pos]["state"] = map.WALL_STATE.DOOR
		#if pos.x % 12 == 0:
			#tile_1_pos = pos + Vector3i(0, 0, -6)
			#tile_2_pos = pos + Vector3i(0, 0, 6)
		#else:
			#tile_1_pos = pos + Vector3i(-6, 0, 0)
			#tile_2_pos = pos + Vector3i(6, 0, 0)
		#var tile_1 :Tile = map.tile_map[tile_1_pos]["object"]
		#var tile_2 :Tile = map.tile_map[tile_2_pos]["object"]
		
		#tile_1.update_faces()
		#tile_2.update_faces()
		map.write_door(pos)
		#map.room_map[tile_1.lookup_tile_to_room]["tiles"].append(tile_2)
		#tile_1.lookup_tile_to_room.update_nav()
		#var point_pos_1 :Vector3i = pos - ((pos - tile_1_pos) / 2)
		#var id_1 :int = map.place_nav.get_available_point_id()
		#map.place_nav.add_point(id_1, point_pos_1)
		#for i :Vector3i in map.room_map[tile_1.lookup_tile_to_room]["doors"]:
			#var id :int = map.place_nav.get_closest_point(i)
			#map.place_nav.connect_points(id_1, id)
		#map.room_map[tile_1.lookup_tile_to_room]["doors"].append(point_pos_1)
		#map.room_map[tile_2.lookup_tile_to_room]["tiles"].append(tile_1)
		#tile_2.lookup_tile_to_room.update_nav()
		#var point_pos_2 :Vector3i = pos - ((pos - tile_2_pos) / 2)
		#var id_2 :int = map.place_nav.get_available_point_id()
		#map.place_nav.add_point(id_2, point_pos_2)
		#for i :Vector3i in map.room_map[tile_2.lookup_tile_to_room]["doors"]:
			#var id :int = map.place_nav.get_closest_point(i)
			#map.place_nav.connect_points(id_2, id)
		#map.room_map[tile_2.lookup_tile_to_room]["doors"].append(point_pos_2)
		#
		#map.place_nav.connect_points(id_1, id_2)
		
		
		
		
		current_object.queue_free()
		#
		#var tile_1: Object
		#var tile_2: Object
#
		#if fmod(current_object.position.x, 1) == 0:
			#tile_1 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z)]
			#tile_2 = lookup_place_to_tile[Vector3i(current_object.position.x - 1, 0, current_object.position.z)]
		#else:
			#tile_1 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z)]
			#tile_2 = lookup_place_to_tile[Vector3i(current_object.position.x, 0, current_object.position.z - 1)]
		#
		#var tile_1_new_dict = tile_1.face_dict.duplicate()
		#var tile_2_new_dict = tile_2.face_dict.duplicate()
				#
		#var tile_1_door_face: Vector3i = tile_2.position - tile_1.position
		#
		#tile_1_new_dict[tile_1_door_face] = current_object
		#
		#tile_1.update_faces()
		#
		#tile_1.lookup_tile_to_room.room_to_door.append(current_object)
		#
		#tile_2.lookup_connected_rooms.append(tile_1.lookup_tile_to_room)
		#
		#tile_2.lookup_tile_to_room.room_to_tile.append(tile_1)
		#
		#var tile_2_door_face: Vector3i = tile_1.position - tile_2.position
		#
		#tile_2_new_dict[tile_2_door_face] = current_object
		#
		#tile_2.update_faces()
		#
		#tile_2.lookup_tile_to_room.room_to_door.append(current_object)
		#
		#tile_1.lookup_connected_rooms.append(tile_2.lookup_tile_to_room)
		#
		#tile_1.lookup_tile_to_room.room_to_tile.append(tile_2)
		#
		#current_object.lookup_door_to_room.append(tile_1.lookup_tile_to_room)
		#current_object.lookup_door_to_room.append(tile_2.lookup_tile_to_room)
		#current_object.lookup_door_to_tile[tile_1] = tile_1_door_face
		#current_object.lookup_door_to_tile[tile_2] = tile_2_door_face
		#
		#var id = map.get_available_point_id()
		#map.add_point(id, current_object.position)
		#current_object.map_point = id
		#prints(map.get_point_ids())
		#for i in tile_1.lookup_tile_to_room.room_to_door:
			#if i.map_point != id:
				#map.connect_points(i.map_point, id)
		#for i in tile_2.lookup_tile_to_room.room_to_door:
			#if i.map_point != id:
				#map.connect_points(i.map_point, id)
		#prints(map.get_point_connections(id))
		
	elif current_object is Actor:
		current_object.actor_state = Actor.ACTOR_STATE.PAUSED
	
	current_room = null
	available_placements = []
	current_object = null

func give_neighbors(tile, map_position, directions):
#Responds to a tile's request for it's neighbors

	var neighbor_dict = {}
	for i in directions.keys():
		if lookup_place_to_tile.has(directions[i] + map_position):
			neighbor_dict[directions[i]] = lookup_place_to_tile[directions[i] + map_position]
	tile.neighbor_dict = neighbor_dict

func give_walls(tile :Tile) -> void:
	map.give_walls(tile)

func _on_tile_container_child_entered_tree(node: Node) -> void:
#Connects signals from created tiles

	lookup_place_to_tile[node.place_position] = node
	node.connect("neighbor_request", give_neighbors)
	node.connect("wall_request", give_walls)

func _on_actor_container_child_entered_tree(node: Node) -> void:
#Connects signals from created actors

	node.connect("current_tile_request", give_tile)
	node.connect("task_path_request", give_path)
	node.connect("room_path_request", give_room)
	if node.has_node("TaskCreator"):
		var tasker = node.get_node("TaskCreator")
		tasker.connect("record_task", record_task)
		tasker.connect("finish_task", finish_task)
		
func _on_room_container_child_entered_tree(node: Node) -> void:
#Connects signals from created rooms
	
	node.connect("nav_request", give_nav)

func give_nav(room):
	map.give_nav(room)

func give_path(actor :Actor, start :Vector3i, end :Vector3i) -> void:
	var start_tile :Tile = lookup_place_to_tile[start]
	var end_tile :Tile = lookup_place_to_tile[end]
	map.give_path(actor, start_tile, end_tile)
	
func give_room(actor, start, end):
	map.give_room(actor, start, end)
	
	#var end_tile:Tile = lookup_place_to_tile[end]
	#if end_tile.construction_mode == Tile.CON_MODE.BUILT:
		#
		#var start_id = map.get_available_point_id()
		#map.add_point(start_id, start)
		#var start_room_doors = lookup_place_to_tile[start].lookup_tile_to_room.room_to_door
		#if start_room_doors == []:
			#actor.temp_complete_task()
			#return
		#for i in start_room_doors:
			#var door_id = map.get_closest_point(i.position)
			#map.connect_points(start_id, door_id)
			#
		#var end_id = map.get_available_point_id()
		#map.add_point(end_id, end)
		#var end_room_doors = lookup_place_to_tile[end].lookup_tile_to_room.room_to_door
		#if end_room_doors == []:
			#actor.temp_complete_task()
			#return
		#for i in end_room_doors:
			#var door_id = map.get_closest_point(i.position)
			#map.connect_points(end_id, door_id)
		#var place_path_id = map.get_id_path(start_id, end_id)
		#var place_path_pos = []
		#for i in place_path_id:
			#place_path_pos.append(map.get_point_position(i))
		#actor.current_place_path = place_path_pos
	#else:
		#actor.temp_complete_task()
	
func give_tile(actor):
#Responds to an actor's request for its current tile

	#var closest: Vector3
	#var closest_distance: float = INF
	#
	#for i in lookup_place_to_tile:
		#var distance: float = actor.position.distance_to(i)
		#if closest_distance > distance:
			#closest_distance = distance
			#closest = i
	var loc = Vector3i(round(actor.position.x), 0, round(actor.position.z))
	actor.current_tile = lookup_place_to_tile[loc]

func record_task(task):
#Records created tasks in the task list.

	task_list.record_task(task)

func finish_task(source, task):
#Marks tasks as completed in the task list.

	task_list.finish_task(source, task)
