extends Building
class_name Turret

@export_group("Ammunition")
@export var ammo_cap: int = 10 ## The number of shots per "magazine", i.e. reload.
@export var fire_cooldown_time: float = 0.2 ## The time between shots, in seconds.
@export var reload_time: float = 1.0 ## The time taken to reload, in seconds.

@export_group("Bullet Spawning")
@export var bullet_scene: PackedScene = preload("res://objects/bullet.tscn") ## The entity to spawn as a bullet.
@export var fire_position: Vector3 = Vector3(0,1,0) ## The local position to fire from.

@export_group("Ballistics")
@export var shot_velocity: float = 100 ## The velocity that bullets are fired at, in m/s.
@export var lead_shots: bool = true ## Turn on to make the turret aim ahead of moving targets.
@export var compensate_for_gravity: bool = true ## Turn on to make the turret aim above distant targets to arc it's shots.
@export var use_artillery_firing_solution: bool = false ## If compensating for gravity, turn this on to make the turret aim high instead of low.

enum LoadState {UNLOADED, RELOADING, LOADED}

@onready var ammo: int = 0
var loaded: LoadState = LoadState.UNLOADED
var cooldown_timer: float = 0.0
var reload_timer: float = 0.0
var current_target: Enemy = null

func _on_consumer_item_added(_item: Item) -> void:
	pass
	
func _process(delta: float) -> void:
	super(delta)
	if loaded == LoadState.UNLOADED:
		if consumer.take_any_item_from_storage():
			loaded = LoadState.RELOADING
	if loaded == LoadState.RELOADING:
		reload_timer += delta
		if reload_timer >= reload_time:
			loaded = LoadState.LOADED
			ammo = ammo_cap
			reload_timer = 0.0
	if loaded == LoadState.LOADED:
		cooldown_timer += delta
		if cooldown_timer >= fire_cooldown_time:
			cooldown_timer -= fire_cooldown_time
			if current_target == null:
				search_for_enemy()
			if current_target != null:
				fire_at_target()
	
func check_target_status() -> void:
	if current_target.is_queued_for_deletion():
		current_target = null
		
func search_for_enemy() -> void:
	var enemies: Array[Node] = get_tree().get_nodes_in_group("Enemies")
	var closest: float = 9999999999
	for enemy: Enemy in enemies:
		if !enemy.sighted:
			continue
		var distance_sqr = global_position.distance_squared_to(enemy.global_position)
		if distance_sqr <= closest:
			var params: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.new()
			params.collision_mask = 0b00101
			params.from = to_global(fire_position)
			params.to = enemy.global_position
			var result: Dictionary = get_world_3d().direct_space_state.intersect_ray(params)
			#DebugDraw3D.draw_line_hit(params.from, params.to, result["position"] if result.has("position") else Vector3(), result.has("position"), 0.25, Color.YELLOW, Color.BLACK, 1.0)
			if result.has("collider"):
				if !result["collider"] is PhysicsBody3D:
					continue
				var body: PhysicsBody3D = result["collider"]
				if body is Enemy:
					current_target = body
					closest = body.global_position.distance_squared_to(global_position)
	
func fire_at_target() -> void:
	#Engine.time_scale = 0.1
	var bullet: RigidBody3D = bullet_scene.instantiate()
	add_child(bullet)
	var bullet_pos: Vector3 = to_global(fire_position)
	bullet.global_position = bullet_pos
	
	var target_range: float = bullet_pos.distance_to(current_target.global_position)
	var shot_time: float = (target_range / shot_velocity)
	
	var aim_target: Vector3 = current_target.global_position
	if lead_shots:
		aim_target += current_target.linear_velocity * shot_time
		target_range = bullet_pos.distance_to(aim_target)
	#DebugDraw3D.draw_sphere(aim_target, 0.25, Color.RED, shot_time)
	
	var fire_direction: Vector3
	if compensate_for_gravity:
		var elevation: float = find_fire_angle(target_range, (aim_target.y - bullet_pos.y), use_artillery_firing_solution)
		var azimuth: float = Vector3.FORWARD.signed_angle_to((aim_target-bullet_pos).slide(Vector3.UP), Vector3.UP)
		print("Shot params: θ=%d φ=%d" % [rad_to_deg(elevation), rad_to_deg(azimuth)])
		if elevation < -PI or elevation > PI or is_nan(elevation):
			print("wtf?? θ=%d" % rad_to_deg(elevation))
			return
		fire_direction = Vector3.FORWARD
		fire_direction = fire_direction.rotated(Vector3(1,0,0), elevation)
		fire_direction = fire_direction.rotated(Vector3(0,1,0), azimuth)
	else:
		fire_direction = (aim_target - bullet_pos).normalized()
		
	#DebugDraw3D.draw_arrow_ray(bullet_pos, fire_direction, target_range, Color.RED, 0.5, true, shot_time)
	
	bullet.look_at(bullet_pos+fire_direction)
	bullet.linear_velocity = (-bullet.global_basis.z * shot_velocity)
	#bullet.apply_impulse((aim_target - bullet.global_position).normalized()*shot_impulse)
	bullet.angular_velocity = (bullet.global_basis * Vector3(0,0,deg_to_rad(1080)))
	current_target = null
	ammo -= 1
	if ammo <= 0:
		Engine.time_scale = 1.0
		loaded = LoadState.UNLOADED
		
func find_fire_angle(distance: float, height: float, high_solution: bool = true) -> float:
	# theta = arctan( ( (v^2) ± sqrt( v^4 - g*( g*x^2 + 2*y*v^2 ) ) ) / g*x )
	const g: float = 9.8
	var gx2: float = g * pow(distance, 2)
	var twoyv2: float = 2 * height * pow(shot_velocity,2)
	var v2: float = pow(shot_velocity, 2)
	var v4: float = pow(shot_velocity, 4)
	
	var arc_x: float = g * distance
	var arc_y_offs: float = sqrt(v4 - g * (gx2 + twoyv2))
	var arc_y: float = v2 + arc_y_offs * (1 if high_solution else -1)
	
	var theta: float = atan2(arc_y, arc_x)
	return clamp(theta, -PI/2, PI/2)