using System.Collections.Generic;
using Godot;
using Godot.Collections;

public partial class SurvivalZombieSpawner : Node
{
    // Zombie pools
    // Points from pools go in that order: support -> tank -> horde -> bank
    // Horde pool is allowed to shrink during runtime
    [Export] private Array<string> supportPool;
    [Export] private Array<string> tankPool;
    [Export] private Array<string> hordePool;

    private List<ZombieResource> cachedSupportPool;
    private List<ZombieResource> cachedTankPool;
    private List<ZombieResource> cachedHordePool;

    private float minSupportPoints;
    private float minTankPoints;
    private float minHordePoints;

    [Export] private float points = 0;
    [Export] private float huge_wave_points;
    [Export] private Curve velocity_curve;
    private float velocity = 0;
    [Export] private double time = 0.0;
    private float fin_a;
    private RandomNumberGenerator rng = new();


    public override void _Ready()
    {
        rng.Randomize();

        cachedSupportPool = LoadFromRegistry(supportPool);
        cachedTankPool = LoadFromRegistry(tankPool);
        cachedHordePool = LoadFromRegistry(hordePool);

        cachedTankPool.Sort((x, y) =>
        {
            return (int)(x.Cost - y.Cost);
        });
        cachedHordePool.Sort((x, y) =>
        {
            return (int)(x.Cost - y.Cost);
        });

        minSupportPoints = cachedSupportPool[0].Cost;
        minTankPoints = cachedTankPool[0].Cost;
        minHordePoints = cachedHordePool[0].Cost;

        fin_a = (velocity_curve.Sample(velocity_curve.MaxDomain) - velocity_curve.Sample(velocity_curve.MaxDomain - 0.001f)) / 0.001f;
    }

    public override void _Process(double delta)
    {
        points += velocity * (float)delta;

        if (time > velocity_curve.MaxDomain)
        {
            velocity += fin_a * (float)delta;            
        }
        else
        {
            velocity = velocity_curve.Sample((float)time);
        }
        time += delta;
    }

    public void SummonWave()
    {
        bool big_wave = false;
        if ((int)time % 300 == 0)
        {
            points += velocity/velocity_curve.Sample((float)time)*huge_wave_points;
            big_wave = true;
        }

        float support_points = points * 0.2f;
        float tank_points = points * 0.5f;
        float horde_points = points * 0.3f;
        points = 0;

        List<ZombieResource> wave = [];
        var remaining = SummonTank(tank_points, wave);
        support_points += remaining * 0.8f;
        horde_points += remaining * 0.2f; 
        horde_points += SummonSupport(support_points, wave);
        points += SummonHorde(horde_points, wave, big_wave);

        wave.Sort((x, y) => { return rng.RandiRange(-1, 1); });

        foreach (var zom in wave)
        {
            ZombieSequencer.Instance.Add(zom.internal_id);
        }

        big_wave = false;
    }

    private float SummonSupport(float given_points, List<ZombieResource> wave)
    {
        if (cachedSupportPool.Count == 0)
        {
            return given_points;
        }
        while (given_points >= minSupportPoints)
        {
            var chosen_zombie = cachedSupportPool[rng.RandiRange(0, cachedSupportPool.Count - 1)];
            if (given_points - chosen_zombie.Cost >= 0)
            {
                wave.Add(chosen_zombie);
                given_points -= chosen_zombie.Cost;
            }
        }
        return given_points;
    }
    private float SummonTank(float given_points, List<ZombieResource> wave)
    {
        if (cachedTankPool.Count == 0)
        {
            return given_points;
        }
        int zombieIndex = cachedTankPool.Count - 1;   
        while (given_points >= minSupportPoints && zombieIndex > -1)
        {
            if (cachedTankPool[zombieIndex].Cost > given_points)
            {
                zombieIndex--;
                continue;
            }
            var chosen_zombie = cachedTankPool[zombieIndex];
            wave.Add(chosen_zombie);
            given_points -= chosen_zombie.Cost;
        }
        return given_points;
    }
    private float SummonHorde(float given_points, List<ZombieResource> wave, bool is_big)
    {
        if (cachedHordePool.Count == 0)
        {
            return given_points;
        }
        while (is_big == false && cachedHordePool.Count > 1 && cachedHordePool[1].Cost * 15 <= given_points)
        {
            cachedHordePool.RemoveAt(0);
            minHordePoints = cachedHordePool[0].Cost;
        }
        while (given_points >= minHordePoints)
        {
            var chosen_zombie = cachedHordePool[rng.RandiRange(0, cachedHordePool.Count - 1)];
            if (given_points - chosen_zombie.Cost >= 0)
            {
                wave.Add(chosen_zombie);
                given_points -= chosen_zombie.Cost;
            }
        }
        return given_points;
    }

    private List<ZombieResource> LoadFromRegistry(Array<string> pool)
    {
        List<ZombieResource> list = [];
        foreach (var res in pool)
        {
            list.Add(GameRegistry.GetZombieByName(res));
        }
        return list;
    }
}
