using System.Buffers;
using System.Security.Cryptography;

namespace SharpChat;

public static class RNG {
    public const string CHARS = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789";

    private static Random NormalRandom { get; } = new();
    private static RandomNumberGenerator SecureRandom { get; } = RandomNumberGenerator.Create();

    public static int Next() {
        return NormalRandom.Next();
    }

    public static int Next(int max) {
        return NormalRandom.Next(max);
    }

    public static int Next(int min, int max) {
        return NormalRandom.Next(min, max);
    }

    public static void NextBytes(byte[] buffer) {
        SecureRandom.GetBytes(buffer);
    }

    public static int SecureNext() {
        return SecureNext(int.MaxValue);
    }

    public static int SecureNext(int max) {
        return SecureNext(0, max);
    }

    public static int SecureNext(int min, int max) {
        --max;
        if(min == max)
            return min;

        uint umax = (uint)max - (uint)min;
        uint num;

        byte[] buffer = ArrayPool<byte>.Shared.Rent(4);
        try {
            SecureRandom.GetBytes(buffer);
            num = BitConverter.ToUInt32(buffer);

            if(umax != uint.MaxValue) {
                ++umax;

                if((umax & (umax - 1)) != 0) {
                    uint limit = uint.MaxValue - (uint.MaxValue & umax) - 1;

                    while(num > limit) {
                        SecureRandom.GetBytes(buffer);
                        num = BitConverter.ToUInt32(buffer);
                    }
                }
            }
        } finally {
            ArrayPool<byte>.Shared.Return(buffer);
        }

        return (int)((num % umax) + min);
    }

    private static string RandomStringInternal(Func<int, int> next, int length) {
        char[] str = new char[length];
        for(int i = 0; i < length; ++i)
            str[i] = CHARS[next(CHARS.Length)];
        return new string(str);
    }

    public static string RandomString(int length) {
        return RandomStringInternal(Next, length);
    }

    public static string SecureRandomString(int length) {
        return RandomStringInternal(SecureNext, length);
    }
}