extends SceneTree

## Asset-free formula, dependency and timing regression for ADT M2 transforms.

const RESOLVER_SCRIPT := preload("res://src/render/m2/m2_placement_transform_resolver.gd")
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
const GROUPER_PATH := "res://src/render/m2/m2_placement_grouper.gd"
const WATERFALL_WORLD_YAW_OFFSET := PI * 0.5
const CLIFFROCK_WORLD_YAW_OFFSET := PI


func _initialize() -> void:
	var failures: Array[String] = []
	_verify_regular_basis(failures)
	_verify_cliffrock_basis(failures)
	_verify_new_waterfall_basis(failures)
	_verify_tall_waterfall_basis_and_offset(failures)
	_verify_path_normalization_and_scale_clamp(failures)
	_verify_loader_boundary(failures)
	var elapsed_milliseconds := _verify_bounded_timing(failures)
	if not failures.is_empty():
		for failure in failures:
			push_error("M2_PLACEMENT_TRANSFORM_RESOLVER: %s" % failure)
		quit(1)
		return
	print(
		"M2_PLACEMENT_TRANSFORM_RESOLVER PASS cases=9 iterations=10000 elapsed_ms=%.3f"
		% elapsed_milliseconds
	)
	quit(0)


func _verify_regular_basis(failures: Array[String]) -> void:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var rotation := Vector3(0.2, -0.7, 1.1)
	_expect_basis_near(
		resolver.call("resolve_basis", rotation, "World/Generic/Tree.m2"),
		Basis.from_euler(rotation),
		"regular basis",
		failures
	)
	_expect_vector_near(
		resolver.call("resolve_origin_offset", rotation, "World/Generic/Tree.m2", 3.0),
		Vector3.ZERO,
		"regular offset",
		failures
	)


func _verify_cliffrock_basis(failures: Array[String]) -> void:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var rotation := Vector3(0.3, 0.4, -0.2)
	var expected_basis := (
		Basis(Vector3.UP, rotation.y + CLIFFROCK_WORLD_YAW_OFFSET)
		* Basis(Vector3.BACK, rotation.x)
		* Basis(Vector3.RIGHT, -rotation.z)
	)
	_expect_basis_near(
		resolver.call("resolve_basis", rotation, "WORLD\\AZEROTH\\ElwynnCliffRock01.MDX"),
		expected_basis,
		"cliffrock basis",
		failures
	)


func _verify_new_waterfall_basis(failures: Array[String]) -> void:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var rotation := Vector3(-0.1, 0.8, 0.2)
	var fall_axis := Vector3(-0.138742, 0.990329, 0.0).normalized()
	var expected_basis := (
		Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET)
		* Basis.from_euler(rotation)
		* Basis(fall_axis, PI * 0.5)
	)
	_expect_basis_near(
		resolver.call("resolve_basis", rotation, "world/x/NewWaterfall.m2"),
		expected_basis,
		"new waterfall basis",
		failures
	)
	_expect_vector_near(
		resolver.call("resolve_origin_offset", rotation, "world/x/NewWaterfall.m2", 1.0),
		Vector3.ZERO,
		"new waterfall offset",
		failures
	)


func _verify_tall_waterfall_basis_and_offset(failures: Array[String]) -> void:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var rotation := Vector3(0.05, -0.2, 0.15)
	var path := "world/x/ElwynnTallWaterfall01.m2"
	var fall_axis := Vector3(-0.068962, 0.997619, 0.0).normalized()
	var placement_basis := (
		Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET) * Basis.from_euler(rotation)
	)
	var expected_basis := placement_basis * Basis(fall_axis, -PI * 0.5)
	var anchor := Vector3(-2.667799, 89.62273, 0.00129)
	var expected_offset := (placement_basis * anchor - expected_basis * anchor) * 1.5
	_expect_basis_near(
		resolver.call("resolve_basis", rotation, path),
		expected_basis,
		"tall waterfall basis",
		failures
	)
	_expect_vector_near(
		resolver.call("resolve_origin_offset", rotation, path, 1.5),
		expected_offset,
		"tall waterfall offset",
		failures
	)


func _verify_path_normalization_and_scale_clamp(failures: Array[String]) -> void:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var path := "WORLD\\X\\ELWYNNTALLWATERFALL01.MDX"
	var minimum_offset: Vector3 = resolver.call("resolve_origin_offset", Vector3.ZERO, path, 0.0001)
	_expect_vector_near(
		resolver.call("resolve_origin_offset", Vector3.ZERO, path, -9.0),
		minimum_offset,
		"negative scale clamp",
		failures
	)
	_expect_basis_near(
		resolver.call("resolve_basis", Vector3.ZERO, "ElwynnCliffRock02.m2"),
		Basis(Vector3.UP, PI),
		"second cliffrock",
		failures
	)


func _verify_loader_boundary(failures: Array[String]) -> void:
	var source := _read_text(LOADER_PATH, failures)
	var grouper_source := _read_text(GROUPER_PATH, failures)
	_expect_true(
		source.contains("M2_PLACEMENT_TRANSFORM_RESOLVER_SCRIPT.new()"),
		"loader composes resolver",
		failures
	)
	_expect_equal(
		source.count("_m2_placement_transform_resolver.resolve_basis("),
		2,
		"two direct loader basis adapters",
		failures
	)
	_expect_equal(
		source.count("_m2_placement_transform_resolver.resolve_origin_offset("),
		2,
		"two direct loader offset adapters",
		failures
	)
	_expect_equal(
		grouper_source.count("placement_transform_resolver.resolve_basis("),
		1,
		"one grouped basis adapter",
		failures
	)
	_expect_equal(
		grouper_source.count("placement_transform_resolver.resolve_origin_offset("),
		1,
		"one grouped offset adapter",
		failures
	)
	_expect_true(
		not source.contains("func _adt_m2_placement_basis("),
		"loader omits basis formula",
		failures
	)
	_expect_true(
		not source.contains("func _waterfall_sheet_twist("),
		"loader omits waterfall formula",
		failures
	)


func _verify_bounded_timing(failures: Array[String]) -> float:
	var resolver: RefCounted = RESOLVER_SCRIPT.new()
	var rotation := Vector3(0.1, 0.2, 0.3)
	var started_microseconds := Time.get_ticks_usec()
	for iteration in range(10000):
		var path := (
			"world/x/ElwynnTallWaterfall01.m2"
			if iteration % 4 == 0
			else "world/generic/Tree.m2"
		)
		resolver.call("resolve_basis", rotation, path)
		resolver.call("resolve_origin_offset", rotation, path, 1.0)
	var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
	_expect_true(elapsed_milliseconds < 1000.0, "10000 transform pairs bounded", failures)
	return elapsed_milliseconds


func _read_text(path: String, failures: Array[String]) -> String:
	var file := FileAccess.open(path, FileAccess.READ)
	if file == null:
		failures.append("cannot open %s" % path)
		return ""
	return file.get_as_text()


func _expect_basis_near(
		actual_basis: Basis,
		expected_basis: Basis,
		label: String,
		failures: Array[String]
) -> void:
	if not actual_basis.is_equal_approx(expected_basis):
		failures.append("%s basis mismatch" % label)


func _expect_vector_near(
		actual_value: Vector3,
		expected_value: Vector3,
		label: String,
		failures: Array[String]
) -> void:
	if not actual_value.is_equal_approx(expected_value):
		failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])


func _expect_equal(
		actual_value: int,
		expected_value: int,
		label: String,
		failures: Array[String]
) -> void:
	if actual_value != expected_value:
		failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])


func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
	if not actual_value:
		failures.append("%s expected true" % label)
