# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test, SparseArrays
using Test: guardseed
using Statistics: mean

const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test")
isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl"))
using .Main.OffsetArrays

using Random
using Random.DSFMT

using Random: Sampler, SamplerRangeFast, SamplerRangeInt, SamplerRangeNDL, MT_CACHE_F, MT_CACHE_I

import Future # randjump

@testset "Issue #6573" begin
    Random.seed!(0)
    rand()
    x = rand(384)
    @test findall(x .== rand()) == []
end

@test rand() != rand()
@test 0.0 <= rand() < 1.0
@test rand(UInt32) >= 0
@test -10 <= rand(-10:-5) <= -5
@test -10 <= rand(-10:5) <= 5
@test minimum([rand(Int32(1):Int32(7^7)) for i = 1:100000]) > 0
@test typeof(rand(false:true)) === Bool
@test typeof(rand(Char)) === Char
@test length(randn(4, 5)) == 20
@test length(randn(ComplexF64, 4, 5)) == 20
@test length(bitrand(4, 5)) == 20

@test rand(MersenneTwister(0)) == 0.8236475079774124
@test rand(MersenneTwister(42)) == 0.5331830160438613
# Try a seed larger than 2^32
@test rand(MersenneTwister(5294967296)) == 0.3498809918210497

# Test array filling, Issues #7643, #8360
@test rand(MersenneTwister(0), 1) == [0.8236475079774124]
let A = zeros(2, 2)
    rand!(MersenneTwister(0), A)
    @test A == [0.8236475079774124  0.16456579813368521;
                0.9103565379264364  0.17732884646626457]
end
let A = zeros(2, 2)
    @test_throws ArgumentError rand!(MersenneTwister(0), A, 5)
    @test rand(MersenneTwister(0), Int64, 1) == [-3433174948434291912]
end
let A = zeros(Int64, 2, 2)
    rand!(MersenneTwister(0), A)
    @test A == [858542123778948672  5715075217119798169;
                8690327730555225005 8435109092665372532]
end

# rand from AbstractArray
let mt = MersenneTwister()
    @test rand(mt, 0:3:1000) in 0:3:1000
    @test issubset(rand!(mt, Vector{Int}(undef, 100), 0:3:1000), 0:3:1000)
    coll = Any[2, UInt128(128), big(619), "string"]
    @test rand(mt, coll) in coll
    @test issubset(rand(mt, coll, 2, 3), coll)

    # check API with default RNG:
    rand(0:3:1000)
    rand!(Vector{Int}(undef, 100), 0:3:1000)
    rand(coll)
    rand(coll, 2, 3)
end

# randn
@test randn(MersenneTwister(42)) == -0.5560268761463861
let A = zeros(2, 2)
    randn!(MersenneTwister(42), A)
    @test A == [-0.5560268761463861  0.027155338009193845;
                -0.444383357109696  -0.29948409035891055]
end

let B = zeros(ComplexF64, 2)
    randn!(MersenneTwister(42), B)
    @test B == [ComplexF64(-0.5560268761463861,-0.444383357109696),
                ComplexF64(0.027155338009193845,-0.29948409035891055)] * 0.7071067811865475244008
end

for T in (Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128, BigInt,
          Float16, Float32, Float64, Rational{Int})
    r = rand(convert(T, 97):convert(T, 122))
    @test typeof(r) == T
    @test 97 <= r <= 122
    r = rand(convert(T, 97):convert(T,2):convert(T, 122),2)[1]
    @test typeof(r) == T
    @test 97 <= r <= 122
    @test mod(r,2)==1

    if T<:Integer && !(T===BigInt)
        x = rand(typemin(T):typemax(T))
        @test isa(x,T)
        @test typemin(T) <= x <= typemax(T)
    end
end

@test !any([(Random.maxmultiple(i)+i) > 0xFF for i in 0x00:0xFF])
@test all([(Random.maxmultiple(i)+1) % i for i in 0x01:0xFF] .== 0)
@test all([(Random.maxmultiple(i)+1+i) > 0xFF for i in 0x00:0xFF])
@test length(0x00:0xFF)== Random.maxmultiple(0x0)+1


if sizeof(Int32) < sizeof(Int)
    local r = rand(Int32(-1):typemax(Int32))
    @test typeof(r) == Int32
    @test -1 <= r <= typemax(Int32)
    for U = (Int64, UInt64)
        @test all(div(one(UInt128) << 52, k)*k - 1 == SamplerRangeInt(map(U, 1:k)).u
                  for k in 13 .+ Int64(2).^(32:51))
        @test all(div(one(UInt128) << 64, k)*k - 1 == SamplerRangeInt(map(U, 1:k)).u
                  for k in 13 .+ Int64(2).^(52:62))
    end
end

# BigInt specific
for T in [UInt32, UInt64, UInt128, Int128]
    local r, s
    s = big(typemax(T)-1000) : big(typemax(T)) + 10000
    # s is a 11001-length array
    @test rand(s) isa BigInt
    @test sum(rand(s, 1000) .== rand(s, 1000)) <= 20
    @test big(typemax(T)-1000) <= rand(s) <= big(typemax(T)) + 10000
    r = rand(s, 1, 2)
    @test size(r) == (1, 2)
    @test typeof(r) == Matrix{BigInt}
    guardseed() do
        Random.seed!(0)
        r = rand(s)
        Random.seed!(0)
        @test rand(s) == r
    end
end

# Test ziggurat tables
ziggurat_table_size = 256
nmantissa           = Int64(2)^51 # one bit for the sign
ziggurat_nor_r      = parse(BigFloat,"3.65415288536100879635194725185604664812733315920964488827246397029393565706474")
erfc_zigg_root2     = parse(BigFloat,"2.580324876539008898343885504487203185398584536409033046076029509351995983934371e-04")
nor_section_area    = ziggurat_nor_r*exp(-ziggurat_nor_r^2/2) + erfc_zigg_root2*sqrt(big(π)/2)
emantissa           = Int64(2)^52
ziggurat_exp_r      = parse(BigFloat,"7.69711747013104971404462804811408952334296818528283253278834867283241051210533")
exp_section_area    = (ziggurat_exp_r + 1)*exp(-ziggurat_exp_r)

ki = Vector{UInt64}(undef, ziggurat_table_size)
wi = Vector{Float64}(undef, ziggurat_table_size)
fi = Vector{Float64}(undef, ziggurat_table_size)
# Tables for exponential variates
ke = Vector{UInt64}(undef, ziggurat_table_size)
we = Vector{Float64}(undef, ziggurat_table_size)
fe = Vector{Float64}(undef, ziggurat_table_size)
function randmtzig_fill_ziggurat_tables() # Operates on the global arrays
    wib = big.(wi)
    fib = big.(fi)
    web = big.(we)
    feb = big.(fe)
    # Ziggurat tables for the normal distribution
    x1 = ziggurat_nor_r
    wib[256] = x1/nmantissa
    fib[256] = exp(-0.5*x1*x1)
    # Index zero is special for tail strip, where Marsaglia and Tsang
    # defines this as
    # k_0 = 2^31 * r * f(r) / v, w_0 = 0.5^31 * v / f(r), f_0 = 1,
    # where v is the area of each strip of the ziggurat.
    ki[1] = trunc(UInt64,x1*fib[256]/nor_section_area*nmantissa)
    wib[1] = nor_section_area/fib[256]/nmantissa
    fib[1] = one(BigFloat)

    for i = 255:-1:2
        # New x is given by x = f^{-1}(v/x_{i+1} + f(x_{i+1})), thus
        # need inverse operator of y = exp(-0.5*x*x) -> x = sqrt(-2*ln(y))
        x = sqrt(-2.0*log(nor_section_area/x1 + fib[i+1]))
        ki[i+1] = trunc(UInt64,x/x1*nmantissa)
        wib[i] = x/nmantissa
        fib[i] = exp(-0.5*x*x)
        x1 = x
    end

    ki[2] = UInt64(0)

    # Zigurrat tables for the exponential distribution
    x1 = ziggurat_exp_r
    web[256] = x1/emantissa
    feb[256] = exp(-x1)

    # Index zero is special for tail strip, where Marsaglia and Tsang
    # defines this as
    # k_0 = 2^32 * r * f(r) / v, w_0 = 0.5^32 * v / f(r), f_0 = 1,
    # where v is the area of each strip of the ziggurat.
    ke[1] = trunc(UInt64,x1*feb[256]/exp_section_area*emantissa)
    web[1] = exp_section_area/feb[256]/emantissa
    feb[1] = one(BigFloat)

    for i = 255:-1:2
        # New x is given by x = f^{-1}(v/x_{i+1} + f(x_{i+1})), thus
        # need inverse operator of y = exp(-x) -> x = -ln(y)
        x = -log(exp_section_area/x1 + feb[i+1])
        ke[i+1] = trunc(UInt64,x/x1*emantissa)
        web[i] = x/emantissa
        feb[i] = exp(-x)
        x1 = x
    end
    ke[2] = zero(UInt64)

    wi[:] = wib
    fi[:] = fib
    we[:] = web
    fe[:] = feb
    return nothing
end
randmtzig_fill_ziggurat_tables()
@test all(ki == Random.ki)
@test all(wi == Random.wi)
@test all(fi == Random.fi)
@test all(ke == Random.ke)
@test all(we == Random.we)
@test all(fe == Random.fe)

for U in (Int64, UInt64)
    @test all(div(one(UInt64) << 52, k)*k - 1 == SamplerRangeInt(map(U, 1:k)).u
              for k in 13 .+ Int64(2).^(1:30))
end

#issue 8257
let i8257 = 1:1/3:100
    for i = 1:100
        @test rand(i8257) in i8257
    end
end

# test code paths of rand!

let mt = MersenneTwister(0)
    A128 = Vector{UInt128}()
    @test length(rand!(mt, A128)) == 0
    for (i,n) in enumerate([1, 3, 5, 6, 10, 11, 30])
        resize!(A128, n)
        rand!(mt, A128)
        @test length(A128) == n
        @test A128[end] == UInt128[0x15de6b23025813ad129841f537a04e40,
                                   0xcfa4db38a2c65bc4f18c07dc91125edf,
                                   0x33bec08136f19b54290982449b3900d5,
                                   0xde41af3463e74cb830dad4add353ca20,
                                   0x066d8695ebf85f833427c93416193e1f,
                                   0x48fab49cc9fcee1c920d6dae629af446,
                                   0x4b54632b4619f4eca22675166784d229][i]
    end

    Random.seed!(mt, 0)
    Aend = Any[]
    Bend = Any[]
    for (i,T) in enumerate([Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, Float16, Float32])
        A = Vector{T}(undef, 16)
        B = Vector{T}(undef, 31)
        rand!(mt, A)
        rand!(mt, B)
        push!(Aend, A[end])
        push!(Bend, B[end])
    end
    @test Aend == Any[21, 0x7b, 17385, 0x3086, -1574090021, 0xadcb4460, 6797283068698303107, 0x68a9f9865393cfd6,
                      33687499368208574024854346399216845930, Float16(0.7744), 0.97259974f0]
    @test Bend == Any[49, 0x65, -3725, 0x719d, 814246081, 0xdf61843a, -3433174948434291912, 0xd461716f27c91500,
                      -85900088726243933988214632401750448432, Float16(0.10645), 0.13879478f0]

    Random.seed!(mt, 0)
    AF64 = Vector{Float64}(undef, Random.dsfmt_get_min_array_size()-1)
    @test rand!(mt, AF64)[end] == 0.957735065345398
    @test rand!(mt, AF64)[end] == 0.6492481059865669
    resize!(AF64, 2*length(mt.vals))
    @test invoke(rand!, Tuple{MersenneTwister,AbstractArray{Float64},Random.SamplerTrivial{Random.CloseOpen01_64}},
                 mt, AF64, Random.SamplerTrivial(Random.CloseOpen01()))[end]  == 0.1142787906708973
end

# Issue #9037
let mt = MersenneTwister(0)
    a = Vector{Float64}()
    resize!(a, 1000) # could be 8-byte aligned
    b = Vector{Float64}(undef, 1000) # should be 16-byte aligned
    c8 = Vector{UInt64}(undef, 1001)
    pc8 = pointer(c8)
    if Int(pc8) % 16 == 0
        # Make sure pc8 is not 16-byte aligned since that's what we want to test.
        # It still has to be 8-byte aligned since it is otherwise invalid on
        # certain architectures (e.g. ARM)
        pc8 += 8
    end
    c = unsafe_wrap(Array, Ptr{Float64}(pc8), 1000) # Int(pointer(c)) % 16 == 8

    for A in (a, b, c)
        local A
        Random.seed!(mt, 0)
        rand(mt) # this is to fill mt.vals, cf. #9040
        rand!(mt, A) # must not segfault even if Int(pointer(A)) % 16 != 0
        @test A[end-4:end] == [0.3371041633752143, 0.41147647589610803, 0.6063082992397912, 0.9103565379264364, 0.16456579813368521]
    end
end

# make sure reading 128-bit ints from RandomDevice works
let a = [rand(RandomDevice(), UInt128) for i=1:10]
    @test reduce(|, a)>>>64 != 0
end

# test all rand APIs
for rng in ([], [MersenneTwister(0)], [RandomDevice()], [Xoshiro()])
    ftypes = [Float16, Float32, Float64]
    cftypes = [ComplexF16, ComplexF32, ComplexF64, ftypes...]
    types = [Bool, Char, BigFloat, Base.BitInteger_types..., ftypes...]
    randset = Set(rand(Int, 20))
    randdict = Dict(zip(rand(Int,10), rand(Int, 10)))
    collections = [BitSet(rand(1:100, 20))          => Int,
                   randset                          => Int,
                   GenericSet(randset)              => Int,
                   randdict                         => Pair{Int,Int},
                   GenericDict(randdict)            => Pair{Int,Int},
                   1:100                            => Int,
                   rand(Int, 100)                   => Int,
                   Int                              => Int,
                   Float64                          => Float64,
                   "qwèrtï"                         => Char,
                   GenericString("qwèrtï")          => Char,
                   OffsetArray(rand(2, 3), (4, -5)) => Float64]
    functypes = Dict(rand  => types, randn  => cftypes, randexp  => ftypes,
                     rand! => types, randn! => cftypes, randexp! => ftypes)

    b2 = big(2)
    u3 = UInt(3)
    for f in [rand, randn, randexp]
        f(rng...)                     ::Float64
        f(rng..., 5)                  ::Vector{Float64}
        f(rng..., 2, 3)               ::Array{Float64, 2}
        f(rng..., b2, u3)             ::Array{Float64, 2}
        for T in functypes[f]
            a0 = f(rng..., T)         ::T
            a1 = f(rng..., T, 5)      ::Vector{T}
            a2 = f(rng..., T, 2, 3)   ::Array{T, 2}
            a3 = f(rng..., T, b2, u3) ::Array{T, 2}
            a4 = f(rng..., T, (2, 3)) ::Array{T, 2}
            if T <: AbstractFloat && f === rand
                for a in [a0, a1..., a2..., a3..., a4...]
                    @test 0.0 <= a < 1.0
                end
            end
        end
    end
    for (C, T) in collections
        a0  = rand(rng..., C)                                                       ::T
        a1  = rand(rng..., C, 5)                                                    ::Vector{T}
        a2  = rand(rng..., C, 2, 3)                                                 ::Array{T, 2}
        a3  = rand(rng..., C, (2, 3))                                               ::Array{T, 2}
        a4  = rand(rng..., C, b2, u3)                                               ::Array{T, 2}
        a5  = rand!(rng..., Array{T}(undef, 5), C)                          ::Vector{T}
        a6  = rand!(rng..., Array{T}(undef, 2, 3), C)                       ::Array{T, 2}
        a7  = rand!(rng..., GenericArray{T}(undef, 5), C)                   ::GenericArray{T, 1}
        a8  = rand!(rng..., GenericArray{T}(undef, 2, 3), C)                ::GenericArray{T, 2}
        a9  = rand!(rng..., OffsetArray(Array{T}(undef, 5), 9), C)          ::OffsetArray{T, 1}
        a10 = rand!(rng..., OffsetArray(Array{T}(undef, 2, 3), (-2, 4)), C) ::OffsetArray{T, 2}
        @test size(a1) == (5,)
        @test size(a2) == size(a3) == (2, 3)
        for a in [a0, a1..., a2..., a3..., a4..., a5..., a6..., a7..., a8..., a9..., a10...]
            if C isa Type
                @test a isa C
            else
                @test a in C
            end
        end
    end
    for C in [1:0, Dict(), Set(), BitSet(), Int[],
              GenericDict(Dict()), GenericSet(Set()),
              "", Test.GenericString("")]
        @test_throws ArgumentError rand(rng..., C)
        @test_throws ArgumentError rand(rng..., C, 5)
    end
    for f! in [rand!, randn!, randexp!]
        for T in functypes[f!]
            X = T == Bool ? T[0,1] : T[0,1,2]
            for A in (Vector{T}(undef, 5),
                      Matrix{T}(undef, 2, 3),
                      GenericArray{T}(undef, 5),
                      GenericArray{T}(undef, 2, 3),
                      OffsetArray(Array{T}(undef, 5), -3),
                      OffsetArray(Array{T}(undef, 2, 3), (4, 5)))
                local A
                f!(rng..., A)                    ::typeof(A)
                if f! === rand!
                    f!(rng..., A, X)             ::typeof(A)
                    if A isa Array && T !== Char # Char/Integer comparison
                        f!(rng..., sparse(A))    ::typeof(sparse(A))
                        f!(rng..., sparse(A), X) ::typeof(sparse(A))
                    end
                end
            end
        end
    end

    bitrand(rng..., 5)             ::BitArray{1}
    bitrand(rng..., 2, 3)          ::BitArray{2}
    bitrand(rng..., b2, u3)        ::BitArray{2}
    rand!(rng..., BitVector(undef, 5))     ::BitArray{1}
    rand!(rng..., BitMatrix(undef, 2, 3))  ::BitArray{2}

    # Test that you cannot call randn or randexp with non-Float types.
    for r in [randn, randexp, randn!, randexp!]
        local r
        @test_throws MethodError r(Int)
        @test_throws MethodError r(Int32)
        @test_throws MethodError r(Bool)
        @test_throws MethodError r(String)
        @test_throws MethodError r(AbstractFloat)
        # TODO(#17627): Consider adding support for randn(BigFloat) and removing this test.
        @test_throws MethodError r(BigFloat)

        @test_throws MethodError r(Int64, (2,3))
        @test_throws MethodError r(String, 1)

        @test_throws MethodError r(rng..., Number, (2,3))
        @test_throws MethodError r(rng..., Any, 1)
    end
end

function hist(X, n)
    v = zeros(Int, n)
    for x in X
        v[floor(Int, x*n) + 1] += 1
    end
    v
end

# test uniform distribution of floats
for rng in [MersenneTwister(), RandomDevice(), Xoshiro()],
    T in [Float16, Float32, Float64, BigFloat],
        prec in (T == BigFloat ? [3, 53, 64, 100, 256, 1000] : [256])
    setprecision(BigFloat, prec) do
        # array version
        counts = hist(rand(rng, T, 2000), 4)
        @test minimum(counts) > 300 # should fail with proba < 1e-26
        # scalar version
        counts = hist([rand(rng, T) for i in 1:2000], 4)
        @test minimum(counts) > 300
    end
end

@testset "rand(Bool) uniform distribution" begin
    for n in [rand(1:8), rand(9:16), rand(17:64)]
        a = zeros(Bool, n)
        a8 = unsafe_wrap(Array, Ptr{UInt8}(pointer(a)), length(a); own=false) # unsafely observe the actual bit patterns in `a`
        as = zeros(Int, n)
        # we will test statistical properties for each position of a,
        # but also for 3 linear combinations of positions (for the array version)
        lcs = unique!.([rand(1:n, 2), rand(1:n, 3), rand(1:n, 5)])
        aslcs = zeros(Int, 3)
        for rng = (MersenneTwister(), RandomDevice(), Xoshiro())
            for scalar = [false, true]
                fill!(a, 0)
                fill!(as, 0)
                fill!(aslcs, 0)
                for _ = 1:49
                    if scalar
                        for i in eachindex(as)
                            as[i] += rand(rng, Bool)
                        end
                    else
                        as .+= rand!(rng, a)
                        @test all(x -> x === 0x00 || x === 0x01, a8)
                        aslcs .+= [xor(getindex.(Ref(a), lcs[i])...) for i in 1:3]
                    end
                end
                @test all(x -> 7 <= x <= 42, as) # for each x, fails with proba ≈ 2/35_000_000
                if !scalar
                    @test all(x -> 7 <= x <= 42, aslcs)
                end
            end
        end
    end
end

@testset "reproducility of methods for $RNG" for RNG=(MersenneTwister,Xoshiro)
    mta, mtb = RNG(42), RNG(42)

    @test rand(mta) == rand(mtb)
    @test rand(mta,10) == rand(mtb,10)
    @test randn(mta) == randn(mtb)
    @test randn(mta,10) == randn(mtb,10)
    @test randexp(mta) == randexp(mtb)
    @test randexp(mta,10) == randexp(mtb,10)
    @test rand(mta,1:100) == rand(mtb,1:100)
    @test rand(mta,1:10,10) == rand(mtb,1:10,10)
    @test rand(mta,Bool) == rand(mtb,Bool)
    @test bitrand(mta,10) == bitrand(mtb,10)

    @test randstring(mta) == randstring(mtb)
    @test randstring(mta,10) == randstring(mtb,10)

    @test randsubseq(mta,1:10,0.4) == randsubseq(mtb,1:10,0.4)
    @test randsubseq!(mta,Int[],1:10,0.4) == randsubseq!(mtb,Int[],1:10,0.4)

    @test shuffle(mta,Vector(1:10)) == shuffle(mtb,Vector(1:10))
    @test shuffle!(mta,Vector(1:10)) == shuffle!(mtb,Vector(1:10))
    @test shuffle(mta,Vector(2:11)) == shuffle(mtb,2:11)
    @test shuffle!(mta, rand(mta, 2, 3)) == shuffle!(mtb, rand(mtb, 2, 3))
    @test shuffle(mta, rand(mta, 2, 3)) == shuffle(mtb, rand(mtb, 2, 3))

    @test randperm(mta,10) == randperm(mtb,10)
    @test sort!(randperm(10)) == sort!(shuffle(1:10)) == 1:10
    @test randperm(mta,big(10)) == randperm(mtb,big(10)) # cf. #16376
    @test randperm(0) == []
    @test_throws ArgumentError randperm(-1)

    let p = randperm(UInt16(12))
        @test typeof(p) ≡ Vector{UInt16}
        @test sort!(p) == 1:12
    end

    A, B = Vector{Int}(undef, 10), Vector{Int}(undef, 10)
    @test randperm!(mta, A) == randperm!(mtb, B)
    @test randperm!(A) === A

    @test randcycle(mta,10) == randcycle(mtb,10)
    @test randcycle!(mta, A) == randcycle!(mtb, B)
    @test randcycle!(A) === A

    let p = randcycle(UInt16(10))
        @test typeof(p) ≡ Vector{UInt16}
        @test sort!(p) == 1:10
    end

    @test sprand(mta,1,1,0.9) == sprand(mtb,1,1,0.9)
    @test sprand(mta,10,10,0.3) == sprand(mtb,10,10,0.3)
end

@testset "MersenneTwister polynomial generation and jump" begin
    seed = rand(UInt)
    mta = MersenneTwister(seed)
    mtb = MersenneTwister(seed)
    step = 25000*2
    size = 4
    jump25000 = " ";
    jump1e20  = " "
    @test DSFMT.GF2X(jump25000) == DSFMT.calc_jump(25000)
    @test DSFMT.GF2X(jump1e20)  == DSFMT.calc_jump(big(10)^20)

    # check validity of the implementation of copy(::GF2X)
    let z = big(1); @assert z !== z+0 end

    # test PRNG jump

    function randjumpvec(m, steps, len) # old version of randjump
        mts = accumulate(Future.randjump, fill(steps, len-1); init=m)
        pushfirst!(mts, m)
        mts
    end

    mts = randjumpvec(mta, 25000, size)
    @test length(mts) == 4

    for x in (rand(mts[k], Float64) for j=1:step, k=1:size)
        @test rand(mtb, Float64) == x
    end

    @testset "generated RNGs are in a deterministic state (relatively to ==)" begin
        m = MersenneTwister()
        @test Future.randjump(m, 25000) == Future.randjump(m, 25000)
    end
end

# test that the following is not an error (#16925)
guardseed() do
    Random.seed!(typemax(UInt))
    Random.seed!(typemax(UInt128))
end

# copy, == and hash
let seed = rand(UInt32, 10)
    r = MersenneTwister(seed)
    @test r == MersenneTwister(seed) # r.vals should be all zeros
    @test hash(r) == hash(MersenneTwister(seed))
    s = copy(r)
    @test s == r && s !== r
    @test hash(s) == hash(r)
    skip, len = rand(0:2000, 2)
    for j=1:skip
        rand(r)
        rand(s)
    end
    @test rand(r, len) == rand(s, len)
    @test s == r
    @test hash(s) == hash(r)
    h = rand(UInt)
    @test hash(s, h) == hash(r, h)
end

# MersenneTwister initialization with invalid values
@test_throws DomainError DSFMT.DSFMT_state(zeros(Int32, rand(0:DSFMT.JN32-1)))

@test_throws DomainError MersenneTwister(zeros(UInt32, 1), DSFMT.DSFMT_state(),
                                         zeros(Float64, 10), zeros(UInt128, MT_CACHE_I>>4), 0, 0, 0, 0, -1, -1)

@test_throws DomainError MersenneTwister(zeros(UInt32, 1), DSFMT.DSFMT_state(),
                                         zeros(Float64, MT_CACHE_F), zeros(UInt128, MT_CACHE_I>>4), -1, 0, 0, 0, -1, -1)

@test_throws DomainError MersenneTwister(zeros(UInt32, 1), DSFMT.DSFMT_state(),
                                         zeros(Float64, MT_CACHE_F), zeros(UInt128, MT_CACHE_I>>3), 0, 0, 0, 0, -1, -1)

@test_throws DomainError MersenneTwister(zeros(UInt32, 1), DSFMT.DSFMT_state(),
                                         zeros(Float64, MT_CACHE_F), zeros(UInt128, MT_CACHE_I>>4), 0, -1, 0, 0, -1, -1)

# seed is private to MersenneTwister
let seed = rand(UInt32, 10)
    r = MersenneTwister(seed)
    @test r.seed == seed && r.seed !== seed
    # RNGs do not share their seed in randjump
    let r2 = Future.randjump(r, big(10)^20)
        @test  r.seed !== r2.seed
        Random.seed!(r2)
        @test seed == r.seed != r2.seed
    end
    resize!(seed, 4)
    @test r.seed != seed
end

# Random.seed!(rng, ...) returns rng (#21248)
guardseed() do
    g = Random.default_rng()
    m = MersenneTwister(0)
    @test Random.seed!() === g
    @test Random.seed!(rand(UInt)) === g
    @test Random.seed!(rand(UInt32, rand(1:8))) === g
    @test Random.seed!(m) === m
    @test Random.seed!(m, rand(UInt)) === m
    @test Random.seed!(m, rand(UInt32, rand(1:10))) === m
    @test Random.seed!(m, rand(1:10)) === m
end

# Issue 20062 - ensure internal functions reserve_1, reserve are type-stable
let r = MersenneTwister(0)
    @inferred Random.reserve_1(r)
    @inferred Random.reserve(r, 1)
end

# test randstring API
let b = ['0':'9';'A':'Z';'a':'z']
    for rng = [[], [MersenneTwister(0)]]
        @test length(randstring(rng...)) == 8
        @test length(randstring(rng..., 20)) == 20
        @test issubset(randstring(rng...), b)
        for c = ['a':'z', "qwèrtï", Set(codeunits("gcat"))],
                len = [8, 20]
            s = len == 8 ? randstring(rng..., c) : randstring(rng..., c, len)
            @test length(s) == len
            if eltype(c) == Char
                @test issubset(s, c)
            else # UInt8
                @test issubset(s, Set(Char(v) for v in c))
            end
        end
    end
    @test randstring(MersenneTwister(0)) == randstring(MersenneTwister(0), b)
end

# this shouldn't crash (#22403)
@test_throws ArgumentError rand!(Union{UInt,Int}[1, 2, 3])

@testset "$RNG() & Random.seed!(rng::$RNG) initializes randomly" for RNG in (MersenneTwister, RandomDevice, Xoshiro)
    m = RNG()
    a = rand(m, Int)
    m = RNG()
    @test rand(m, Int) != a
    # passing `nothing` is equivalent to passing nothing
    m = RNG(nothing)
    b = rand(m, Int)
    @test b != a
    Random.seed!(m)
    c = rand(m, Int)
    @test c ∉ (a, b)
    Random.seed!(m)
    @test rand(m, Int) ∉ (a, b, c)
    Random.seed!(m, nothing)
    d = rand(m, Int)
    @test d ∉ (a, b, c)
    Random.seed!(m, nothing)
    @test rand(m, Int) ∉ (a, b, c, d)
end

@testset "$RNG(seed) & Random.seed!(m::$RNG, seed) produce the same stream" for RNG=(MersenneTwister,Xoshiro)
    seeds = Any[0, 1, 2, 10000, 10001, rand(UInt32, 8), rand(UInt128, 3)...]
    if RNG == Xoshiro
        push!(seeds, rand(UInt64, rand(1:4)))
    end
    for seed=seeds
        m = RNG(seed)
        a = [rand(m) for _=1:100]
        Random.seed!(m, seed)
        @test a == [rand(m) for _=1:100]
    end
end

@testset "Random.seed!(seed) sets Random.GLOBAL_SEED" begin
    seeds = Any[0, rand(UInt128), rand(UInt64, 4)]

    for seed=seeds
        Random.seed!(seed)
        @test Random.GLOBAL_SEED === seed
    end
    # two separate loops as otherwise we are no sure that the second call (with GLOBAL_RNG)
    # actually sets GLOBAL_SEED
    for seed=seeds
        Random.seed!(Random.GLOBAL_RNG, seed)
        @test Random.GLOBAL_SEED === seed
    end

    Random.seed!(nothing)
    seed1 = Random.GLOBAL_SEED
    @test seed1 isa Vector{UInt64} # could change, but must not be nothing

    Random.seed!(Random.GLOBAL_RNG, nothing)
    seed2 = Random.GLOBAL_SEED
    @test seed2 isa Vector{UInt64}
    @test seed2 != seed1

    Random.seed!()
    seed3 = Random.GLOBAL_SEED
    @test seed3 isa Vector{UInt64}
    @test seed3 != seed2

    Random.seed!(Random.GLOBAL_RNG)
    seed4 = Random.GLOBAL_SEED
    @test seed4 isa Vector{UInt64}
    @test seed4 != seed3
end

struct RandomStruct23964 end
@testset "error message when rand not defined for a type" begin
    @test_throws ArgumentError rand(nothing)
    @test_throws ArgumentError rand(RandomStruct23964())
end

@testset "rand(::$(typeof(RNG)), ::UnitRange{$T}" for RNG ∈ (MersenneTwister(rand(UInt128)), RandomDevice(), Xoshiro()),
                                                        T ∈ (Int8, Int16, Int32, UInt32, Int64, Int128, UInt128)
    for S in (SamplerRangeInt, SamplerRangeFast, SamplerRangeNDL)
        S == SamplerRangeNDL && sizeof(T) > 8 && continue
        r = T(1):T(108)
        @test rand(RNG, S(r)) ∈ r
        @test rand(RNG, S(typemin(T):typemax(T))) isa T
        a, b = sort!(rand(-1000:1000, 2) .% T)
        @test rand(RNG, S(a:b)) ∈ a:b
    end
end

@testset "rand! is allocation-free" begin
    for A in (Array{Int}(undef, 20), Array{Float64}(undef, 5, 4), BitArray(undef, 20), BitArray(undef, 50, 40))
        rand!(A)
        @test @allocated(rand!(A)) == 0
    end
end

@testset "gentype for UniformBits" begin
    @test Random.gentype(Random.UInt52()) == UInt64
    @test Random.gentype(Random.UInt52(UInt128)) == UInt128
    @test Random.gentype(Random.UInt104()) == UInt128
end

@testset "shuffle[!]" begin
    a = []
    @test shuffle(a) == a # issue #28727
    @test shuffle!(a) === a
    a = rand(Int, 1)
    @test shuffle(a) == a
end

@testset "rand(::Tuple)" begin
    for x in (0x1, 1)
        @test rand((x,)) == 0x1
        @test rand((x, 2)) ∈ 1:2
        @test rand((x, 2, 3)) ∈ 1:3
        @test rand((x, 2, 3, 4)) ∈ 1:4
        @test rand((x, 2, 3, 4, 5)) ∈ 1:5
        @test rand((x, 2, 3, 4, 6)) ∈ 1:6
    end
end

@testset "GLOBAL_RNG" begin
    local GLOBAL_RNG = Random.GLOBAL_RNG
    local LOCAL_RNG = Random.default_rng()
    @test VERSION < v"2" # deprecate this in v2

    @test Random.seed!(GLOBAL_RNG, nothing) === LOCAL_RNG
    @test Random.seed!(GLOBAL_RNG, UInt32[0]) === LOCAL_RNG
    @test Random.seed!(GLOBAL_RNG, 0) === LOCAL_RNG
    @test Random.seed!(GLOBAL_RNG) === LOCAL_RNG

    xo = Xoshiro()
    @test copy!(xo, GLOBAL_RNG) === xo
    @test xo == LOCAL_RNG
    Random.seed!(xo, 2)
    @test xo != LOCAL_RNG
    @test copy!(GLOBAL_RNG, xo) === LOCAL_RNG
    @test xo == LOCAL_RNG
    xo2 = copy(GLOBAL_RNG)
    @test xo2 !== LOCAL_RNG
    @test xo2 == LOCAL_RNG

    for T in (Random.UInt52Raw{UInt64},
              Random.UInt104Raw{UInt128},
              Random.CloseOpen12_64)
        x = Random.SamplerTrivial(T())
        @test rand(GLOBAL_RNG, x) === rand(xo, x)
    end
    for T in (Int64, UInt64, Int128, UInt128, Bool, Int8, UInt8, Int16, UInt16, Int32, UInt32)
        x = Random.SamplerType{T}()
        @test rand(GLOBAL_RNG, x) === rand(xo, x)
    end

    A = fill(0.0, 100, 100)
    B = fill(1.0, 100, 100)
    vA = view(A, :, :)
    vB = view(B, :, :)
    I1 = Random.SamplerTrivial(Random.CloseOpen01{Float64}())
    I2 = Random.SamplerTrivial(Random.CloseOpen12{Float64}())
    @test rand!(GLOBAL_RNG, A, I1) === A == rand!(xo, B, I1) === B
    B = fill!(B, 1.0)
    @test rand!(GLOBAL_RNG, vA, I1) === vA
    rand!(xo, vB, I1)
    @test A == B
    for T in (Float16, Float32)
        B = fill!(B, 1.0)
        @test rand!(GLOBAL_RNG, A, I2) === A == rand!(xo, B, I2) === B
        B = fill!(B, 1.0)
        @test rand!(GLOBAL_RNG, A, I1) === A == rand!(xo, B, I1) === B
    end
    for T in Base.BitInteger_types
        x = Random.SamplerType{T}()
        B = fill!(B, 1.0)
        @test rand!(GLOBAL_RNG, A, x) === A == rand!(xo, B, x) === B
    end
    # issue #33170
    @test Sampler(GLOBAL_RNG, 2:4, Val(1)) isa SamplerRangeNDL
    @test Sampler(GLOBAL_RNG, 2:4, Val(Inf)) isa SamplerRangeNDL

    rng = copy(GLOBAL_RNG)
    # make sure _GLOBAL_RNG and the underlying implementation use the same code path
    @test rand(rng) == rand(GLOBAL_RNG)
    @test rand(rng) == rand(GLOBAL_RNG)
    @test rand(rng) == rand(GLOBAL_RNG)
    @test rand(rng) == rand(GLOBAL_RNG)
end

@testset "RNGs broadcast as scalars: T" for T in (MersenneTwister, RandomDevice)
    @test length.(rand.(T(), 1:3)) == 1:3
end

@testset "generated scalar integers do not overlap" begin
    m = MersenneTwister()
    xs = reinterpret(UInt64, m.ints)
    x = rand(m, UInt128)  # m.idxI % 16 == 0
    @test x % UInt64 == xs[end-1]
    x = rand(m, UInt64)
    @test x == xs[end-2]
    x = rand(m, UInt64)
    @test x == xs[end-3]
    x = rand(m, UInt64)
    @test x == xs[end-4]
    x = rand(m, UInt128) # m.idxI % 16 == 8
    @test (x >> 64) % UInt64 == xs[end-6]
    @test x % UInt64 == xs[end-7]
    x = rand(m, UInt64)
    @test x == xs[end-8] # should not be == xs[end-7]

    s = Set{UInt64}()
    n = 0
    for _=1:2000
        x = rand(m, rand((UInt64, UInt128, Int64, Int128)))
        if sizeof(x) == 8
            push!(s, x % UInt64)
            n += 1
        else
            push!(s, x % UInt64, (x >> 64) % UInt64)
            n += 2
        end
    end
    @test length(s) == n
end

@testset "show" begin
    @testset "MersenneTwister" begin
        m = MersenneTwister(123)
        @test string(m) == "MersenneTwister(123)"
        Random.jump!(m, 2*big(10)^20)
        @test string(m) == "MersenneTwister(123, (200000000000000000000, 0))"
        @test m == MersenneTwister(123, (200000000000000000000, 0))
        rand(m)
        @test string(m) == "MersenneTwister(123, (200000000000000000000, 1002, 0, 1))"

        @test m == MersenneTwister(123, (200000000000000000000, 1002, 0, 1))
        rand(m, Int64)
        @test string(m) == "MersenneTwister(123, (200000000000000000000, 2256, 0, 1, 1002, 1))"
        @test m == MersenneTwister(123, (200000000000000000000, 2256, 0, 1, 1002, 1))

        m = MersenneTwister(0x0ecfd77f89dcd508caa37a17ebb7556b)
        @test string(m) == "MersenneTwister(0xecfd77f89dcd508caa37a17ebb7556b)"
        rand(m, Int64)
        @test string(m) == "MersenneTwister(0xecfd77f89dcd508caa37a17ebb7556b, (0, 1254, 0, 0, 0, 1))"
        @test m == MersenneTwister(0xecfd77f89dcd508caa37a17ebb7556b, (0, 1254, 0, 0, 0, 1))

        m = MersenneTwister(0); rand(m, Int64); rand(m)
        @test string(m) == "MersenneTwister(0, (0, 2256, 1254, 1, 0, 1))"
        @test m == MersenneTwister(0, (0, 2256, 1254, 1, 0, 1))
    end

    @testset "RandomDevice" begin
        @test string(RandomDevice()) == "$RandomDevice()"
    end
end

@testset "rand[!] for BigInt/BigFloat" begin
    rng = MersenneTwister()
    s = Random.SamplerBigInt(MersenneTwister, 1:big(9))
    x = rand(s)
    @test x isa BigInt
    y = rand!(rng, x, s)
    @test y === x
    @test x in 1:9

    for t = BigInt[0, 10, big(2)^100]
        s = Random.Sampler(rng, t:t) # s.nlimbs == 0
        @test rand(rng, s) == t
        @test x === rand!(rng, x, s) == t

        s = Random.Sampler(rng, big(-1):t) # s.nlimbs != 0
        @test rand(rng, s) ∈ -1:t
        @test x === rand!(rng, x, s) ∈ -1:t

    end

    s = Random.Sampler(MersenneTwister, Random.CloseOpen01(BigFloat))
    x = rand(s)
    @test x isa BigFloat
    y = rand!(rng, x, s)
    @test y === x
    @test 0 <= x < 1
    s = Random.Sampler(MersenneTwister, Random.CloseOpen12(BigFloat))
    y = rand!(rng, x, s)
    @test y === x
    @test 1 <= x < 2

    old_prec = precision(BigFloat)
    setprecision(100) do
        x = rand(s) # should use precision of s
        @test precision(x) == old_prec
        x = BigFloat()
        @test_throws ArgumentError rand!(rng, x, s) # incompatible precision
    end
    s = setprecision(100) do
        Random.Sampler(MersenneTwister, Random.CloseOpen01(BigFloat))
    end
    x = rand(s) # should use precision of s
    @test precision(x) == 100
    x = BigFloat()
    @test_throws ArgumentError rand!(rng, x, s) # incompatible precision
end

@testset "shuffle! for BitArray" begin
    # Test that shuffle! is uniformly random on BitArrays
    rng = MersenneTwister(123)
    a = (reshape(1:(4*5), 4, 5) .<= 2) # 4x5 BitMatrix whose first two elements are true, rest are false
    m = mean(1:50_000) do _
        shuffle!(rng, a)
    end # mean result of shuffle!-ing a 50_000 times. If the shuffle! is uniform, then each index has a
    # 10% chance of having a true in it, so each value should converge to 0.1.
    @test minimum(m) >= 0.094
    @test maximum(m) <= 0.106
end

# issue #42752
# test that running finalizers that launch tasks doesn't change RNG stream
function f42752(do_gc::Bool, cell = (()->Any[[]])())
    a = rand()
    if do_gc
        finalizer(cell[1]) do _
            @async nothing
        end
        cell[1] = nothing
        GC.gc()
    end
    b = rand()
    (a, b)
end
guardseed() do
    for _ in 1:4
        Random.seed!(1)
        val = f42752(false)
        Random.seed!(1)
        @test f42752(true) === val
    end
end
