using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;

namespace PkmnLib.Dynamic.Models;

/// <summary>
/// The ChoiceQueue is used to run choices one by one.
/// </summary>
/// <remarks>
/// It functions internally by holding a list of choices, and one by one setting items that have been returned to null,
/// It holds several helper functions to change the turn order while doing the execution. This is needed, as several
/// moves in Pokémon actively mess with this order.
/// </remarks>
public class BattleChoiceQueue
{
    private readonly ITurnChoice?[] _choices;
    private int _currentIndex;

    /// <inheritdoc cref="BattleChoiceQueue"/>
    public BattleChoiceQueue(ITurnChoice[] choices)
    {
        _choices = choices;
    }
    
    /// <summary>
    /// Dequeues the next turn choice to be executed. This gives back the choice and sets it to null in the queue. It
    /// also increments the internal index.
    /// </summary>
    /// <returns></returns>
    public ITurnChoice? Dequeue()
    {
        if (_currentIndex >= _choices.Length)
            return null;

        var choice = _choices[_currentIndex];
        _choices[_currentIndex] = null;
        _currentIndex++;
        return choice;
    }
    
    /// <summary>
    /// This reads what the next choice to execute will be, without modifying state.
    /// </summary>
    public ITurnChoice? Peek() => _currentIndex >= _choices.Length ? null : _choices[_currentIndex];
    
    /// <summary>
    /// Checks if there are any more choices to execute.
    /// </summary>
    public bool HasNext() => _currentIndex < _choices.Length;


    /// <summary>
    /// This resorts the yet to be executed choices. This can be useful for dealing with situations
    /// such as Pokémon changing forms just after the very start of a turn, when turn order has
    /// technically already been decided.
    /// </summary>
    public void Resort()
    {
        var length = _choices.Length;
        var currentIndex = _currentIndex;

        for (var i = currentIndex; i < length; i++)
        {
            var choice = _choices[i];
            if (choice == null)
                continue;
            // Ensure that the speed is up to date
            var speed = choice.User.BoostedStats.Speed;
            choice.User.RunScriptHook(script => script.ChangeSpeed(choice, ref speed));
            choice.Speed = speed;
        }
        
        // We only sort the choices that are left
        Array.Sort(_choices, currentIndex, length - currentIndex, TurnChoiceComparer.Instance!);
    }

    /// <summary>
    /// This moves the choice of a specific Pokémon up to the next choice to be executed.
    /// </summary>
    public bool MovePokemonChoiceNext(IPokemon pokemon)
    {
        var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
        if (index == -1)
            return false;
        if (index == _currentIndex)
            return true;
        var choice = _choices[index];
        _choices[index] = null;
        // Push all choices back
        for (var i = index; i > _currentIndex; i--)
            _choices[i] = _choices[i - 1];
        // And insert the choice at the front
        _choices[_currentIndex] = choice;
        return true;
    }
    
    internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices;
}