extends Node3D
class_name Chunk

const CHUNK_SIZE: int = 16

var _blocks: PackedInt32Array = PackedInt32Array()
var _mesh_instance: MeshInstance3D = null
var _static_body: StaticBody3D = null

func _ready() -> void:
	if _blocks.size() == 0:
		_blocks.resize(4096)

func init_blocks(data: PackedInt32Array) -> void:
	_blocks = data

func get_block(x: int, y: int, z: int) -> int:
	if x < 0 or x >= CHUNK_SIZE or y < 0 or y >= CHUNK_SIZE or z < 0 or z >= CHUNK_SIZE:
		return BlockDatabase.BlockType.AIR
	return _blocks[x + y * CHUNK_SIZE + z * 256]

func set_block(x: int, y: int, z: int, id: int) -> void:
	if x < 0 or x >= CHUNK_SIZE or y < 0 or y >= CHUNK_SIZE or z < 0 or z >= CHUNK_SIZE:
		return
	_blocks[x + y * CHUNK_SIZE + z * 256] = id

func generate_mesh() -> void:
	if _mesh_instance != null:
		_mesh_instance.queue_free()
		_mesh_instance = null
	if _static_body != null:
		_static_body.queue_free()
		_static_body = null

	var st: SurfaceTool = SurfaceTool.new()
	st.begin(Mesh.PRIMITIVE_TRIANGLES)

	var has_faces: bool = false

	for x: int in range(CHUNK_SIZE):
		for y: int in range(CHUNK_SIZE):
			for z: int in range(CHUNK_SIZE):
				var block_id: int = get_block(x, y, z)
				if not BlockDatabase.is_solid(block_id):
					continue

				var color: Color = BlockDatabase.get_color(block_id)

				if not _is_opaque_at(x, y + 1, z):
					_add_face_top(st, x, y, z, color)
					has_faces = true
				if not _is_opaque_at(x, y - 1, z):
					_add_face_bottom(st, x, y, z, color)
					has_faces = true
				if not _is_opaque_at(x + 1, y, z):
					_add_face_right(st, x, y, z, color)
					has_faces = true
				if not _is_opaque_at(x - 1, y, z):
					_add_face_left(st, x, y, z, color)
					has_faces = true
				if not _is_opaque_at(x, y, z + 1):
					_add_face_front(st, x, y, z, color)
					has_faces = true
				if not _is_opaque_at(x, y, z - 1):
					_add_face_back(st, x, y, z, color)
					has_faces = true

	if not has_faces:
		return

	st.generate_normals()

	var mat: StandardMaterial3D = StandardMaterial3D.new()
	mat.vertex_color_use_as_albedo = true
	mat.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX
	st.set_material(mat)

	var mesh: ArrayMesh = st.commit()

	_mesh_instance = MeshInstance3D.new()
	_mesh_instance.mesh = mesh
	add_child(_mesh_instance)

	var shape: ConcavePolygonShape3D = ConcavePolygonShape3D.new()
	shape.set_faces(mesh.get_faces())

	var col_shape: CollisionShape3D = CollisionShape3D.new()
	col_shape.shape = shape

	_static_body = StaticBody3D.new()
	_static_body.add_child(col_shape)
	add_child(_static_body)

func _is_opaque_at(x: int, y: int, z: int) -> bool:
	if x < 0 or x >= CHUNK_SIZE or y < 0 or y >= CHUNK_SIZE or z < 0 or z >= CHUNK_SIZE:
		return false
	return BlockDatabase.is_solid(get_block(x, y, z))

func _add_face_top(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy + 1.0, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0))
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy + 1.0, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0))
	st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0))

func _add_face_bottom(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy, fz))
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy, fz))
	st.add_vertex(Vector3(fx, fy, fz))

func _add_face_right(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx + 1.0, fy, fz))
	st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0))
	st.set_color(color)
	st.add_vertex(Vector3(fx + 1.0, fy, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz))

func _add_face_left(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz + 1.0))
	st.add_vertex(Vector3(fx, fy, fz))
	st.add_vertex(Vector3(fx, fy + 1.0, fz))
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz + 1.0))
	st.add_vertex(Vector3(fx, fy + 1.0, fz))
	st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0))

func _add_face_front(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0))
	st.add_vertex(Vector3(fx, fy, fz + 1.0))
	st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0))
	st.set_color(color)
	st.add_vertex(Vector3(fx + 1.0, fy, fz + 1.0))
	st.add_vertex(Vector3(fx, fy + 1.0, fz + 1.0))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz + 1.0))

func _add_face_back(st: SurfaceTool, x: int, y: int, z: int, color: Color) -> void:
	var fx: float = float(x)
	var fy: float = float(y)
	var fz: float = float(z)
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz))
	st.add_vertex(Vector3(fx + 1.0, fy, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz))
	st.set_color(color)
	st.add_vertex(Vector3(fx, fy, fz))
	st.add_vertex(Vector3(fx + 1.0, fy + 1.0, fz))
	st.add_vertex(Vector3(fx, fy + 1.0, fz))
