###############################################################################
#
# DO NOT EDIT THIS FILE MANUALLY!
#
# Instead, regenerate it by running `dev/regenerate_exercise.jl`
#
###############################################################################

# very generic testing: just define ConformanceTests.generate_element(R) to produce elements of R,
# then invoke one of these functions, as appropriate:
# - exercise_NCRing_interface(R)
# - exercise_Ring_interface(R)
# - exercise_Ring_interface_recursive(R)
# - exercise_Field_interface(R)
# - exercise_Field_interface_recursive(R)
#
# The "recursive" variants perform additional tests on algebraic
# structures derived from the original ring, by calling these helpers:
# - exercise_EuclideanRing_interface(R)
# - exercise_Poly_interface(R)
# - exercise_MPoly_interface(R)
# - exercise_MatSpace_interface(R)
# - exercise_MatRing_interface(R)

@nospecialize

function exercise_NCRing_interface(R::AbstractAlgebra.NCRing)

   T = elem_type(R)

   begin

      @assert T <: NCRingElem || T <: RingElement

      begin
         @assert elem_type(typeof(R)) == T
         @assert parent_type(T) == typeof(R)
         for i in 1:1
            a = generate_element(R)::T
            @assert parent(a) == R
         end
         @assert is_domain_type(T) isa Bool
         @assert is_exact_type(T) isa Bool

         # if the ring supports base_ring, verify it also supports base_ring_type and is consistent
         if applicable(base_ring, R)
           @assert base_ring_type(R) == typeof(base_ring(R))
           @assert base_ring_type(zero(R)) == typeof(base_ring(zero(R)))
           @assert base_ring_type(typeof(R)) == typeof(base_ring(R))
           @assert base_ring_type(T) == typeof(base_ring(zero(R)))
        end

         # some rings don't support characteristic and raise an exception (see issue #993)
         try ch = characteristic(R)
            @assert iszero(R(characteristic(R)))
            @assert iszero(characteristic(R) * one(R))
            @assert iszero(one(R) * characteristic(R))
         catch
         end
      end

      begin
         @assert R() isa T
         @assert R(true) isa T
         @assert R(false) isa T
         @assert R(0) isa T
         @assert R(1) isa T
         @assert R(-2) isa T
         @assert R(BigInt(0)) isa T
         @assert R(BigInt(1)) isa T
         @assert R(BigInt(-2)) isa T
         @assert R(BigInt(3)^100) isa T
         for i in 1:1
            a = generate_element(R)::T
            @assert R(a) isa T
         end
      end

      begin
         @assert iszero(R())       # R() is supposed to construct 0 ?
         @assert iszero(zero(R))
         @assert isone(one(R))
         @assert iszero(R(0))
         @assert isone(R(1))
         @assert isone(R(0)) || !is_unit(R(0))
         @assert is_unit(R(1))
         for i in 1:1
            a = generate_element(R)::T
            @assert hash(a) isa UInt
            A = deepcopy(a)
            @assert !ismutable(a) || a !== A
            @assert equality(a, A)
            @assert hash(a) == hash(A)
            @assert parent(a) === parent(A)
            @assert sprint(show, "text/plain", a) isa String
         end
         @assert sprint(show, "text/plain", R) isa String

         for i in 1:1
            a = generate_element(R)::T
            b = generate_element(R)::T
            c = generate_element(R)::T
            A = deepcopy(a)
            B = deepcopy(b)
            C = deepcopy(c)
            @assert equality(+(a), a)
            @assert equality(*(a), a)
            @assert equality(a^1, a)
            @assert equality(a^2, a*a)
            @assert equality(a^3, a*a*a)
            @assert equality(a^4, a*a*a*a)
            @assert equality((a + b) + c, a + (b + c))
            @assert equality(a + b, b + a)
            @assert equality(a - c, a + (-c))
            @assert equality(a + zero(R), a)
            @assert equality(a + (-a), zero(R))
            @assert equality((a*b)*c, a*(b*c))
            @assert equality(a*one(R), a)
            @assert equality(one(R)*a, a)
            @assert equality(a*(b + c), a*b + a*c)
            @assert equality((a + b)*c, a*c + b*c)
            @assert iszero(a*zero(R))
            @assert iszero(zero(R)*a)
            @assert A == a
            @assert B == b
            @assert C == c
         end
      end

      if is_exact_type(T)
        for S in adhoc_partner_rings(R)
          s0 = zero(S)
          r0 = zero(R)
          s1 = one(S)
          r1 = one(R)
          for i in 1:1
            s2 = generate_element(S)
            r2 = R(s2)
            x = generate_element(R)

            for (s,r) in ((s0, r0), (s1, r1), (s2, r2))
              @assert equality(r, s)
              @assert equality(s, r)

              @assert equality(x + s, x + r)
              @assert equality(s + x, r + x)

              @assert equality(x - s, x - r)
              @assert equality(s - x, r - x)

              @assert equality(x * s, x * r)
              @assert equality(s * x, r * x)
            end
          end
        end
      end

      if !(R isa AbstractAlgebra.Ring)
         begin
            for i in 1:1
               a = generate_element(R)::T
               b = generate_element(R)::T
               A = deepcopy(a)
               B = deepcopy(b)
               # documentation is not clear on divexact
               if is_domain_type(T)
                  @assert iszero(b) || equality(divexact_left(b*a, b), a)
                  @assert iszero(b) || equality(divexact_left(b*a, b, check = true), a)
                  @assert iszero(b) || equality(divexact_left(b*a, b, check = false), a)
                  @assert iszero(b) || equality(b \ (b*a), a)

                  @assert iszero(b) || equality(divexact_right(a*b, b), a)
                  @assert iszero(b) || equality(divexact_right(a*b, b, check = true), a)
                  @assert iszero(b) || equality(divexact_right(a*b, b, check = false), a)
                  @assert iszero(b) || equality((a*b) / b, a)
               else
                  try
                     t = divexact_left(b*a, b)
                     @assert equality(b*t, b*a)
                     t = divexact_left(b*a, b, check = true)
                     @assert equality(b*t, b*a)
                     t = divexact_left(b*a, b, check = false)
                     @assert equality(b*t, b*a)
                     t = b \ (b*a)
                     @assert equality(b*t, b*a)
                  catch
                  end
                  try
                     t = divexact_right(a*b, b)
                     @assert equality(t*b, a*b)
                     t = divexact_right(a*b, b, check = true)
                     @assert equality(t*b, a*b)
                     t = divexact_right(a*b, b, check = false)
                     @assert equality(t*b, a*b)
                     t = (a*b) / b
                     @assert equality(t*b, a*b)
                  catch
                  end
               end
               @assert A == a
               @assert B == b
            end
         end
      end

      begin
         for i in 1:1
            a = generate_element(R)::T
            b = generate_element(R)::T
            c = generate_element(R)::T

            exercise_mutating_op_like_zero(zero, zero!, a)
            exercise_mutating_op_like_zero(one, one!, a)

            exercise_mutating_op_like_neg(-, neg!, a)

            exercise_mutating_op_like_add(+, add!, a, b)
            exercise_mutating_op_like_add(-, sub!, a, b)
            exercise_mutating_op_like_add(*, mul!, a, b)

            exercise_mutating_op_like_addmul((a, b, c) -> a + b*c, addmul!, a, b, c)
            exercise_mutating_op_like_addmul((a, b, c) -> a - b*c, submul!, a, b, c)
         end
      end
   end

   return nothing
end


function exercise_Ring_interface(R::AbstractAlgebra.Ring)

   T = elem_type(R)

   begin

      @assert T <: RingElement

      exercise_NCRing_interface(R)

      begin
         # FIXME: we can't expect general rings to support inv, not even for the one
         # element, so don't test this
         #@assert isone(AbstractAlgebra.inv(one(R)))
         #exercise_mutating_op_like_neg(AbstractAlgebra.inv, inv!, one(R))
         #exercise_mutating_op_like_neg(AbstractAlgebra.inv, inv!, -one(R))
         for i in 1:1
            a = generate_element(R)::T
            b = generate_element(R)::T
            A = deepcopy(a)
            B = deepcopy(b)
            @assert a*b == b*a
            # documentation is not clear on divexact
            if is_domain_type(T)
               @assert iszero(b) || equality(divexact(b*a, b), a)
               @assert iszero(b) || equality(divexact(b*a, b, check = true), a)
               @assert iszero(b) || equality(divexact(b*a, b, check = false), a)
               if T isa RingElem
                  @assert iszero(b) || equality((b*a) / b, a)
               end
               iszero(b) || exercise_mutating_op_like_add(divexact, divexact!, b*a, b)
            else
               try
                  t = divexact(b*a, b)
                  @assert equality(t*b, a*b)
                  t = divexact(b*a, b, check = true)
                  @assert equality(t*b, a*b)
                  t = divexact(b*a, b, check = false)
                  @assert equality(t*b, a*b)
                  if T isa RingElem
                     t = (b*a) / b
                     @assert equality(t*b, a*b)
                  end
               catch
               end
            end
            try
               (f, h) = is_zero_divisor_with_annihilator(a)
               @assert parent(h) == R
               @assert f == is_zero_divisor(a)
               if f
                  @assert !is_zero(h)
                  @assert is_zero(a*h)
               end
            catch
            end
            @assert A == a
            @assert B == b
         end
      end
   end

   return nothing
end

function exercise_Field_interface(R::AbstractAlgebra.Field)

   T = elem_type(R)

   begin

      exercise_Ring_interface(R)

      @assert iszero(R(characteristic(R)))
      @assert iszero(characteristic(R) * one(R))
      @assert iszero(one(R) * characteristic(R))

      for i in 1:1
         a = generate_element(R)::T
         A = deepcopy(a)
         @assert is_unit(a) == !iszero(a)
         if !is_zero(a)
            @assert is_one(a * inv(a))
            @assert is_one(inv(a) * a)
            exercise_mutating_op_like_neg(inv, inv!, a)
         end
         @assert A == a
      end
   end

   return nothing
end

function exercise_EuclideanRing_interface(R::AbstractAlgebra.Ring)

   T = elem_type(R)

   is_exact_type(T) || return

   begin

      for i in 1:1
         f = generate_element(R)::T
         g = generate_element(R)::T
         m = generate_element(R)::T
         if iszero(m)
            m = one(R)
         end

         @assert (AbstractAlgebra.div(f, m), mod(f, m)) == AbstractAlgebra.divrem(f, m)
         @assert divides(mulmod(f, g, m) - mod(f*g, m), m)[1]

         fi = one(R)
         for i in 1:5
            fi *= f
            @assert divides(fi - powermod(f, i, m), m)[1]
            @assert divides(fi - mod(f^i, m), m)[1]
         end

         if is_unit(gcd(f, m))
            a = invmod(f, m)
            @assert divides(mulmod(a, f, m) - one(R), m)[1]
            @assert divides(powermod(f, -1, m) - a^1, m)[1]
            @assert divides(powermod(f, -2, m) - a^2, m)[1]
            @assert divides(powermod(f, -3, m) - a^3, m)[1]
         end

         @assert divides(f*m, m) == (true, f)
         (a, b) = divides(f*m + g, m)
         @assert !a || b*m == f*m + g

         #@exercise_throws Exception remove(f, zero(R))
         #@exercise_throws Exception valuation(f, zero(R))

         if !is_unit(m) && !iszero(f)
            n = rand(0:3)
            f *= m^n
            (v, q) = remove(f, m)
            @assert valuation(f, m) == v
            @assert v >= n
            @assert q*m^v == f
            @assert remove(q, m) == (0, q)
            @assert valuation(q, m) == 0
         end

         @assert !(iszero(f) && iszero(g)) || iszero(gcd(f, g))
         @assert equality_up_to_units(gcd(f, g)*lcm(f, g), f*g)

         g1 = gcd(f, gcd(g, m))
         g2 = gcd(gcd(f, g), m)
         g3 = gcd(f, g, m)
         g4 = gcd([f, g, m])
         @assert equality_up_to_units(g1, g2)
         @assert equality_up_to_units(g2, g3)
         @assert equality_up_to_units(g3, g4)

         l1 = lcm(f, lcm(g, m))
         l2 = lcm(lcm(f, g), m)
         l3 = lcm(f, g, m)
         l4 = lcm([f, g, m])
         @assert equality_up_to_units(l1, l2)
         @assert equality_up_to_units(l2, l3)
         @assert equality_up_to_units(l3, l4)

         (d, s, t) = gcdx(f, g)
         @assert d == gcd(f, g)
         @assert d == s*f + t*g
         @assert gcdinv(f, g) == (d, s)

         exercise_mutating_op_like_add(AbstractAlgebra.div, div!, f, m)
         exercise_mutating_op_like_add(mod, mod!, f, m)
         exercise_mutating_op_like_add(gcd, gcd!, f, m)
         exercise_mutating_op_like_add(lcm, lcm!, f, m)
         if !iszero(f*m)
           exercise_mutating_op_like_add(divides, divides!, f, f*m; only3arg = true)
           exercise_mutating_op_like_add(divides, divides!, f, m; only3arg = true)
         end
      end

   end

   return nothing
end


function exercise_Poly_interface(Rx::AbstractAlgebra.PolyRing)

   T = elem_type(Rx)

   begin

      exercise_Ring_interface(Rx)

      x = gen(Rx)
      R = base_ring(Rx)

      begin
         for i in 1:1
            a = generate_element(Rx)::T
            for b in coefficients(a)
               @assert Rx(b) isa T
            end
            @assert a == Rx(collect(coefficients(a)))

            B = MPolyBuildCtx(Rx)   # TODO rename to BuildCtx
            for (c, e) in zip(AbstractAlgebra.coefficients(a), AbstractAlgebra.exponent_vectors(a))
               push_term!(B, c, e)
            end
            @assert finish(B) == a
         end
         @assert Rx(Int[]) == zero(Rx)
         @assert Rx([0, 1, 2]) == x + 2*x^2
         @assert Rx([big(0), big(1), big(2)]) == x + 2*x^2
         @assert Rx(map(R, [0, 1, 2])) == x + 2*x^2
      end

      if R isa AbstractAlgebra.Field
         exercise_EuclideanRing_interface(Rx)
         begin
            for i in 1:1
               a = generate_element(Rx)
               b = generate_element(Rx)
               for j in 1:8
                  q = generate_element(Rx)
                  a, b = q*a + b, a
               end
               g, s, t = gcdx(a, b)
               @assert g == gcd(a, b)
               @assert g == s*a + t*b
               @assert (g, s) == gcdinv(a, b)
               if degree(a) < degree(b)
                  a, b = b, a
               end
               degree(a) > degree(b) >= 0 || continue
               (A, B, m11, m12, m21, m22, s) = hgcd(a, b)
               @assert degree(A) >= cld(degree(a), 2) > degree(B)
               @assert m11*A + m12*B == a
               @assert m21*A + m22*B == b
               @assert m11*m22 - m21*m12 == s
               @assert s^2 == 1
            end
         end
      end

      begin
         @assert var(Rx) isa Symbol
         @assert symbols(Rx) isa Vector{Symbol}
         @assert length(symbols(Rx)) == 1
         @assert is_gen(gen(Rx))
         @assert is_gen(x)
         @assert is_monic(x)
         @assert is_trivial(Rx) || !is_gen(x^2)
         for i in 1:1
            a = generate_element(Rx)
            @assert iszero(a) || degree(a) >= 0
            @assert equality(a, leading_coefficient(a)*x^max(0, degree(a)) + tail(a))
            @assert constant_coefficient(a) isa elem_type(R)
            @assert trailing_coefficient(a) isa elem_type(R)
            @assert is_monic(a) == isone(leading_coefficient(a))
         end
      end

      begin
        p = x^2 + 2*x + 3
        @assert reverse(p) !== p
        @assert reverse(p) == 3*x^2 + 2*x + 1
        @assert reverse(p, 2) == 3*x + 2

        p = x^2 + 2*x
        reverse!(p)
        @assert p == 2*x + 1

        p = x^2 + 2x
        reverse!(p, 2)
        @assert p == 2
      end

      begin
        p = x^2 + 2*x + 3
        @assert shift_left!(p, 1) == x^3 + 2*x^2 + 3*x
        @assert shift_left!(p, 3) == x^6 + 2*x^5 + 3*x^4

        p = x^2 + 2*x + 3
        @assert shift_right!(p, 1) == x + 2
        @assert shift_right!(p, 2) == zero(Rx)
      end
   end

   return nothing
end


function exercise_MPoly_interface(Rxy::AbstractAlgebra.MPolyRing)

   # for simplicity, these tests for now assume exactly two generators
   @assert ngens(Rxy) == 2

   T = elem_type(Rxy)

   begin

      exercise_Ring_interface(Rxy)

      begin
         @assert symbols(Rxy) isa Vector{Symbol}
         @assert length(symbols(Rxy)) == ngens(Rxy)
         @assert length(gens(Rxy)) == ngens(Rxy)
         @assert gens(Rxy) == [gen(Rxy, i) for i in 1:ngens(Rxy)]
         @assert all(is_gen, gens(Rxy)) || is_trivial(Rxy)
      end

      begin
         for i in 1:1
            a = generate_element(Rxy)::T
            for b in coefficients(a)
               @assert Rxy(b) isa T
            end

            # test MPolyBuildCtx
            B = MPolyBuildCtx(Rxy)
            for (c, e) in zip(AbstractAlgebra.coefficients(a), AbstractAlgebra.exponent_vectors(a))
               push_term!(B, c, e)
            end
            @assert finish(B) == a
         end
         x, y = gens(Rxy)
         f = 13*x^3*y^4 + 2*x - 7
         #@assert Rxy([2,-7,13], [[1,0],[0,0],[3,4]]) == f   # FIXME: interface spec does not say this is required?

         R = base_ring(Rxy)
         @assert Rxy(R.([2,-7,13]), [[1,0],[0,0],[3,4]]) == f
      end

      # skip trivial rings after this, it is not worth the bother
      is_trivial(Rxy) && return

      begin
         R = base_ring(Rxy)
         x, y = gens(Rxy)

         a = zero(Rxy)
         @assert !is_monomial(a)
         @assert !is_term(a)
         @assert is_constant(a)
         @assert !is_gen(a)
         @assert !is_unit(a)
         @assert is_nilpotent(a)
         @assert is_homogeneous(a)
         @assert length(a) == 0
         @assert total_degree(a) < 0
         @assert all(is_negative, degrees(a))

         a = one(Rxy)
         @assert is_monomial(a)
         @assert is_term(a)
         @assert is_constant(a)
         @assert !is_gen(a)
         @assert is_unit(a)
         @assert !is_nilpotent(a)
         @assert is_homogeneous(a)
         @assert length(a) == 1
         @assert total_degree(a) == 0
         @assert degrees(a) == [0, 0]

         a = x
         @assert is_monomial(a)
         @assert is_term(a)
         @assert !is_constant(a)
         @assert is_gen(a)
         @assert !is_unit(a)
         @assert !is_nilpotent(a)
         @assert is_homogeneous(a)
         @assert length(a) == 1
         @assert total_degree(a) == 1
         @assert degrees(a) == [1, 0]

         a = x^2
         @assert is_monomial(a)
         @assert is_term(a)
         @assert !is_constant(a)
         @assert !is_gen(a)
         @assert !is_unit(a)
         @assert !is_nilpotent(a)
         @assert is_homogeneous(a)
         @assert length(a) == 1
         @assert total_degree(a) == 2
         @assert degrees(a) == [2, 0]

         if !is_zero(R(2))
            a = 2*x
            @assert !is_monomial(a)
            @assert is_term(a)
            @assert !is_constant(a)
            @assert !is_gen(a)
            @assert !is_unit(a)
            @assert is_nilpotent(a) == is_nilpotent(R(2))
            @assert length(a) == 1
            @assert total_degree(a) == 1
            @assert degrees(a) == [1, 0]
         end

         a = x^3 + y^4
         @assert !is_monomial(a)
         @assert !is_term(a)
         @assert !is_constant(a)
         @assert !is_gen(a)
         @assert !is_unit(a)
         @assert !is_nilpotent(a)
         @assert !is_homogeneous(a)
         @assert length(a) == 2
         @assert total_degree(a) == 4
         @assert degrees(a) == [3, 4]

         for i in 1:1
            a = generate_element(Rxy)
            iszero(a) && continue
            @assert length(a) >= 0
            @assert sum(degrees(a)) >= total_degree(a)
         end

      end

      # TODO: add more tests, covering everything described in the manual, see
      # https://nemocas.github.io/AbstractAlgebra.jl/dev/mpoly_interface/
      # https://nemocas.github.io/AbstractAlgebra.jl/dev/mpolynomial/
   end

   return nothing
end


function exercise_MatSpace_interface(S::MatSpace)

   ST = elem_type(S)
   R = base_ring(S)
   T = elem_type(R)

   @assert base_ring_type(S) == typeof(R)
   @assert parent_type(ST) == typeof(S)
   @assert dense_matrix_type(R) == ST

   begin

      begin
         for k in 1:1
            a = generate_element(S)::ST
            @assert nrows(a) == nrows(S)
            @assert ncols(a) == ncols(S)
            @assert a == S(T[a[i, j] for i in 1:nrows(a), j in 1:ncols(a)])
            @assert a == S(T[a[i, j] for i in 1:nrows(a) for j in 1:ncols(a)])
            @assert a == matrix(R, T[a[i, j] for i in 1:nrows(a), j in 1:ncols(a)])
            @assert a == matrix(R, nrows(S), ncols(S),
                              T[a[i, j] for i in 1:nrows(a) for j in 1:ncols(a)])

            b = similar(a)
            @assert b isa ST
            @assert nrows(b) == nrows(S)
            @assert ncols(b) == ncols(S)

            b = similar(a, nrows(S)+1, ncols(S)+1)
            @assert b isa ST
            @assert nrows(b) == nrows(S)+1
            @assert ncols(b) == ncols(S)+1

            b = similar(a, R)
            @assert b isa MatElem
            #@assert b isa ST   # undefined
            @assert nrows(b) == nrows(S)
            @assert ncols(b) == ncols(S)

            b = similar(a, R, nrows(S)+1, ncols(S)+1)
            @assert b isa MatElem
            #@assert b isa ST   # undefined
            @assert nrows(b) == nrows(S)+1
            @assert ncols(b) == ncols(S)+1

         end
         @assert iszero(zero_matrix(R, nrows(S), ncols(S)))
      end

      begin
         M = matrix(R, 3, 3, BigInt[1, 2, 3, 2, 3, 4, 3, 4, 5])
         N1 = @view M[1:2, :]
         N2 = @view M[:, 1:2]
         @assert N1*N2 == matrix(R, 2, 2, BigInt[14, 20, 20, 29])
      end

      begin
         for k in 1:1
            a = generate_element(S)::ST
            A = deepcopy(a)
            @assert A isa ST

            b = zero_matrix(R, nrows(a), ncols(a))
            @assert b isa ST
            for i in 1:nrows(a), j in 1:ncols(a)
               b[i, j] = a[i, j]
            end
            @assert b == a

            t = transpose(a)
            @assert t isa ST
            @assert base_ring(t) == base_ring(a)
            @assert nrows(t) == ncols(S)
            @assert ncols(t) == nrows(S)
            @assert transpose(t) == a
            @assert a == A

            if nrows(S) == ncols(S)
              # in-place transpose! only supported for square matrices
              t = transpose!(deepcopy(a))
              @assert t isa ST
              @assert base_ring(t) == base_ring(a)
              @assert t == transpose(a)
            end

            z = zero_matrix(R, ncols(a), nrows(a))
            t = transpose!(z, a)
            @assert t isa ST
            @assert base_ring(t) == base_ring(a)
            @assert t == transpose(a)
            @assert a == A
         end
      end

      begin
          a = matrix(R, [1 2 ; 3 4])
          b = swap_rows(a, 1, 2)
          @assert b == matrix(R, [3 4 ; 1 2])
          @assert a == matrix(R, [1 2 ; 3 4])

          a = matrix(R, [1 2 ; 3 4])
          b = swap_cols(a, 1, 2)
          @assert b == matrix(R, [2 1 ; 4 3])
          @assert a == matrix(R, [1 2 ; 3 4])

          # TODO: reverse_rows, reverse_cols
          # TODO: add_column, add_row
          # TODO: multiply_column, multiply_row
          # TODO: ! variants (such as `swap_cols!` etc.) of all of the above
      end

   end

   return nothing
end

function exercise_MatRing_interface(S::MatRing)

   ST = elem_type(S)
   R = base_ring(S)
   T = elem_type(R)

   @assert nrows(S) == ncols(S)

   begin

      exercise_NCRing_interface(S)

      begin
         for k in 1:1
            a = generate_element(S)::ST
            @assert nrows(a) == nrows(S)
            @assert ncols(a) == ncols(S)
            @assert a == S(T[a[i, j] for i in 1:nrows(a), j in 1:ncols(a)])
            @assert a == S(T[a[i, j] for i in 1:nrows(a) for j in 1:ncols(a)])
         end
      end

      begin
         for k in 1:1
            a = generate_element(S)::ST
            A = deepcopy(a)
            b = zero(S)
            for i in 1:nrows(a), j in 1:ncols(a)
               b[i, j] = a[i, j]
            end
            @assert b == a
            @assert transpose(transpose(a)) == a
            @assert a == A
         end
      end

      begin
         for k in 1:1
            a = generate_element(S)::ST
            b = generate_element(S)::ST
            A = deepcopy(a)
            B = deepcopy(b)
            @assert det(a*b) == det(a)*det(b)
            @assert a == A
            @assert b == B
         end
      end
   end

   return nothing
end

function exercise_Ring_interface_recursive(R::AbstractAlgebra.Ring)
   exercise_Ring_interface(R)
   Rx, _ = polynomial_ring(R, :x)
   exercise_Poly_interface(Rx)
   Rxy, _ = polynomial_ring(R, [:x, :y])
   exercise_MPoly_interface(Rxy)
   for d in 0:3
     S = matrix_ring(R, d)
     exercise_MatRing_interface(S)
   end
   for (r,c) in ( (0,0), (1,0), (0,2), (2,2), (2,3), (3,2) )
     S = matrix_space(R, r,c)
     exercise_MatSpace_interface(S)
   end
end

function exercise_Field_interface_recursive(R::AbstractAlgebra.Field)
   exercise_Ring_interface_recursive(R)
   exercise_Field_interface(R)
end

@specialize

############
# The following functions should not expect that their input is a `NCRingElem` or similar.
# They should be usable in more general types, that don't even have a `parent/elem` correspondence

@nospecialize

function exercise_mutating_op_like_zero(f::Function, f!::Function, A)
  a = deepcopy(A)
  a = f!(a)
  @assert equality(a, f(A))
end

function exercise_mutating_op_like_neg(f::Function, f!::Function, A)
  # initialize storage var with different values to check that its value is not used
  for z in [zero(A), deepcopy(A)]
     a = deepcopy(A)
     z = f!(z, a)
     @assert equality(z, f(A))
     @assert a == A
  end

  a = deepcopy(A)
  a = f!(a)
  @assert equality(a, f(A))
end

function exercise_mutating_op_like_add(f::Function, f!::Function, A, B, T = Any; only3arg::Bool = false)
  # only3arg = don't test f!(a, b)
  @req A isa T || B isa T "Invalid argument types"

  # initialize storage var with different values to check that its value is not used
  storage_values = T[]
  if A isa T
     push!(storage_values, zero(A))
     push!(storage_values, deepcopy(A))
  end
  if B isa T
     push!(storage_values, zero(B))
     push!(storage_values, deepcopy(B))
  end
  for z in storage_values
     a = deepcopy(A)
     b = deepcopy(B)
     z = f!(z, a, b)
     @assert equality(z, f(A, B))
     @assert a == A
     @assert b == B
  end

  if A isa T
     a = deepcopy(A)
     b = deepcopy(B)
     a = f!(a, a, b)
     @assert equality(a, f(A, B))
     @assert b == B

     if !only3arg
       a = deepcopy(A)
       b = deepcopy(B)
       a = f!(a, b)
       @assert equality(a, f(A, B))
       @assert b == B
     end
  end

  if B isa T
     a = deepcopy(A)
     b = deepcopy(B)
     b = f!(b, a, b)
     @assert equality(b, f(A, B))
     @assert a == A
  end

  if A isa T && B isa T
     # `f(B, B)` may fail if `!(A isa T)`, since we call it with different arguments than the intended `f(A, B)` (same for f!)
     a = deepcopy(A)
     b = deepcopy(B)
     a = f!(a, b, b)
     @assert equality(a, f(B, B))
     @assert b == B

     b = deepcopy(B)
     b = f!(b, b, b)
     @assert equality(b, f(B, B))

     if !only3arg
       b = deepcopy(B)
       b = f!(b, b)
       @assert equality(b, f(B, B))
     end
  end
end

function exercise_mutating_op_like_addmul(f::Function, f!_::Function, Z, A, B, T = Any)
  @req Z isa T "Invalid argument types"
  @req A isa T || B isa T "Invalid argument types"

  f!(z, a, b, ::Nothing) = f!_(z, a, b)
  f!(z, a, b, t) = f!_(z, a, b, t)

  # initialize storage var with different values to check that its value is not used
  # and `nothing` for the three-arg dispatch
  storage_values = Union{T,Nothing}[nothing]
  if A isa T
     push!(storage_values, zero(A))
     push!(storage_values, deepcopy(A))
  end
  if B isa T
     push!(storage_values, zero(B))
     push!(storage_values, deepcopy(B))
  end
  for t in storage_values
     z = deepcopy(Z)
     a = deepcopy(A)
     b = deepcopy(B)
     z = f!(z, a, b, t)
     @assert equality(z, f(Z, A, B))
     @assert a == A
     @assert b == B

     if A isa T
        a = deepcopy(A)
        b = deepcopy(B)
        a = f!(a, a, b, t)
        @assert equality(a, f(A, A, B))
        @assert b == B
     end

     if B isa T
        a = deepcopy(A)
        b = deepcopy(B)
        b = f!(b, a, b, t)
        @assert equality(b, f(B, A, B))
        @assert a == A
     end

     if A isa T && B isa T
        # `f(B, B)` may fail if `!(A isa T)`, since we call it with different arguments than the intended `f(A, B)` (same for f!)
        a = deepcopy(A)
        b = deepcopy(B)
        a = f!(a, b, b, t)
        @assert equality(a, f(A, B, B))
        @assert b == B

        b = deepcopy(B)
        b = f!(b, b, b, t)
        @assert equality(b, f(B, B, B))
     end
  end
end

@specialize
