using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.Models;

/// <summary>
/// The different ways a move can be learned.
/// </summary>
public enum MoveLearnMethod
{
    /// <summary>
    /// We do not know the learn method.
    /// </summary>
    Unknown,

    /// <summary>
    /// The move is learned by leveling up.
    /// </summary>
    LevelUp,

    /// <summary>
    /// The move is learned when the Pokémon is hatched from an egg.
    /// </summary>
    Egg,

    /// <summary>
    /// The move is learned by using a tutor in the game.
    /// </summary>
    Tutor,

    /// <summary>
    /// The move is learned by using a TM or HM.
    /// </summary>
    Machine,

    /// <summary>
    /// The move is learned when the Pokémon changes form.
    /// </summary>
    FormChange,

    /// <summary>
    /// The move is learned by using a move sketch.
    /// </summary>
    Sketch,
}

/// <summary>
/// A learned move is the data attached to a Pokemon for a move it has learned. It has information
/// such as the remaining amount of users, how it has been learned, etc.
/// </summary>
public interface ILearnedMove : IDeepCloneable
{
    /// <summary>
    /// The immutable move information of the move.
    /// </summary>
    IMoveData MoveData { get; }

    /// <summary>
    /// The maximal power points for this move.
    /// </summary>
    byte MaxPp { get; }

    /// <summary>
    /// The current power points for this move.
    /// </summary>
    byte CurrentPp { get; }

    /// <summary>
    /// The way the move has been learned.
    /// </summary>
    MoveLearnMethod LearnMethod { get; }

    /// <summary>
    /// Try and reduce the PP by a certain amount. If the amount is higher than the current uses,
    /// return false. Otherwise, reduce the PP, and return true.
    /// </summary>
    bool TryUse(byte amount = 1);

    /// <summary>
    /// Reduce the remaining PP by a certain amount. If the number of PP is already 0, return false.
    /// </summary>
    bool ReduceUses(byte amount = 1);

    /// <summary>
    /// Set the remaining PP to the max amount of PP.
    /// </summary>
    void RestoreAllUses();

    /// <summary>
    /// Restore the remaining PP by a certain amount. Will prevent it from going above max PP.
    /// </summary>
    void RestoreUses(byte amount);

    /// <summary>
    /// Forcibly set the remaining PP to a certain amount.
    /// </summary>
    /// <param name="uses"></param>
    void SetCurrentPP(byte uses);
}

/// <inheritdoc />
public class LearnedMoveImpl : ILearnedMove
{
    private byte _maxPpModification = 0;

    /// <inheritdoc cref="LearnedMoveImpl" />
    public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod)
    {
        MoveData = moveData;
        LearnMethod = learnMethod;
        CurrentPp = MaxPp;
    }

    /// <inheritdoc cref="LearnedMoveImpl" />
    public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod, byte pp) : this(moveData, learnMethod)
    {
        CurrentPp = pp;
    }

    /// <inheritdoc />
    public IMoveData MoveData { get; }

    /// <inheritdoc />
    public byte MaxPp => (byte)(MoveData.BaseUsages + _maxPpModification);

    /// <inheritdoc />
    public MoveLearnMethod LearnMethod { get; }

    /// <summary>
    /// The available power points for this move.
    /// </summary>
    public byte CurrentPp { get; private set; }

    /// <summary>
    /// Try to use the move. This subtracts the amount of PP from the current PP. If the amount requested is
    /// higher than the current PP, this will return false, and the PP will not be reduced.
    /// </summary>
    public bool TryUse(byte amount = 1)
    {
        if (CurrentPp < amount)
        {
            CurrentPp = 0;
            return false;
        }

        CurrentPp -= amount;
        return true;
    }

    /// <inheritdoc />
    public bool ReduceUses(byte amount = 1)
    {
        if (CurrentPp == 0)
            return false;

        if (CurrentPp >= amount)
            CurrentPp -= amount;
        else
            CurrentPp = 0;

        return true;
    }

    /// <summary>
    /// Restore the PP to the maximum amount of PP.
    /// </summary>
    public void RestoreAllUses() => CurrentPp = MaxPp;

    /// <summary>
    /// Restore the PP by a certain amount. This will prevent the PP from going above the maximum PP.
    /// </summary>
    public void RestoreUses(byte amount) => CurrentPp = (byte)Math.Min(CurrentPp + amount, MaxPp);

    /// <inheritdoc />
    public void SetCurrentPP(byte uses)
    {
        CurrentPp = Math.Min(uses, MaxPp);
    }

    /// <inheritdoc />
    public override string ToString() => MoveData.Name;
}