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

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 : IDeepCloneable
{
    private readonly ITurnChoice?[] _choices;
    private int _currentIndex;

    /// <summary>
    /// Returns the last choice that was executed.
    /// </summary>
    public ITurnChoice? LastRanChoice { get; private set; }

    /// <inheritdoc cref="BattleChoiceQueue"/>
    public BattleChoiceQueue(ITurnChoice[] choices)
    {
        Array.Sort(choices, TurnChoiceComparer.Instance);
        _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++;
        LastRanChoice = choice;
        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<IScriptChangeSpeed>(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>
    /// <returns>
    /// Returns true if the Pokémon was found and moved, false otherwise.
    /// </returns>
    public bool MovePokemonChoiceNext(IPokemon pokemon)
    {
        var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
        if (index == -1)
            return false;
        if (index == _currentIndex)
            return false; // Already next
        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;
    }

    /// <summary>
    /// This moves the choice of a specific Pokémon to the end of the queue, making it the last choice to be executed.
    /// </summary>
    /// <returns>
    /// Returns true if the Pokémon was found and moved, false otherwise.
    /// </returns>
    public bool MovePokemonChoiceLast(IPokemon pokemon)
    {
        var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
        if (index == -1)
            return false;
        var choice = _choices[index];
        _choices[index] = null;
        // Put all choices after the index of the choice forward
        for (var i = index; i < _choices.Length - 1; i++)
            _choices[i] = _choices[i + 1];
        // And insert the choice at the end
        _choices[^1] = choice;
        return true;
    }

    internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices;

    /// <summary>
    /// This returns the first choice that matches the predicate, or null if none was found.
    /// </summary>
    public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) =>
        _choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate);

    /// <summary>
    /// This returns all upcoming choices that match the predicate.
    /// </summary>
    public IEnumerable<ITurnChoice> Where(Func<ITurnChoice, bool> predicate) =>
        _choices.Skip(_currentIndex).WhereNotNull().Where(predicate);

    /// <summary>
    /// Removes a choice from the queue.
    /// </summary>
    public void Remove(ITurnChoice choice)
    {
        var index = Array.FindIndex(_choices, _currentIndex, x => Equals(x, choice));
        if (index == -1)
            return;
        _choices[index] = null;
        for (var i = index; i > _currentIndex; i--)
            _choices[i] = _choices[i - 1];
    }
}