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

# Test functionality of BitSet

using Random

@testset "Construction, collect" begin
    data_in = (1,5,100)
    s = BitSet(data_in)
    data_out = collect(s)
    @test all(map(in(data_out), data_in))
    @test length(data_out) === length(data_in)
end

@testset "eltype, empty" begin
    @test eltype(BitSet()) === Int
    @test eltype(BitSet) === Int
    @test isequal(empty(BitSet([1,2,3])), BitSet())
end

@testset "show" begin
    @test sprint(show, BitSet()) == "BitSet([])"
    @test sprint(show, BitSet([1,2,3])) == "BitSet([1, 2, 3])"
    show(IOBuffer(), BitSet())
end

@testset "in, hashing" begin
    s = BitSet([1,2,10,20,200,300,1000,10000,10002])
    @test last(s) === 10002
    @test first(s) === 1
    @test length(s) === 9
    @test pop!(s) === 10002
    @test_throws KeyError pop!(s, -1)
    @test length(s) === 8
    @test popfirst!(s) === 1
    @test length(s) === 7
    @test !in(0,s)
    @test !in(1,s)
    @test in(2,s)
    @test !in(10002,s)
    @test in(10000,s)
    @test in(10000.0,s)
    @test !in(10002.0,s)
    @test_throws ArgumentError first(BitSet())
    @test_throws ArgumentError last(BitSet())
    t = copy(s)
    sizehint!(t, 20000) #check that hash does not depend on size of internal storage
    @test hash(s) === hash(t)
    push!(t, 20000)
    @test 20000 in t
    sizehint!(t, 200) # ensure that sizehint!'ing a small amount isn't destructive
    @test 20000 in t
    @test pop!(t, 20000) === 20000
    @test hash(s) === hash(t)
    # Ensure empty chunks don't affect hash
    @test hash(BitSet([1])) != hash(BitSet([17]))
    @test hash(BitSet([1])) != hash(BitSet([33]))
    @test hash(BitSet([1])) != hash(BitSet([65]))
    @test hash(BitSet([1])) != hash(BitSet([129]))
    # test with a different internal structure
    s = BitSet([129])
    pop!(push!(s, 65), 65)
    @test hash(BitSet([1])) != hash(s)

    @test !(-1 in BitSet(1:10))
end

# # issue #8570
# This requires 2^29 bytes of storage, which is too much for a simple test
# s = BitSet(typemax(Int32))
# @test length(s) === 1
# for b in s; b; end

@testset "union!, symdiff!" begin
    i = BitSet([1, 2, 3])
    union!(i, [1, 2])
    @test length(i) === 3
    union!(i, [3, 4, 5])
    @test length(i) === 5

    @test_throws KeyError pop!(i, 10)

    empty!(i)
    @test length(i) === 0

    @test symdiff!(i, -3) == BitSet([-3])
    @test symdiff!(i, -3) == BitSet([])

    @test symdiff!(i, 3) == BitSet([3])
    @test symdiff!(i, 257) == BitSet([3, 257])
    @test symdiff!(i, [3, 6]) == BitSet([6, 257])

    i = BitSet(1:6)
    @test symdiff!(i, BitSet([6, 513])) == BitSet([1:5; 513])

    @test 0 ∈ symdiff!(BitSet(rand(1:100, 30)), 0)
    @test BitSet(0:2:4) ⊆ symdiff!(BitSet(rand(5:100, 30)), [0, 2, 4])

    # issue #23557 :
    @test_throws MethodError symdiff!(BitSet([1]), ['a']) # should no stack-overflow
    @test_throws MethodError symdiff!(BitSet([1, 2]),  [[1]]) # should not return BitSet([2])
end

@testset "copy, copy!, empty" begin
    s1 = BitSet([1,2,3])
    s2 = empty(s1)
    copy!(s2, s1)
    s3 = copy(s2)
    @test s3 == s2 == s1
    @test collect(s3) == collect(s2) == [1,2,3]
end

@testset "push!, union" begin
    i = BitSet([1, 2, 3])
    j = union(i)
    @test j == i
    @test !(j === i)

    j = BitSet([4, 5, 6])
    @test union(i, j) == BitSet(1:6)

    k = BitSet([7, 8, 9])
    @test union(i, j, k) == BitSet(1:9)
    i = BitSet([1, 2, 3])
    j = union(i)
    @test j == i
    @test !(j === i)

    j = BitSet([4, 5, 6])
    @test union(i, j) == BitSet(1:6)

    k = BitSet([7, 8, 9])
    @test union(i, j, k) == BitSet(1:9)

    s1 = BitSet()
    @test push!(s1, -1) == BitSet([-1])
    push!(s1, -10, 1, 10, 100, 1000)
    @test collect(s1) == [-10, -1, 1, 10, 100, 1000]
    push!(s1, 606)
    @test collect(s1) == [-10, -1, 1, 10, 100, 606, 1000]

    s2 = BitSet()
    @test s2 === union!(s2, s1)
    s3 = BitSet([-1, 1, 10, 100])
    union!(s3, [-10, 1, 606, 1000])
    s4 = union(BitSet([-1, 1, 100, 1000]), BitSet([-10, 10, 100, 606]))
    @test s1 == s2 == s3 == s4
end

@testset "pop!, delete!" begin
    s = BitSet(1:2:10)
    # deleting non-positive values should be no-op
    # (Issue #23179 : delete!(s, 0) should not crash)
    len = length(s)
    for n in -20:0
        @test length(delete!(s, n)) == len
    end
    @test pop!(s, 1) === 1
    @test !(1 in s)
    @test_throws KeyError pop!(s, 1)
    @test_throws KeyError pop!(s, -1)
    @test pop!(s, -1, 1) === 1
    @test pop!(s, 1, 0) === 0
    @test s === delete!(s, 1)
    for i in s; pop!(s, i); end
    @test isempty(s)
    push!(s, 100)
    @test pop!(s, 100) == 100
    push!(s, 1:2:10...)
    @test pop!(s) === 9
    @test pop!(s) === 7
    @test popfirst!(s) === 1
    @test popfirst!(s) === 3
    @test collect(s) == [5]
    empty!(s)
    @test isempty(s)
end

@testset "intersect" begin
    i = BitSet([1, 2, 3])
    j = BitSet([4, 5, 6])

    @test intersect(i) == i
    @test !(intersect(i) === i)
    @test intersect(i, j) == BitSet([])
    push!(j, 257)
    @test intersect(i, j) == BitSet([])
    push!(j, 2, 3, 17)
    @test intersect(i, j) == BitSet([2, 3])
    k = BitSet([1, 2, 3, 4, 5, 6, 7])
    @test intersect(i, j, k) == BitSet([2, 3])

    @test isempty(intersect(BitSet()))
    @test isempty(intersect(BitSet(1:10), BitSet()))
    @test isempty(intersect(BitSet(), BitSet(1:10)))

    @test intersect(BitSet([1,2,3])) == BitSet([1,2,3])
    @test intersect(BitSet(1:7), BitSet(3:10)) ==
    	  intersect(BitSet(3:10), BitSet(1:7)) == BitSet(3:7)
    @test intersect(BitSet(1:10), BitSet(1:4), 1:5, [2,3,10]) == BitSet([2,3])
end

@testset "setdiff, symdiff" begin
    @test setdiff(BitSet([1, 2, 3, 4]), BitSet([2, 4, 5, 6])) == BitSet([1, 3])
    @test symdiff(BitSet([1, 2, 3, 4]), BitSet([2, 4, 5, 6])) == BitSet([1, 3, 5, 6])

    s2 = BitSet([1, 2, 3, 4])
    setdiff!(s2, BitSet([2, 4, 5, 6]))
    @test s2 == BitSet([1, 3])

    s1 = BitSet(1:100)
    setdiff!(s1, BitSet(1:2:100))
    s2 = setdiff(BitSet(1:100), BitSet(1:2:100))
    @test s1 == s2 == BitSet(2:2:100)
    @test collect(s1) == collect(2:2:100)

    # issue #23191 : these tests should not segfault
    @test setdiff(s1, 0) == s1
    @test setdiff(s1, -9:0) == s1

    @test symdiff(BitSet([1, 2, 3, 4]), BitSet([2, 4, 5, 6])) ==
          symdiff(BitSet([2, 4, 5, 6]), BitSet([1, 2, 3, 4])) ==
          symdiff(BitSet([1, 2, 3, 4]), [2, 4, 5, 6]) ==
          symdiff(BitSet([2, 4, 5, 6]), [1, 2, 3, 4]) == BitSet([1, 3, 5, 6])
end

@testset "subsets, equality" begin
    i = BitSet([1, 2, 3])
    k = BitSet([4, 5])
    copy!(k, i)
    @test k == i
    @test !(k === i)
    copy!(k, k)
    @test k == i

    i = BitSet([1, 2, 3])
    j = BitSet([1, 2, 4])
    @test i != j

    push!(j, 257)
    pop!(j, 257)
    @test i != j
    @test j != i

    @test issubset(BitSet([1, 2, 4]), BitSet(1:10))
    @test issubset(BitSet([]), BitSet([]))
    @test BitSet([1, 2, 4]) < BitSet(1:10)
    @test !(BitSet([]) < BitSet([]))
    @test BitSet([1, 2, 4]) <= BitSet(1:10)
    @test BitSet([1, 2, 4]) <= BitSet([1, 2, 4])
    @test BitSet([]) <= BitSet([])

    @test BitSet(2:2:10) < BitSet(1:10)
    @test !(BitSet(2:2:10) < BitSet(2:2:10))
    @test BitSet(2:2:10) <= BitSet(2:10)
    @test BitSet(2:2:10) <= BitSet(2:2:10)

    # == with last-bit set (groups.google.com/forum/#!topic/julia-users/vZNjiIEG_sY)
    s = BitSet(255)
    @test s == s
end

@testset "setlike" begin
    p = BitSet([1,2,5,6])
    q = BitSet([1,3,5,7])
    a = Set(p)
    b = Set(q)
    for f in (union, intersect, setdiff, symdiff)
        @test collect(f(p, p)) == sort(collect(f(a, a)))
        @test collect(f(q, q)) == sort(collect(f(b, b)))
        @test collect(f(p, q)) == sort(collect(f(a, b)))
        @test collect(f(q, p)) == sort(collect(f(b, a)))
    end
end

@testset "misc" begin
    s = BitSet()
    push!(s, 1, 2, 100)
    @test !(0 in s)
    @test 1 in s
    @test 2 in s
    @test !(3 in s)
    @test 100 in s
    @test !(101 in s)
    @test !(1000 in s)
    @test first(s) === 1
    @test last(s) === 100
    @test s == BitSet([1, 2, 100])
    push!(s, 1000)
    @test [i for i in s] == [1, 2, 100, 1000]
    @test pop!(s) === 1000
    @test s == BitSet([1, 2, 100])
    @test hash(s) === hash(BitSet([1, 2, 100]))

    b = 1:1000
    s = BitSet(b)
    @test collect(s) == collect(b)
    @test length(s) === length(b)
    @test pop!(s, 100) === 100
    @test collect(s) == [1:99; 101:1000]
    @test_throws KeyError pop!(s, 100)
    @test_throws KeyError pop!(s, 0)
    @test pop!(s, 100, 0) === 0
    @test pop!(s, 99, 0) === 99
end

@testset "unsigned overflow" begin
    @test BitSet(UInt8(2^8-1)) == BitSet(2^8-1)
    @test [x for x in BitSet(UInt8(2^8-1))] == [UInt8(2^8-1)]
    @test BitSet(UInt16(2^16-1)) == BitSet(2^16-1)
    @test [x for x in BitSet(UInt16(2^16-1))] == [UInt16(2^16-1)]
end

@testset "order" begin
    a = rand(1:1000, 100)
    s = BitSet(a)
    m, M = extrema(s)
    @test m == first(s) == minimum(s) == minimum(a)
    @test M == last(s)  == maximum(s) == maximum(a)
    @test issorted(s)
end

@testset "extreme values" begin
    @test pop!(BitSet(typemin(Int))) == typemin(Int)
    @test pop!(BitSet(typemax(Int))) == typemax(Int)
end

@testset "sizehint! returns a BitSet" begin
    # see #25029
    @test sizehint!(BitSet(), 100) isa BitSet
    # TODO: test that we don't delegate sizehint! to the underlying bits
    # field without dividing by 64 (i.e. the 100 above should allocate
    # only 2 UInt64 words
end

@testset "union!(::BitSet, ::AbstractUnitRange) optimization" begin
    # check that using a:b or a:1:b gives equal results
    a, b = minmax(rand(-1000:1000, 2)...)
    x, y = BitSet(a:b), BitSet(a:1:b)
    @test x == y
    a, b = minmax(rand(-1000:1000, 2)...)
    @test union!(x, BitSet(a:b)) == union!(y, BitSet(a:1:b))
    x = BitSet(rand(-1000:1000, 500))
    y = copy(x)
    @test union!(x, BitSet(a:b)) == union!(y, BitSet(a:1:b))
    @test_throws ArgumentError BitSet(Int128(typemin(Int))-1:typemin(Int))
    @test_throws ArgumentError BitSet(typemax(Int):Int128(typemax(Int))+1)
    # union! with an empty range doesn't modify the BitSet
    @test union!(x, b:a) == y
end
