@tool
class_name TerrainPropsData
extends RefCounted

## Data structure containing terrain information for props placement.
## Generated by TerrainGenerator after terrain generation.

var resolution: Vector2i = Vector2i(128, 128)
var terrain_size: Vector2 = Vector2(200, 200)
var heights: Array[float] = []
var biome_ids: Array[int] = []
var slope_values: Array[float] = []
var rail_distances: Array[float] = []
var station_distances: Array[float] = []
var mask_values: Array[Array] = []


func get_index(x: int, z: int) -> int:
	return z * resolution.x + x


func get_uv(x: int, z: int) -> Vector2:
	return Vector2(float(x) / float(resolution.x - 1), float(z) / float(resolution.y - 1))


func get_world_position(x: int, z: int) -> Vector3:
	var half_x := terrain_size.x / 2.0
	var half_z := terrain_size.y / 2.0
	var step_x := terrain_size.x / float(resolution.x - 1)
	var step_z := terrain_size.y / float(resolution.y - 1)
	var idx := get_index(x, z)
	return Vector3(
		x * step_x - half_x,
		heights[idx] if idx < heights.size() else 0.0,
		z * step_z - half_z
	)


func get_height(x: int, z: int) -> float:
	var idx := get_index(x, z)
	if idx < 0 or idx >= heights.size():
		return 0.0
	return heights[idx]


func get_biome_id(x: int, z: int) -> int:
	var idx := get_index(x, z)
	if idx < 0 or idx >= biome_ids.size():
		return 0
	return biome_ids[idx]


func get_slope_limit(x: int, z: int) -> float:
	var idx := get_index(x, z)
	if idx < 0 or idx >= slope_values.size():
		return 45.0
	return slope_values[idx]


func get_rail_distance(x: int, z: int) -> float:
	var idx := get_index(x, z)
	if idx < 0 or idx >= rail_distances.size():
		return INF
	return rail_distances[idx]


func get_station_distance(x: int, z: int) -> float:
	var idx := get_index(x, z)
	if idx < 0 or idx >= station_distances.size():
		return INF
	return station_distances[idx]


func get_mask_value(mask_index: int, x: int, z: int) -> float:
	if mask_index < 0 or mask_index >= mask_values.size():
		return 0.0
	var mask_arr: Array = mask_values[mask_index]
	var idx := get_index(x, z)
	if idx < 0 or idx >= mask_arr.size():
		return 0.0
	return mask_arr[idx]


func get_calculated_slope(x: int, z: int) -> float:
	if x <= 0 or x >= resolution.x - 1 or z <= 0 or z >= resolution.y - 1:
		return 0.0
	
	var step_x := terrain_size.x / float(resolution.x - 1)
	var step_z := terrain_size.y / float(resolution.y - 1)
	
	var h := get_height(x, z)
	var h_xm := get_height(x - 1, z)
	var h_xp := get_height(x + 1, z)
	var h_zm := get_height(x, z - 1)
	var h_zp := get_height(x, z + 1)
	
	var dx := (h_xp - h_xm) / (2.0 * step_x)
	var dz := (h_zp - h_zm) / (2.0 * step_z)
	
	return rad_to_deg(atan(sqrt(dx * dx + dz * dz)))


func can_place_prop(x: int, z: int, rules: Dictionary) -> bool:
	var slope := get_calculated_slope(x, z)
	var rail_dist := get_rail_distance(x, z)
	var station_dist := get_station_distance(x, z)
	var biome := get_biome_id(x, z)
	
	if rules.has("max_slope") and slope > rules["max_slope"]:
		return false
	if rules.has("min_rail_distance") and rail_dist < rules["min_rail_distance"]:
		return false
	if rules.has("min_station_distance") and station_dist < rules["min_station_distance"]:
		return false
	if rules.has("allowed_biomes") and not biome in rules["allowed_biomes"]:
		return false
	if rules.has("excluded_biomes") and biome in rules["excluded_biomes"]:
		return false
	
	if rules.has("required_mask"):
		var mask_idx: int = rules["required_mask"]
		var threshold: float = rules.get("mask_threshold", 0.5)
		if get_mask_value(mask_idx, x, z) < threshold:
			return false
	
	if rules.has("excluded_mask"):
		var mask_idx: int = rules["excluded_mask"]
		var threshold: float = rules.get("exclude_threshold", 0.5)
		if get_mask_value(mask_idx, x, z) >= threshold:
			return false
	
	return true


func get_density_multiplier(x: int, z: int, rules: Dictionary) -> float:
	var multiplier := 1.0
	
	if rules.has("slope_density_curve"):
		var slope := get_calculated_slope(x, z)
		var curve: Curve = rules["slope_density_curve"]
		multiplier *= curve.sample(clampf(slope / 90.0, 0.0, 1.0))
	
	if rules.has("rail_distance_curve"):
		var dist := get_rail_distance(x, z)
		var curve: Curve = rules["rail_distance_curve"]
		var max_dist: float = rules.get("rail_distance_max", 100.0)
		multiplier *= curve.sample(clampf(dist / max_dist, 0.0, 1.0))
	
	if rules.has("density_mask"):
		var mask_idx: int = rules["density_mask"]
		multiplier *= get_mask_value(mask_idx, x, z)
	
	return multiplier
