extends Node
class_name AlienHordeManager

const CALCULATE_TARGET_FREQUENCY = 10
const CALCULATE_DIRECTION_FREQUENCY = 10
const CALCULATE_SCATTER_FREQUENCY = 5
const CALCULATE_STATE_FREQUENCY = 5
const ATTACK_DISTANCE = 10.
const MECHA_ATTACK_DISTANCE = 5.
const MAX_ALIEN_MECHA_AGGRO_DISTANCE = 100
const ALIEN_RANDOM_SCALE = 0.3
const MASTODONT_CHANCE = 0.01
const MASTODONT_SCALE_FACTOR = 4.

@export var hex_map : HexMap
@export var mecha : Mecha

@onready var multimeshes : Array[MultiMeshInstance3D] = [
    %RunningAlienMultiMesh,
    %AttackingAlienMultiMesh
]
@onready var targets : Array = hex_map.get_all_tiles_with_id("House")

var current_frame : int = 0

var alien_id_acc : int = 0

var aliens : Array[Alien] = []

func _physics_process(delta):
    current_frame += 1

    for t in targets:
        if t is HouseHexTile:
            if t.destroyed:
                targets.erase(t)

    # if current_frame % CALCULATE_CLUSTERS_FREQUENCY == 0:
    #     calculate_clusters()

    calculate_target()
    calculate_state()
    update_horde(delta)
    spawn_models(delta)

func spawn_alien(position : Vector3, rotation: float) -> Alien:
    var alien : Alien = Alien.new()
    alien.id = alien_id_acc
    alien.position = position
    alien.rotation.y = rotation
    var is_mastodont = randf() < MASTODONT_CHANCE
    alien.size = randf_range(1. - ALIEN_RANDOM_SCALE, 1. + ALIEN_RANDOM_SCALE) * (MASTODONT_SCALE_FACTOR if is_mastodont else 1.)
    alien.scale = Vector3.ONE * alien.size

    add_child(alien)

    aliens.append(alien)

    alien.died.connect(
        func(): 
            aliens.erase(alien)
    )


    alien_id_acc += 1

    return alien

func calculate_target():
    for alien in aliens:
        if (current_frame + alien.id)%CALCULATE_TARGET_FREQUENCY and alien.state != Alien.State.DEAD :
            if (
                len(targets) == 0
                or mecha.position.distance_to(alien.position) < alien.get_random().randf_range(0., MAX_ALIEN_MECHA_AGGRO_DISTANCE)
            ):
                alien.target = mecha
            else :
                var old_target = alien.target
                alien.target = targets[alien.get_random().randi() % len(targets)]
                if old_target != alien.target:
                    alien.state = Alien.State.RUNNING
            

func calculate_state():
    for alien in aliens:
        if (current_frame + alien.id)%CALCULATE_STATE_FREQUENCY and alien.state != Alien.State.DEAD and alien.target:
            var target_attack_distance = MECHA_ATTACK_DISTANCE if alien.target is Mecha else ATTACK_DISTANCE
            alien.state = (
                Alien.State.RUNNING if alien.target.position.distance_to(alien.position) > target_attack_distance
                else Alien.State.ATTACKING
            )

func update_horde(delta):
    for alien in aliens:
        match alien.state:
            Alien.State.RUNNING:
                alien.move(
                    delta,
                    (current_frame + alien.id)%CALCULATE_DIRECTION_FREQUENCY,
                    (current_frame + alien.id)%CALCULATE_SCATTER_FREQUENCY
                )                
            Alien.State.ATTACKING:
                alien.attack_target(delta)

func alien_to_multimesh_id(alien : Alien) -> int:
    match alien.state:
        Alien.State.RUNNING:
            return 0
        Alien.State.ATTACKING:
            return 1
        _: return -1

func spawn_models(delta):
    
    for m_id in range(len(multimeshes)):
        var multimesh = multimeshes[m_id].multimesh

        multimesh.instance_count = len(aliens)

        for i in range(len(aliens)):
            var alien = aliens[i]
            var is_current = m_id == alien_to_multimesh_id(alien)

            multimesh.set_instance_transform(
                i,
                alien.transform.scaled(Vector3.ONE if is_current else Vector3.ZERO)
            )

            if alien.damage_effect_last > 0:
                alien.damage_effect_last -= delta

            multimesh.set_instance_custom_data(
                i,
                Color(
                    alien.id,
                    alien.damage_effect_last > 0,
                    alien.size,
                    0
                ))
            multimesh.set_instance_color(i ,Color(alien.get_random().randi()%255,alien.get_random().randi()%255,alien.get_random().randi()%255))

func is_alien_alive(alien):
    return alien and alien is Alien and alien.hp > 0
