using PkmnLib.Static.Utils;

namespace PkmnLib.Static;

/// <summary>
/// A growth rate defines how much experience is required per level.
/// </summary>
public interface IGrowthRate : INamedValue
{
    ///<summary>
    /// Calculate the level something with this growth rate would have at a certain experience.
    /// </summary>
    /// <param name="experience">The experience to calculate the level for.</param>
    /// <returns>The level at the given experience.</returns>
    LevelInt CalculateLevel(uint experience);

    ///<summary>
    /// Calculate the experience something with this growth rate would have at a certain level.
    /// </summary>
    /// <param name="level">The level to calculate the experience for.</param>
    /// <returns>The starting experience at the given level.</returns>
    uint CalculateExperience(LevelInt level);
}

/// <summary>
/// An implementation of the growth rate that uses a lookup table for experience.
/// </summary>
public class LookupGrowthRate : IGrowthRate
{
    /// <inheritdoc />
    public StringKey Name { get; }

    private readonly uint[] _experienceTable;

    /// <inheritdoc cref="LookupGrowthRate" />
    public LookupGrowthRate(StringKey name, IEnumerable<uint> experienceTable)
    {
        Name = name;
        _experienceTable = experienceTable.ToArray();
        if (_experienceTable.Length < 1)
        {
            throw new ArgumentException("Experience table must have at least one entry.");
        }

        if (_experienceTable[0] != 0)
        {
            throw new ArgumentException("Experience table must start at 0.");
        }

        if (_experienceTable.Length > LevelInt.MaxValue)
        {
            throw new ArgumentException($"Experience table may have at most {LevelInt.MaxValue} entries.");
        }
    }

    /// <inheritdoc />
    public LevelInt CalculateLevel(uint experience)
    {
        for (LevelInt level = 0; level < _experienceTable.Length; level++)
        {
            if (_experienceTable[level] > experience)
            {
                return level;
            }
        }

        return (LevelInt)_experienceTable.Length;
    }

    /// <inheritdoc />
    public uint CalculateExperience(LevelInt level)
    {
        if (level < 1)
            level = 1;
        return level >= _experienceTable.Length ? _experienceTable[^1] : _experienceTable[level - 1];
    }
}