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);
        }
    }
}