using System.Collections;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.Models;

/// <summary>
/// A collection of Pokemon.
/// </summary>
public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable
{
    /// <summary>
    /// Event that is triggered when a Pokemon is swapped into the party.
    /// </summary>
    event EventHandler<(IPokemon?, int index)>? OnSwapInto;

    /// <summary>
    /// Event that is triggered when two Pokemon are swapped in the party.
    /// </summary>
    event EventHandler<(int index1, int index2)>? OnSwap;

    /// <summary>
    /// Sets the Pokemon at an index to a Pokemon, returning the old Pokemon.
    /// </summary>
    IPokemon? SwapInto(IPokemon? pokemon, int index);

    /// <summary>
    /// Swaps two Pokemon in the party around.
    /// </summary>
    void Swap(int index1, int index2);

    /// <summary>
    /// Whether the party has any Pokemon that could be used in battle.
    /// </summary>
    /// <remarks>
    /// This will return false if all Pokemon are fainted, or eggs, etc.
    /// </remarks>
    bool HasUsablePokemon();

    /// <summary>
    /// Packs the party so that all Pokémon are at the front, and the empty slots are at the back.
    /// </summary>
    void Pack();
}

/// <inheritdoc />
public class PokemonPartyImpl : IPokemonParty
{
    private readonly IPokemon?[] _pokemon;

    /// <inheritdoc cref="PokemonPartyImpl" />
    public PokemonPartyImpl(int size)
    {
        _pokemon = new IPokemon[size];
    }

    /// <inheritdoc />
    public event EventHandler<(IPokemon?, int index)>? OnSwapInto;

    /// <inheritdoc />
    public event EventHandler<(int index1, int index2)>? OnSwap;

    /// <summary>
    /// Sets the Pokemon at an index to a Pokemon, returning the old Pokemon.
    /// </summary>
    public IPokemon? SwapInto(IPokemon? pokemon, int index)
    {
        var old = _pokemon[index];
        _pokemon[index] = pokemon;
        OnSwapInto?.Invoke(this, (pokemon, index));
        return old;
    }

    /// <summary>
    /// Swaps two Pokemon in the party around.
    /// </summary>
    public void Swap(int index1, int index2)
    {
        (_pokemon[index1], _pokemon[index2]) = (_pokemon[index2], _pokemon[index1]);
        var originalIndex1Value = _pokemon[index1];
        var originalIndex2Value = _pokemon[index2];
        Pack();
        var newIndex1 = Array.IndexOf(_pokemon, originalIndex1Value);
        var newIndex2 = Array.IndexOf(_pokemon, originalIndex2Value);
        OnSwap?.Invoke(this, (newIndex1, newIndex2));
    }

    private void SwapWithoutPack(int index1, int index2)
    {
        (_pokemon[index1], _pokemon[index2]) = (_pokemon[index2], _pokemon[index1]);
        OnSwap?.Invoke(this, (index1, index2));
    }

    /// <inheritdoc />
    public bool HasUsablePokemon() => _pokemon.Any(p => p is { IsUsable: true });

    /// <inheritdoc />
    public IEnumerator<IPokemon?> GetEnumerator() => ((IEnumerable<IPokemon?>)_pokemon).GetEnumerator();

    /// <inheritdoc />
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    /// <inheritdoc />
    public int Count => _pokemon.Length;

    /// <inheritdoc />
    public IPokemon? this[int index] => _pokemon[index];

    /// <inheritdoc />
    public void Pack()
    {
        // Pack the party so that all Pokémon are at the front.
        for (var i = 0; i < _pokemon.Length; i++)
        {
            if (_pokemon[i] != null)
                continue;
            for (var j = i + 1; j < _pokemon.Length; j++)
            {
                if (_pokemon[j] == null)
                    continue;
                SwapWithoutPack(i, j);
                break;
            }
        }
    }
}