using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using PkmnLib.Static.Utils;

namespace PkmnLib.Static.Species;

/// <summary>
/// The data belonging to a Pokémon with certain characteristics.
/// </summary>
public interface ISpecies : INamedValue
{
    /// <summary>
    /// The national dex identifier of the Pokémon.
    /// </summary>
    ushort Id { get; }

    /// <summary>
    /// The chance between 0.0 and 1.0 that a Pokémon is female. 0.0 means always male, 1.0 means always female.
    /// If less than 0, the Pokémon is genderless.
    /// </summary>
    float GenderRate { get; }

    /// <summary>
    /// How much experience is required for a level.
    /// </summary>
    StringKey GrowthRate { get; }

    /// <summary>
    /// How hard it is to capture a Pokémon. 255 means this will be always caught, 0 means this is
    /// uncatchable.
    /// </summary>
    byte CaptureRate { get; }

    /// <summary>
    /// The base happiness of the Pokémon.
    /// </summary>
    byte BaseHappiness { get; }

    /// <summary>
    /// The forms that belong to this Pokémon.
    /// </summary>
    IReadOnlyDictionary<StringKey, IForm> Forms { get; }

    /// <summary>
    /// The arbitrary flags that can be set on a Pokémon for script use.
    /// </summary>
    ImmutableHashSet<StringKey> Flags { get; }

    /// <summary>
    /// Gets a form by name.
    /// </summary>
    bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form);

    /// <summary>
    /// Gets the form the Pokémon will have by default, if no other form is specified.
    /// </summary>
    IForm GetDefaultForm();

    /// <summary>
    /// Gets a random gender.
    /// </summary>
    Gender GetRandomGender(IRandom rand);

    /// <summary>
    /// Check whether the Pokémon has a specific flag set.
    /// </summary>
    bool HasFlag(StringKey key);

    /// <summary>
    /// The data regarding into which Pokémon this species can evolve, and how.
    /// </summary>
    IReadOnlyList<IEvolution> EvolutionData { get; }

    /// <summary>
    /// The egg groups the Pokémon belongs to.
    /// </summary>
    ICollection<StringKey> EggGroups { get; }
}

/// <inheritdoc />
public class SpeciesImpl : ISpecies
{
    /// <inheritdoc cref="SpeciesImpl" />
    public SpeciesImpl(ushort id, StringKey name, float genderRate, StringKey growthRate, byte captureRate,
        byte baseHappiness, IReadOnlyDictionary<StringKey, IForm> forms, IEnumerable<StringKey> flags,
        IReadOnlyList<IEvolution> evolutionData, IEnumerable<StringKey> eggGroups)
    {
        Id = id;
        Name = name;
        GenderRate = genderRate;
        GrowthRate = growthRate;
        CaptureRate = captureRate;
        BaseHappiness = baseHappiness;
        Forms = forms;
        Flags = flags.ToImmutableHashSet();
        EvolutionData = evolutionData;
        EggGroups = eggGroups.ToImmutableHashSet();
        if (Forms.Count == 0)
            throw new ArgumentException("Species must have at least one form.");
        if (!Forms.ContainsKey("default"))
            throw new ArgumentException("Species must have a default form.");
    }

    /// <inheritdoc />
    public ushort Id { get; }

    /// <inheritdoc />
    public StringKey Name { get; }

    /// <inheritdoc />
    public float GenderRate { get; }

    /// <inheritdoc />
    public StringKey GrowthRate { get; }

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

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

    /// <inheritdoc />
    public IReadOnlyDictionary<StringKey, IForm> Forms { get; }

    /// <inheritdoc />
    public ImmutableHashSet<StringKey> Flags { get; }

    /// <inheritdoc />
    public IReadOnlyList<IEvolution> EvolutionData { get; }

    /// <inheritdoc />
    public ICollection<StringKey> EggGroups { get; }

    /// <inheritdoc />
    public bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form) => Forms.TryGetValue(id, out form);

    /// <inheritdoc />
    public IForm GetDefaultForm() => Forms["default"];

    /// <inheritdoc />
    public Gender GetRandomGender(IRandom rand)
    {
        if (GenderRate < 0.0f)
            return Gender.Genderless;
        var v = rand.GetFloat();
        return v < GenderRate ? Gender.Female : Gender.Male;
    }

    /// <inheritdoc />
    public bool HasFlag(StringKey key) => Flags.Contains(key);
}