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

namespace PkmnLib.Dynamic.Models;

/// <summary>
/// A side in a battle.
/// </summary>
public interface IBattleSide : IScriptSource, IDeepCloneable
{
    /// <summary>
    /// The index of the side on the battle.
    /// </summary>
    byte Index { get; }
    
    /// <summary>
    /// The number of Pokémon that can be on the side.
    /// </summary>
    byte NumberOfPositions { get; }
    
    /// <summary>
    /// A list of Pokémon currently on the battlefield.
    /// </summary>
    IReadOnlyList<IPokemon?> Pokemon { get; }
    
    /// <summary>
    /// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts.
    /// </summary>
    IReadOnlyList<ITurnChoice?> SetChoices { get; }
    
    /// <summary>
    /// Whether every Pokémon on this side has its choices  
    /// </summary>
    bool AllChoicesSet { get; }
    
    /// <summary>
    /// The slots on the side that can still be filled. Once all slots are set to false, this side
    /// has lost the battle.
    /// </summary>
    IReadOnlyList<bool> FillablePositions { get; }
    
    /// <summary>
    /// A reference to the battle this side is in.
    /// </summary>
    IBattle Battle { get; }
    
    /// <summary>
    /// Whether this side has fled.
    /// </summary>
    bool HasFledBattle { get; }
    
    /// <summary>
    /// The volatile scripts that are attached to the side.
    /// </summary>
    IScriptSet VolatileScripts { get; }
    
    /// <summary>
    /// Returns true if there are slots that need to be filled with a new pokemon, that have parties
    /// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are
    /// empty, but can't be filled by any party anymore.
    /// </summary>
    bool AllPositionsFilled();

    /// <summary>
    /// Sets a choice for a Pokémon on this side.
    /// </summary>
    void SetChoice(byte position, ITurnChoice choice);
    
    /// <summary>
    /// Resets all choices on this side.
    /// </summary>
    void ResetChoices();

    /// <summary>
    /// Forcibly removes a Pokémon from the field.
    /// </summary>
    /// <param name="index"></param>
    void ForceClearPokemonFromField(byte index);

    /// <summary>
    /// Switches out a spot on the field for a different Pokémon. If null is passed, the spot is
    /// cleared. Returns the Pokémon that was previously in the spot.
    /// </summary>
    IPokemon? SwapPokemon(byte position, IPokemon? pokemon);

    /// <summary>
    /// Swaps two Pokémon on the side.
    /// </summary>
    void SwapPokemon(byte position1, byte position2);

    /// <summary>
    /// Checks whether a Pokemon is on the field in this side.
    /// </summary>
    bool IsPokemonOnSide(IPokemon pokemon);

    /// <summary>
    /// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore.
    /// If this happens, the slot can not be used again.
    /// </summary>
    void MarkPositionAsUnfillable(byte position);

    /// <summary>
    /// Checks whether a slot is fillable. If it is not, the slot can not be used anymore.
    /// </summary>
    bool IsPositionFillable(byte position);

    /// <summary>
    /// Checks whether the side has been defeated.
    /// </summary>
    bool IsDefeated();
    
    /// <summary>
    /// The number of times this side has attempted to flee.
    /// </summary>
    uint FleeAttempts { get; }
    
    /// <summary>
    /// Registers a flee attempt for this side.
    /// </summary>
    void RegisterFleeAttempt();

    /// <summary>
    /// Mark the side as fled.
    /// </summary>
    void MarkAsFled();

    /// <summary>
    /// Gets a random Pokémon on the given side.
    /// </summary>
    byte GetRandomPosition();
}

/// <inheritdoc cref="IBattleSide"/>
public class BattleSideImpl : ScriptSource, IBattleSide
{
    /// <inheritdoc cref="BattleSideImpl"/>
    public BattleSideImpl(byte index, byte numberOfPositions, IBattle battle)
    {
        Index = index;
        NumberOfPositions = numberOfPositions;
        _pokemon = new IPokemon?[numberOfPositions];
        _setChoices = new ITurnChoice?[numberOfPositions];
        _fillablePositions = new bool[numberOfPositions];
        for (byte i = 0; i < numberOfPositions; i++)
        {
            _fillablePositions[i] = true;
        }
        Battle = battle;
        VolatileScripts = new ScriptSet();
    }
    
    /// <inheritdoc />
    public byte Index { get; }

    /// <inheritdoc />
    public byte NumberOfPositions { get; }

    private readonly IPokemon?[] _pokemon;
    /// <inheritdoc />
    public IReadOnlyList<IPokemon?> Pokemon => _pokemon;

    private readonly ITurnChoice?[] _setChoices;

    /// <inheritdoc />
    public IReadOnlyList<ITurnChoice?> SetChoices => _setChoices;

    /// <inheritdoc />
    public bool AllChoicesSet => _setChoices.All(choice => choice is not null);

    private readonly bool[] _fillablePositions;
    /// <inheritdoc />
    public IReadOnlyList<bool> FillablePositions => _fillablePositions;

    /// <inheritdoc />
    public IBattle Battle { get; }

    /// <inheritdoc />
    public bool HasFledBattle { get; private set; }

    /// <inheritdoc />
    public IScriptSet VolatileScripts { get; }

    /// <inheritdoc />
    public bool AllPositionsFilled()
    {
        for (byte i = 0; i < NumberOfPositions; i++)
        {
            var pokemon = Pokemon[i];
            var isPokemonViable = pokemon is not null && pokemon.IsUsable;
            // If the Pokémon is not valid, but the slot can be filled, return false.
            if (!isPokemonViable && Battle.CanSlotBeFilled(Index, i))
            {
                return false;
            }
        }
        return true;
    }

    /// <inheritdoc />
    public void SetChoice(byte position, ITurnChoice choice)
    {
        _setChoices[position] = choice;
    }

    /// <inheritdoc />
    public void ResetChoices()
    {
        for (byte i = 0; i < NumberOfPositions; i++)
        {
            _setChoices[i] = null;
        }
    }

    /// <param name="index"></param>
    /// <inheritdoc />
    public void ForceClearPokemonFromField(byte index)
    {
        var pokemon = _pokemon[index];
        if (pokemon is not null)
        {
            pokemon.RunScriptHook(script => script.OnRemove());
            pokemon.SetOnBattlefield(false);
        }
        
        _pokemon[index] = null;
    }

    /// <inheritdoc />
    public IPokemon? SwapPokemon(byte position, IPokemon? pokemon)
    {
        var oldPokemon = _pokemon[position];
        if (oldPokemon is not null)
        {
            oldPokemon.RunScriptHook(script => script.OnRemove());
            oldPokemon.SetOnBattlefield(false);
        }
        _pokemon[position] = pokemon;
        if (pokemon is not null)
        {
            pokemon.SetBattleData(Battle, Index);
            pokemon.SetOnBattlefield(true);
            pokemon.SetBattleSidePosition(position);
            foreach (var side in Battle.Sides)
            {
                if (side == this)
                    continue;
                foreach (var opponent in side.Pokemon.WhereNotNull())
                {
                    opponent.MarkOpponentAsSeen(pokemon);
                    pokemon.MarkOpponentAsSeen(opponent);
                }
            }
            Battle.EventHook.Invoke(new SwitchEvent(Index, position, pokemon));
            pokemon.RunScriptHook(script => script.OnSwitchIn(pokemon));
        }
        else
        {
            Battle.EventHook.Invoke(new SwitchEvent(Index, position, null));
        }
        
        return oldPokemon;
    }

    /// <inheritdoc />
    public void SwapPokemon(byte position1, byte position2)
    {
        throw new NotImplementedException();
    }

    /// <inheritdoc />
    public bool IsPokemonOnSide(IPokemon pokemon) => _pokemon.Contains(pokemon);

    /// <inheritdoc />
    public void MarkPositionAsUnfillable(byte position) => _fillablePositions[position] = false;

    /// <inheritdoc />
    public bool IsPositionFillable(byte position) => _fillablePositions[position];

    /// <inheritdoc />
    public bool IsDefeated()
    {
        return _fillablePositions.All(fillable => !fillable);
    }

    /// <inheritdoc />
    public uint FleeAttempts { get; private set; }

    /// <inheritdoc />
    public void RegisterFleeAttempt()
    {
        FleeAttempts++;
    }

    /// <inheritdoc />
    public void MarkAsFled() => HasFledBattle = true;

    /// <inheritdoc />
    public byte GetRandomPosition() => (byte)Battle.Random.GetInt(0, NumberOfPositions);

    /// <inheritdoc />
    public override int ScriptCount => 1 + Battle.ScriptCount;

    /// <inheritdoc />
    public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
    {
        scripts.Add(VolatileScripts);
    }

    /// <inheritdoc />
    public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
    {
        scripts.Add(VolatileScripts);
        Battle.CollectScripts(scripts);
    }
}