@tool
extends Node3D


@export var clear_chunks: bool
@export var generate_chunks: bool
@export var chunk_size: int
@export var world_size: Vector3i

var chunks = {}

@export var world_seed: int
@export var noise_frequency: float
@export var noise_threshold: float
@export var cave_noise_frequency: float
@export var cave_noise_threshold: float
var noise
var cave_noise

var chunks_tagged_for_regen = []

var chunk_prototype = preload("res://chunk.tscn")

func _ready() -> void:
	generate_chunks = true
	
func _process(_delta: float) -> void:
	if clear_chunks:
		clear_chunks = false
		clearChunks()
	
	if generate_chunks:
		noise = FastNoiseLite.new()
		noise.frequency = noise_frequency
		noise.seed = world_seed
		cave_noise = FastNoiseLite.new()
		cave_noise.frequency = cave_noise_frequency
		cave_noise.seed = world_seed
		generateChunks()
		generate_chunks = false
		
	for chunk in chunks_tagged_for_regen:
		chunk.generate_chunk()
	
	chunks_tagged_for_regen.clear()
		
func clearChunks():
	print("clearing chunk...")
	var children = get_children()
	for child in children:
		child.free()

func generateChunks():
	for x in range(world_size.x):
		for y in range(world_size.y):
			for z in range(world_size.z):
				generateChunk(Vector3i(x * chunk_size, y * chunk_size, z * chunk_size))
				await Engine.get_main_loop().process_frame

func generateChunk(pos: Vector3i):
	var chunk = chunk_prototype.instantiate()
	chunk.position = pos
	add_child(chunk)
	chunks[pos] = chunk
	chunk.init_blocks(chunk_size, pos)
	chunk.generate_chunk()

func SetBlockAtPosition(pos: Vector3i, block_type: Block.BlockType):
	var chunk_pos = worldPosToChunkPos(pos)
	var local_pos = pos - chunk_pos
	if chunks.has(chunk_pos):
		chunks[chunk_pos].SetBlockAtPosition(local_pos, block_type)
		chunks_tagged_for_regen.append(chunks[chunk_pos])
		var adjacent_chunks_masks = getAdjacentChunksFromBlockPos(local_pos)
		for adjacent_chunk_mask in adjacent_chunks_masks:
			var adjacent_chunk_pos = chunk_pos + adjacent_chunk_mask * chunk_size
			if chunks.has(adjacent_chunk_pos):
				chunks_tagged_for_regen.append(chunks[adjacent_chunk_pos])

func getAdjacentChunksFromBlockPos(pos: Vector3i):
	var chunk_masks = []
	#adjacent to chunk on left
	if pos.x == 0:
		chunk_masks.append(Vector3i(-1, 0, 0))
	# chunk below
	if pos.y == 0:
		chunk_masks.append(Vector3i(0, -1, 0))
	# chunk behind
	if pos.z == 0:
		chunk_masks.append(Vector3i(0, 0, -1))
	# chunk on the right
	if pos.x == chunk_size -1:
		chunk_masks.append(Vector3i(1, 0, 0))
	# chunk above
	if pos.y == chunk_size -1:
		chunk_masks.append(Vector3i(0, 1, 0))
	# chunk on the right
	if pos.z == chunk_size -1:
		chunk_masks.append(Vector3i(0, 0, 1))
	
	return chunk_masks

func worldPosToChunkPos(pos: Vector3i):
	return (pos / chunk_size) * chunk_size
	

func GetBlock(pos: Vector3i, from_noise = false):
	var chunk_pos = worldPosToChunkPos(pos)
	var local_pos = pos - chunk_pos
	
	# there is air at edges of world
	if pos.x < 0 or pos.y < 0 or pos.z < 0 or pos.x >= world_size.x * chunk_size or pos.y >= world_size.y * chunk_size or pos.z >= world_size.z * chunk_size:
		return Block.BlockType.Air
		
	if not from_noise:
		if chunks.has(chunk_pos):
			return chunks[chunk_pos].GetBlockAtPosition(local_pos)
			#
	if ((noise.get_noise_2d(pos.x, pos.z) + 1) * chunk_size > pos.y + noise_threshold * 10) and (cave_noise.get_noise_3d(pos.x, pos.y, pos.z) > cave_noise_threshold):
		return Block.BlockType.Dirt
	else:
		return Block.BlockType.Air
