extends Node3D
const ELEMENT_SIZE = 2

const Screw = preload("res://scenes/entities/screws_and_tools/screw.gd")


@onready var tiles_layer = $"tiles"
@onready var objects_layer = $"objects"
@onready var floor_objects_layer = $"floor_objects"
@onready var decoration_layer = $"decoration"
@onready var camera_rig = $"camera_rig"



var game = null
var level_size = 0
var initialized = false


var level_chunks = {}
var elem_ids = {} # =elem_ids
var metadata = {}

var level_chunk = preload("res://scenes/level/level_chunk.gd")
var templates = preload("res://scenes/level/templates.gd").new()
var builder = preload("res://scenes/level/builder.gd").new(self)
var loader = preload("res://scenes/level/loader.gd").new(self)


# the level is build up of level chunks, which contain a number of element holders, often just called elements.
# the amount currently defined in the level_manager. like for example 2x2x2 = 8 elements (holders) in a chunk.
# each element holder contains a sub element holder for each type of element that it holds.
# the actual elements are then for example tiles, or objects like the player, allowing to place different element types in one place.
# for example scews are not just regular objects, they are floor objects, and at the same place there could be another object on top

func _ready() -> void:
	camera_rig.set_camera_space_size(level_size)
	

func _process(_delta):
	if not initialized:
		_initialize_level()
	

func _initialize_level():
	Screw.reset_screws()
	#var actual_screws = 0
	#Screw.screws_amount = 
	for screw in get_tree().get_nodes_in_group("screw"):
		if !screw.is_queued_for_deletion():
			Screw.screws_amount += 1
	print ("screws: " + str(Screw.screws_amount))
	
	
	for screw in get_tree().get_nodes_in_group("screw"):
		if screw.all_screws_removed.is_connected(Callable(self, "_on_level_finished")):
			screw.all_screws_removed.disconnect(Callable(self, "_on_level_finished"))
		else:
			screw.all_screws_removed.connect(Callable(self, "_on_level_finished"))
	initialized = true

func _init():
	level_size = LevelManager.chunks_xz * LevelManager.LEVEL_CHUNK_SIZE
	
	add_to_group("level")
	for x in range(LevelManager.chunks_xz):
		for y in range(LevelManager.chunks_xz):
			for z in range(LevelManager.chunks_y):
				var new_chunk =  self.level_chunk.new(x, y, z)
				new_chunk.chunk_offset.x = x * LevelManager.LEVEL_CHUNK_SIZE
				new_chunk.chunk_offset.y = y * LevelManager.LEVEL_CHUNK_SIZE
				new_chunk.chunk_offset.z = z * LevelManager.LEVEL_CHUNK_SIZE
				new_chunk.level = self
				self.level_chunks[str(x) + "_" + str(y) + "_" + str(z)] = new_chunk
				
			
	for x in range(LevelManager.chunks_xz * LevelManager.LEVEL_CHUNK_SIZE):
		for y in range(LevelManager.chunks_xz * LevelManager.LEVEL_CHUNK_SIZE):
			for z in range(LevelManager.chunks_y * LevelManager.LEVEL_CHUNK_SIZE):
				self.elem_ids[str(x) + "_" + str(y) + "_" + str(z)] = 1
				
	


func level_to_local(queried_position):
	return Vector3(queried_position.x * self.ELEMENT_SIZE, queried_position.z * 0.8 - 0.8, queried_position.y * self.ELEMENT_SIZE)


func local_to_level(queried_position, z_pos):
	#var current_tile_layer = select_box_pos_level.z
	var element_position = Vector3(0,0,0)

	if queried_position.x == self.camera_rig.camera_space_size:
		queried_position.x = self.select_box_space_size
	if queried_position.z == self.camera_rig.camera_space_size:
		queried_position.z = self.select_box_space_size

	var camera_position_x = int(queried_position.x)
	var camera_position_y = int(queried_position.y)
	var camera_position_z = int(queried_position.z)

	@warning_ignore("integer_division")
	element_position.x = (camera_position_x - (camera_position_x % self.ELEMENT_SIZE)) / self.ELEMENT_SIZE
	@warning_ignore("integer_division")
	element_position.y = (camera_position_z - (camera_position_z % self.ELEMENT_SIZE)) / self.ELEMENT_SIZE
	element_position.z = z_pos

	return element_position


func is_pos_out_of_bounds(position):
	var chunk = self.get_chunk_from_element_pos(position)
	if chunk == null:
		return true
	return false

#gets actually the element holder, which is just called element for short
func get_element(element_position, skip_init = false):
	var chunk_pos = element_to_chunk_pos(element_position)
	var chunk = get_level_chunk(chunk_pos)
	
	var local_elem_pos = element_to_chunkelem_pos(element_position)
	var elem = chunk.get_element(local_elem_pos, skip_init)
	return elem


#get level chunk by chunk position (vector), not by the position of an element, 0 is first chunk. 1 second, etc
#contains a number of elements (element holders), which contain
func get_level_chunk(chunk_position):
	var key = str(chunk_position.x) + "_" + str(chunk_position.y) + "_" + str(chunk_position.z)
	if self.level_chunks.has(key):
		return self.level_chunks[key]
	return null


# calculates chunk position from tile position input
func element_to_chunk_pos(element_position):
	var chunk_pos = Vector3(0, 0, 0)
	chunk_pos.x = floorf(element_position.x / LevelManager.LEVEL_CHUNK_SIZE)
	chunk_pos.y = floorf(element_position.y / LevelManager.LEVEL_CHUNK_SIZE)
	chunk_pos.z = floorf(element_position.z / LevelManager.LEVEL_CHUNK_SIZE)
	return chunk_pos


# calculates local tile position inside a chunk
func element_to_chunkelem_pos(element_position):
	var elem_pos = Vector3(0, 0, 0)
	elem_pos.x = fmod(element_position.x, LevelManager.LEVEL_CHUNK_SIZE)
	elem_pos.y = fmod(element_position.y, LevelManager.LEVEL_CHUNK_SIZE)
	elem_pos.z = fmod(element_position.z, LevelManager.LEVEL_CHUNK_SIZE)
	return elem_pos


#get level chunk by tile position
func get_chunk_from_element_pos(tile_position):
	var chunk_pos = element_to_chunk_pos(tile_position)
	return get_level_chunk(chunk_pos)


func get_element2(x, y, z):
	return get_element(Vector3(x, y, z))


func get_dict():
	var elements_dict = {}
	for i in self.elem_ids.keys():
		var elem = get_element_from_string(i)

		if elem != null and elem.has_content():
			elements_dict[i] = elem.get_dict()

	return {
		"metadata" : self.metadata,
		"tiles" : elements_dict
	}

func get_element_from_string(elem_id):
	var split = elem_id.split_floats("_")
	var x = split[0]
	var y = split[1]
	var z = split[2] 
	
	return get_element2(x, y, z)

func wipe_metadata():
	self.metadata.clear()

func _on_level_finished():
	if game != null:
		game.on_level_finished()
#func has_screws() -> bool:
#	return screws_amount > 0
	

