using Test, Primes, Groebner, AbstractAlgebra

@testset "regression, generic alignment" begin
    @test Groebner.align_up(13, 20) == 20
    @test Groebner.align_up(21, 20) == 40
    @test Groebner.align_up(29, 20) == 40

    @test Groebner.align_down(13, 20) == 0
    @test Groebner.align_down(21, 20) == 20
    @test Groebner.align_down(40, 20) == 40

    for tasks in 1:16
        composite = 4
        batch = composite * tasks
        for primes_used in 1:1000
            batch = Groebner.get_next_batch(primes_used, batch, 0.1, composite, tasks)
            @test batch % (composite * tasks) == 0
        end
    end
end

@testset "regression, SI.jl normalform" begin
    R, (x, y, z) = polynomial_ring(GF(2^31 - 1), ["x", "y", "z"])

    @test Groebner.normalform([x], R(0)) == R(0)
    @test Groebner.normalform([x], R(1)) == R(1)
    @test Groebner.normalform([x], [R(0)]) == [R(0)]
    @test Groebner.normalform([x], [R(1)]) == [R(1)]
    @test Groebner.normalform([x], [R(0), R(1), R(0)]) == [R(0), R(1), R(0)]
end

@testset "regression, ordering of empty" begin
    R, (x, y) = polynomial_ring(GF(2^31 - 1), ["x", "y"], internal_ordering=:lex)

    ord = Groebner.DegRevLex()
    gb1 = Groebner.groebner([x, y], ordering=ord)
    gb2 = Groebner.groebner([R(0)], ordering=ord)
    @test parent(first(gb1)) == R
    @test parent(first(gb2)) == R
    nf1 = Groebner.normalform([x, y], x, ordering=ord)
    nf2 = Groebner.normalform([x, y], R(0), ordering=ord)
    @test parent(nf1) == R
    @test parent(nf2) == R
end

@testset "regression, SI.jl cmp" begin
    # this may crash if the comparator is invalid
    function bug(gens::Vector{Int}, exps::Vector{Vector{T}}) where {T}
        inds = collect(1:length(gens))
        cmp  = (x, y) -> Groebner.monom_isless(exps[gens[x]], exps[gens[y]], Groebner.DegRevLex())
        sort!(inds, lt=cmp)
        @test true
    end

    gens = [
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        3,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        3,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        2,
        3,
        2,
        2,
        2,
        2,
        2
    ]

    exps = Groebner.ExponentVector{UInt64}[[0x0000, 0x0000], [0x0002, 0x0002], [0x0002, 0x0002]]

    bug(gens, exps)
end

@testset "regression, column order in normalform" begin
    P, (x1, x2) = polynomial_ring(GF(1119649), ["x1", "x2"])

    gb = [x1 + 1]
    f = x1 + x2^2
    f_nf = Groebner.normalform(gb, f, ordering=Groebner.DegRevLex())
    residual = Groebner.normalform(gb, f - f_nf, ordering=Groebner.DegRevLex())

    @test iszero(residual)
end

@testset "regression, tracing invariants" begin
    R, (x1, x2, x3, x4, x5, x6, x7, _Z) =
        polynomial_ring(ZZ, [:x1, :x2, :x3, :x4, :x5, :x6, :x7, :_Z], internal_ordering=:degrevlex)
    sys_z_t = [
        2 * x1^2 - 2 * x2^2 + 2 * x3^2 - 2 * x4^2 + 2 * x5^2 - 2 * x6^2 + 2 * x7^2 - 1
        2 * x1^3 - 2 * x2^3 + 2 * x3^3 - 2 * x4^3 + 2 * x5^3 - 2 * x6^3 + 2 * x7^3 - 1
        2 * x1^4 - 2 * x2^4 + 2 * x3^4 - 2 * x4^4 + 2 * x5^4 - 2 * x6^4 + 2 * x7^4 - 1
        2 * x1^5 - 2 * x2^5 + 2 * x3^5 - 2 * x4^5 + 2 * x5^5 - 2 * x6^5 + 2 * x7^5 - 1
        2 * x1^6 - 2 * x2^6 + 2 * x3^6 - 2 * x4^6 + 2 * x5^6 - 2 * x6^6 + 2 * x7^6 - 1
        2 * x1^7 - 2 * x2^7 + 2 * x3^7 - 2 * x4^7 + 2 * x5^7 - 2 * x6^7 + 2 * x7^7 - 1
        2 * x1^8 - 2 * x2^8 + 2 * x3^8 - 2 * x4^8 + 2 * x5^8 - 2 * x6^8 + 2 * x7^8 - 1
        -35 * x1 + 43 * x2 + 63 * x3 - 80 * x4 - 56 * x5 + 73 * x6 - 73 * x7 + _Z
    ]
    pr = 2^28
    bad = 268419493
    sys_zp0 = map(
        f -> AbstractAlgebra.change_base_ring(AbstractAlgebra.GF(Primes.prevprime(pr - 1)), f),
        sys_z_t
    )
    sys_zp2 = map(f -> AbstractAlgebra.change_base_ring(AbstractAlgebra.GF(bad), f), sys_z_t)
    trace, _ = Groebner.groebner_learn(sys_zp0, ordering=Groebner.DegRevLex())
    flag, gb = Groebner.groebner_apply!(trace, sys_zp2)
    @test true
end

@testset "regression, non-invertible composite pivot" begin
    R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"])

    # Using a batch of 4 primes, some pivot vanishes modulo the first prime,
    # but does not vanish modulo the other three primes in the batch.
    p = BigInt(Primes.prevprime(Groebner.FIRST_LUCKY_PRIME_CANDIDATE - 1))
    system = [x^2 + p * y + 1, x * y + z + 1]

    gb = Groebner.groebner(system; homogenize=:no, tasks=1, _composite=4)
    expected = [
        y^3 + 1 // p * y^2 + 1 // p * z^2 + 2 // p * z + 1 // p,
        x * z + x - p * y^2 - y,
        x * y + z + 1,
        x^2 + p * y + 1
    ]

    @test gb == expected
end

# https://github.com/sumiya11/Groebner.jl/issues/175
@testset "regression, basis" begin
    R, (x1, x2) = polynomial_ring(QQ, ["x1", "x2"], internal_ordering=:deglex)
    set = [613 * x1^2 * x2^3 + 1413 * x1 * x2^2, 428 * x1^3 * x2 + 529 * x1 * x2^2]
    gb = groebner(set)
    @test isgroebner(gb)
end

@testset "regression, dimension with duplicate generators" begin
    R, x = polynomial_ring(QQ, ["x$i" for i in 1:5])
    system = [x[1]^2 - 1, x[1]^2 - 1, x[2]^2 - 2, x[2]^2 - 2]

    @test dimension(system) == 3
end

# https://github.com/sumiya11/Groebner.jl/issues/219
@testset "regression, rational change matrix reconstruction" begin
    R, (x,) = polynomial_ring(QQ, ["x"])
    coefficient = big(10)^10 + 1
    system = [coefficient * x + 1, x]

    basis, matrix = groebner_with_change_matrix(system; ordering=DegRevLex())

    @test matrix * system == basis
end
