extends Area3D
class_name Alien

const DEATH_SCENE : PackedScene = preload("res://entities/alien/alien_death_effect/alien_death_effect.tscn") as PackedScene
const DEATH_SCENE_TIME : float = 10.

const COLLISION_HEIGHT = 5
const COLLISION_RADIUS = 1.5
const NEIGHBOR_DETECTION_MARGIN = 0.5

const ATTACK_DISTANCE = 6.

const NEIGHBOR_ROTATION = 0.5
const NEIGHBOR_BUMP_FORCE = 4. * AlienHordeManager.CALCULATE_SCATTER_FREQUENCY
const NEIGHBOR_BLOCKED_SPEED_FACTOR = 0.5
const DIRECTION_DRIFT_RANGE = PI/8
const DAMAGE_EFFECT_DURATION : float = 0.5

const DEFAULT_HP : float = 20.
const DEFAULT_DAMAGE_RATE : float = 1.

signal died

enum State {RUNNING, ATTACKING, DEAD}

@export var size : float = 1.0
@export var speed : float = 10.
@export var turn_speed : float = 20.

@onready var damage_rate : float = size * DEFAULT_DAMAGE_RATE
@onready var hp : float = size * DEFAULT_HP
var state : State = State.RUNNING
var damage_effect_last : float = 0.

var id : int = 0
var target : Node3D

@onready var collision_shape : CollisionShape3D =  generate_collision_shape()

func _ready():
    monitoring = false

func generate_collision_shape() -> CollisionShape3D:
    var new_collision_shape : CollisionShape3D= CollisionShape3D.new()
    var cylinder_shape : CylinderShape3D = CylinderShape3D.new()

    cylinder_shape.height = COLLISION_HEIGHT
    cylinder_shape.radius = COLLISION_RADIUS

    new_collision_shape.shape = cylinder_shape

    new_collision_shape.position = Vector3(0,COLLISION_HEIGHT/2.,0)
    
    add_child(new_collision_shape)

    return new_collision_shape

func move(
    delta: float,
    calculate_direction: bool,
    calculate_scatter : bool 
):
    var target_angle = rotation.y
    var current_speed = speed

    if calculate_direction and target:
        target_angle = calculate_target_angle()

    if calculate_scatter:
        var collision_right = has_neighbor(1)
        var collision_left = has_neighbor(-1)

        if collision_left and not collision_right:
            target_angle += - NEIGHBOR_ROTATION
            position += Vector3.RIGHT.rotated(Vector3.UP, rotation.y) * NEIGHBOR_BUMP_FORCE * delta
        elif collision_right and not collision_left:
            target_angle += NEIGHBOR_ROTATION
            position += Vector3.LEFT.rotated(Vector3.UP, rotation.y) * NEIGHBOR_BUMP_FORCE * delta
        elif collision_left and collision_right:
            current_speed = speed * get_random().randf_range(0., NEIGHBOR_BLOCKED_SPEED_FACTOR)

    rotation.y = lerp_angle(
        rotation.y,
        target_angle,
        min(1, delta*turn_speed)
    )

    position += Vector3.FORWARD.rotated(Vector3.UP, rotation.y) * current_speed * delta

func calculate_target_angle() -> float:

    var target_angle : float = (
        Vector2(global_position.z,global_position.x)
        - Vector2(target.global_position.z,target.global_position.x)
    ).normalized().angle()

    # Random drift in direction
    target_angle += get_random().randf_range(-DIRECTION_DRIFT_RANGE, DIRECTION_DRIFT_RANGE)

    return target_angle



func has_neighbor(direction = 1) -> bool:
    var space_state = get_world_3d().direct_space_state

    var query = PhysicsRayQueryParameters3D.create(
        global_position,
        global_position + Vector3(
            direction * (COLLISION_RADIUS + NEIGHBOR_DETECTION_MARGIN), 0,0
        ).rotated(Vector3.UP, rotation.y)
    )
    query.collide_with_areas = true
    query.exclude.append(get_rid())

    var result = space_state.intersect_ray(query)
    
    return result != {}

func attack_target(delta : float):
    if target and target.has_method("hit"):
        target.hit(delta * damage_rate)

func hit(damage = 1.):
    hp = max(0, hp - damage)
    damage_effect_last = DAMAGE_EFFECT_DURATION
    if hp == 0:
        die()

func die():
    died.emit()
    state = State.DEAD
    collision_shape.disabled = true
    add_child(DEATH_SCENE.instantiate())
    await get_tree().create_timer(DEATH_SCENE_TIME).timeout
    queue_free()
    
func get_random() -> RandomNumberGenerator:
    var random : RandomNumberGenerator = RandomNumberGenerator.new()
    random.seed = id

    return random