using Pcg;

namespace PkmnLib.Static.Utils;

/// <summary>
/// Wrapper interface for getting random numbers.
/// </summary>
public interface IRandom
{
    /// <summary>
    /// Get a random integer between min and max.
    /// </summary>
    /// <param name="min">The minimum value (inclusive).</param>
    /// <param name="max">The maximum value (exclusive).</param>
    /// <returns>A random integer that is greater than or equal to min and less than max.</returns>
    public int GetInt(int min, int max);

    /// <summary>
    /// Get a random integer between 0 and max.
    /// </summary>
    /// <param name="max">The maximum value (exclusive).</param>
    /// <returns>A random integer that is greater than or equal to 0 and less than max.</returns>
    public int GetInt(int max);

    /// <summary>
    /// Get a random integer between 0 and <see cref="int.MaxValue"/>.
    /// </summary>
    /// <returns>A random integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
    public int GetInt();

    /// <summary>
    /// Get a random float that is greater than or equal to 0.0 and less than 1.0.
    /// </summary>
    /// <returns>A random float that is greater than or equal to 0.0 and less than 1.0.</returns>
    public float GetFloat();

    /// <summary>
    /// Get a random float that is greater than or equal to min and less than max.
    /// </summary>
    /// <param name="min">The minimum value (inclusive).</param>
    /// <param name="max">The maximum value (exclusive).</param>
    /// <returns>A random float that is greater than or equal to min and less than max.</returns>
    public float GetFloat(float min, float max);

    /// <summary>
    /// Get a random boolean. 50% chance of being true.
    /// </summary>
    public bool GetBool();

    /// <summary>
    /// Get a random element from the given list.
    /// </summary>
    public T OneOf<T>(IReadOnlyList<T> list);
}

/// <inheritdoc />
public class RandomImpl : IRandom
{
    private readonly PcgRandom _random;

    /// <summary>
    /// Creates a new instance that uses the given <see cref="PcgRandom"/> instance as its source of randomness.
    /// </summary>
    public RandomImpl(PcgRandom random)
    {
        _random = random;
    }

    /// <summary>
    /// Creates a new instance that uses the given seed to create a new <see cref="PcgRandom"/> instance.
    /// </summary>
    public RandomImpl(int seed)
    {
        _random = new PcgRandom(seed);
    }

    /// <summary>
    /// Creates a new instance that uses a new <see cref="PcgRandom"/> instance.
    /// </summary>
    public RandomImpl()
    {
        _random = new PcgRandom();
    }

    /// <inheritdoc />
    public int GetInt(int min, int max) => _random.Next(min, max);

    /// <inheritdoc />
    public int GetInt(int max) => _random.Next(max);

    /// <inheritdoc />
    public int GetInt() => _random.Next();

    /// <inheritdoc />
    public float GetFloat() => (float)_random.NextDouble();

    /// <inheritdoc />
    public float GetFloat(float min, float max) => (float)(_random.NextDouble() * (max - min) + min);

    /// <inheritdoc />
    public bool GetBool() => _random.Next(2) == 1;

    /// <inheritdoc />
    public T OneOf<T>(IReadOnlyList<T> list)
    {
        if (list.Count == 0)
            throw new ArgumentException("List cannot be empty.", nameof(list));
        return list[GetInt(list.Count)];
    }

    /// <summary>
    /// Generates a new random <see cref="Guid"/>.
    /// </summary>
    /// <remarks>
    /// The goal of this method is to create a new unique identifier that can be used for various purposes,
    /// without having to rely on the system's built-in GUID generation. The built-in GUID generation
    /// can be slow (see also: https://github.com/dotnet/runtime/issues/13628)
    /// </remarks>
    public Guid NewGuid()
    {
        var guidBytes = GuidCache.Value.AsSpan();
        _random.NextBytes(guidBytes);
        return new Guid(guidBytes);
    }

    private static readonly ThreadLocal<byte[]> GuidCache = new(() => new byte[16]);
}