###############################################################################
#
#   Data type and parent object methods
#
###############################################################################

parent_type(::Type{FqMPolyRingElem}) = FqMPolyRing

elem_type(::Type{FqMPolyRing}) = FqMPolyRingElem

mpoly_type(::Type{FqFieldElem}) = FqMPolyRingElem

symbols(a::FqMPolyRing) = symbols(a.data)

parent(a::FqMPolyRingElem) = a.parent

number_of_variables(a::FqMPolyRing) = number_of_variables(a.data)

base_ring(a::FqMPolyRing) = a.base_ring

modulus(R::FqMPolyRing) = modulus(base_ring(R))

modulus(f::FqMPolyRingElem) = modulus(parent(f))

function internal_ordering(a::FqMPolyRing)
  return internal_ordering(a.data)
end

function gens(R::FqMPolyRing)
  return FqMPolyRingElem[FqMPolyRingElem(R, a)::FqMPolyRingElem for a in gens(R.data)]
end

function gen(R::FqMPolyRing, i::Int)
  return FqMPolyRingElem(R, gen(R.data, i))
end

function is_gen(a::FqMPolyRingElem)
  return is_gen(a.data)
end

function is_gen(a::FqMPolyRingElem, i::Int)
  return is_gen(a.data, i)
end

function deepcopy_internal(a::FqMPolyRingElem, dict::IdDict)
  return FqMPolyRingElem(parent(a), deepcopy_internal(a.data, dict))
end

function length(a::FqMPolyRingElem)
  return length(a.data)
end

function one(R::FqMPolyRing)
  return FqMPolyRingElem(R, one(R.data))
end

function zero(R::FqMPolyRing)
  return FqMPolyRingElem(R, zero(R.data))
end

function isone(a::FqMPolyRingElem)
  return isone(a.data)
end

function is_monomial(a::FqMPolyRingElem)
  return is_monomial(a.data)
end

function is_constant(a::FqMPolyRingElem)
  return is_constant(a.data)
end

function fit!(a::FqMPolyRingElem, n::Int)
  # needs to exist for the MPoly interface
  return nothing
end

###############################################################################
#
#   AbstractString I/O
#
###############################################################################

function expressify(a::FqMPolyRingElem, x = symbols(parent(a)); context = nothing)
  return expressify(a.data, x, context = context)
end

# AA has enable all show via expressify for all MPolys

################################################################################
#
#  Getting coefficients
#
################################################################################

function coeff(a::FqMPolyRingElem, i::Int)
  return _unchecked_coerce(base_ring(a), coeff(a.data, i))
end

function coeff!(b::FqFieldElem, a::FqMPolyRingElem, i::Int)
  # I don't know how to put the coefficient directly into b
  c = coeff(a, i)
  set!(b, c)
  return b
end

function coeff(a::FqMPolyRingElem, b::FqMPolyRingElem)
  return _unchecked_coerce(base_ring(a), coeff(a.data, b.data))
end

function trailing_coefficient(a::FqMPolyRingElem)
  return _unchecked_coerce(base_ring(a), trailing_coefficient(a.data))
end

###############################################################################
#
#   Basic manipulation
#
###############################################################################

function degree(a::FqMPolyRingElem, i::Int)
  return degree(a.data, i)
end

function degrees(a::FqMPolyRingElem)
  return degrees(a.data)
end

function total_degree(a::FqMPolyRingElem)
  return total_degree(a.data)
end

###############################################################################
#
#   Multivariable coefficient polynomials
#
###############################################################################

function coeff(a::FqMPolyRingElem, vars::Vector{Int}, exps::Vector{Int})
  return FqMPolyRingElem(parent(a), coeff(a.data, vars, exps))
end

###############################################################################
#
#   Basic arithmetic
#
###############################################################################

function -(a::FqMPolyRingElem)
  R = parent(a)
  @fq_default_mpoly_do_op(-, R, a)
end

function +(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  R = parent(a)
  @fq_default_mpoly_do_op(+, R, a, b)
end

function -(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  R = parent(a)
  @fq_default_mpoly_do_op(-, R, a, b)
end

function *(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  R = parent(a)
  @fq_default_mpoly_do_op(*, R, a, b)
end

###############################################################################
#
#   Ad hoc arithmetic
#
###############################################################################

function +(a::FqMPolyRingElem, b::IntegerUnion)
  return FqMPolyRingElem(parent(a), a.data + base_ring(a.data)(b))
end

function +(a::FqMPolyRingElem, b::FqFieldElem)
  parent(b) == base_ring(a) || error("Unable to coerce element")
  b1 = _unchecked_coerce(base_ring(a.data), b)
  return FqMPolyRingElem(parent(a), a.data + b1)
end

function +(b::Union{FqFieldElem, Integer}, a::FqMPolyRingElem)
  return a + b
end

function -(a::FqMPolyRingElem, b::IntegerUnion)
  return FqMPolyRingElem(parent(a), a.data - base_ring(a.data)(b))
end

function -(a::FqMPolyRingElem, b::FqFieldElem)
  parent(b) == base_ring(a) || error("Unable to coerce element")
  b1 = _unchecked_coerce(base_ring(a.data), b)
  return FqMPolyRingElem(parent(a), a.data - b1)
end

function -(b::IntegerUnion, a::FqMPolyRingElem)
  return FqMPolyRingElem(parent(a), base_ring(a.data)(b) - a.data)
end

function -(b::FqFieldElem, a::FqMPolyRingElem)
  parent(b) == base_ring(a) || error("Unable to coerce element")
  b1 = _unchecked_coerce(base_ring(a.data), b)
  return FqMPolyRingElem(parent(a), b1 - a.data)
end

function *(a::FqMPolyRingElem, b::IntegerUnion)
  return FqMPolyRingElem(parent(a), a.data * base_ring(a.data)(b))
end

function *(a::FqMPolyRingElem, b::FqFieldElem)
  parent(b) == base_ring(a) || error("Unable to coerce element")
  b1 = _unchecked_coerce(base_ring(a.data), b)
  return FqMPolyRingElem(parent(a), a.data * b1)
end

function *(b::Union{FqFieldElem, Integer}, a::FqMPolyRingElem)
  return a*b
end

###############################################################################
#
#   Powering
#
###############################################################################

function ^(a::FqMPolyRingElem, b::Integer)
  return FqMPolyRingElem(parent(a), a.data^b)
end

################################################################################
#
#   GCD
#
################################################################################

function gcd(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  return FqMPolyRingElem(parent(a), gcd(a.data, b.data))
end

function gcd_with_cofactors(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  (g, abar, bbar) = gcd_with_cofactors(a.data, b.data)
  R = parent(a)
  return (FqMPolyRingElem(R, g), FqMPolyRingElem(R, abar), FqMPolyRingElem(R, bbar))
end

################################################################################
#
#   Factorization and Square Root
#
################################################################################

function _convert_fac(a::FqMPolyRing, b::Fac)
  f = Fac{FqMPolyRingElem}()
  f.unit = FqMPolyRingElem(a, b.unit)
  for (p, e) in b
    f[FqMPolyRingElem(a, p)] = e
  end
  return f
end

function factor(a::FqMPolyRingElem)
  iszero(a) && throw(ArgumentError("Argument must be non-zero"))
  return _convert_fac(parent(a), factor(a.data))
end

function factor_squarefree(a::FqMPolyRingElem)
  iszero(a) && throw(ArgumentError("Argument must be non-zero"))
  return _convert_fac(parent(a), factor_squarefree(a.data))
end

function sqrt(a::FqMPolyRingElem; check::Bool=true)
  return FqMPolyRingElem(parent(a), sqrt(a.data, check = check))
end

function is_square(a::FqMPolyRingElem)
  return is_square(a.data)
end

function is_square_with_sqrt(a::FqMPolyRingElem)
  x, y = is_square_with_sqrt(a.data)
  return x, FqMPolyRingElem(parent(a), y)
end

###############################################################################
#
#   Comparison
#
###############################################################################

function ==(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  return a.data == b.data
end

function Base.isless(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  return isless(a.data, b.data)
end

###############################################################################
#
#   Ad hoc comparison
#
###############################################################################

function ==(a::FqMPolyRingElem, b::FqFieldElem)
  return a.data == _unchecked_coerce(base_ring(a.data), b)
end

function ==(b::FqFieldElem, a::FqMPolyRingElem)
  return a.data == _unchecked_coerce(base_ring(a.data), b)
end

function ==(a::FqMPolyRingElem, b::IntegerUnion)
  return a.data == base_ring(a.data)(b)
end

function ==(b::IntegerUnion, a::FqMPolyRingElem)
  return a.data == base_ring(a.data)(b)
end

###############################################################################
#
#   Divisibility
#
###############################################################################

function divides(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  x, y = divides(a.data, b.data)
  return x, FqMPolyRingElem(parent(a), y)
end

###############################################################################
#
#   Division with remainder
#
###############################################################################

function Base.div(a::(FqMPolyRingElem), b::(FqMPolyRingElem))
  check_parent(a, b)
  return FqMPolyRingElem(parent(a), div(a.data, b.data))
end

function Base.divrem(a::FqMPolyRingElem, b::FqMPolyRingElem)
  check_parent(a, b)
  x, y = divrem(a.data, b.data)
  return FqMPolyRingElem(parent(a), x), FqMPolyRingElem(parent(a), y)
end

function Base.divrem(a::FqMPolyRingElem, b::Vector{FqMPolyRingElem})
  for bi in b
    check_parent(a, bi)
  end
  ad = a.data
  bd = typeof(ad)[bi.data for bi in b]
  q, r = Base.divrem(ad, bd)
  return [FqMPolyRingElem(parent(a), qi) for qi in q],
  FqMPolyRingElem(parent(a), r)
end

###############################################################################
#
#   Exact division
#
###############################################################################

function divexact(a::(FqMPolyRingElem), b::(FqMPolyRingElem); check::Bool=true)
  check_parent(a, b)
  return FqMPolyRingElem(parent(a), divexact(a.data, b.data))
end

###############################################################################
#
#   Calculus
#
###############################################################################

function derivative(a::FqMPolyRingElem, i::Int)
  return FqMPolyRingElem(parent(a), derivative(a.data, i))
end

###############################################################################
#
#   Unsafe functions
#
###############################################################################

function zero!(a::FqMPolyRingElem)
  a.data = zero!(a.data)
  return a
end

function one!(a::FqMPolyRingElem)
  a.data = one!(a.data)
  return a
end

function neg!(z::FqMPolyRingElem, a::FqMPolyRingElem)
  z.data = neg!(z.data, a.data)
  return z
end

function add!(a::FqMPolyRingElem, b::FqMPolyRingElem, c::FqMPolyRingElem)
  a.data = add!(a.data, b.data, c.data)
  return a
end

function mul!(a::FqMPolyRingElem, b::FqMPolyRingElem, c::FqMPolyRingElem)
  a.data = mul!(a.data, b.data, c.data)
  return a
end

function setcoeff!(a::FqMPolyRingElem, n::Int, c::FqFieldElem)
  Rd = parent(a).data
  a.data = setcoeff!(a.data, n, _unchecked_coerce(base_ring(Rd), c))
  return a
end

function setcoeff!(a::FqMPolyRingElem, n::Vector{Int}, c)
  return setcoeff!(a, n, base_ring(a)(c)::FqFieldElem)
end

function setcoeff!(a::FqMPolyRingElem, n::Vector{Int}, c::FqFieldElem)
  Rd = parent(a).data
  a.data = setcoeff!(a.data, n, _unchecked_coerce(base_ring(Rd), c))
  return a
end

function combine_like_terms!(a::FqMPolyRingElem)
  a.data = combine_like_terms!(a.data)
  return a
end

###############################################################################
#
#   Manipulating terms and monomials
#
###############################################################################

function set_exponent_vector!(a::FqMPolyRingElem, n::Int, exps::Vector{T}) where T
  a.data = set_exponent_vector!(a.data, n, exps)
  return a
end

function exponent_vector(a::FqMPolyRingElem, i::Int)
  return exponent_vector(a.data, i)
end

function exponent_vector(::Type{Vector{S}}, a::FqMPolyRingElem, i::Int) where S
  v = [zero(S) for _ in 1:nvars(parent(a))]
  return exponent_vector!(v, a.data, i)
end

# Need to specify the type of v to avoid an ambiguity with AbstractAlgebra
function exponent_vector!(v::Vector{S}, a::FqMPolyRingElem, i::Int) where S
  return exponent_vector!(v, a.data, i)
end

function exponent_vector!(v, a::FqMPolyRingElem, i::Int)
  return exponent_vector!(v, a.data, i)
end

function exponent(a::FqMPolyRingElem, i::Int, j::Int)
  return exponent(a.data, i, j)
end

function coeff(a::FqMPolyRingElem, exps::Vector{T}) where T
  return _unchecked_coerce(base_ring(a), coeff(a.data, exps))
end

function sort_terms!(a::FqMPolyRingElem)
  sort_terms!(a.data)
  return a
end

function term(a::FqMPolyRingElem, i::Int)
  b = parent(a)()
  return term!(b, a, i)
end

function term!(b::FqMPolyRingElem, a::FqMPolyRingElem, i::Int)
  b.data = term!(b.data, a.data, i)
  return b
end

function monomial(a::FqMPolyRingElem, i::Int)
  b = parent(a)()
  return monomial!(b, a, i)
end

function monomial!(m::FqMPolyRingElem, a::FqMPolyRingElem, i::Int)
  m.data = monomial!(m.data, a.data, i)
  return m
end

###############################################################################
#
#   Promotion rules
#
###############################################################################

promote_rule(::Type{(FqMPolyRingElem)}, ::Type{V}) where {V <: Integer} = (FqMPolyRingElem)

promote_rule(::Type{(FqMPolyRingElem)}, ::Type{ZZRingElem}) = (FqMPolyRingElem)

promote_rule(::Type{(FqMPolyRingElem)}, ::Type{FqFieldElem}) = (FqMPolyRingElem)

###############################################################################
#
#   Parent object call overload
#
###############################################################################

function (R::FqMPolyRing)()
  return FqMPolyRingElem(R, R.data())
end

function (R::FqMPolyRing)(b::IntegerUnion)
  return FqMPolyRingElem(R, R.data(b))
end

function (R::FqMPolyRing)(b::FqFieldElem)
  parent(b) == base_ring(R) || error("Unable to coerce element")
  return FqMPolyRingElem(R, R.data(_unchecked_coerce(base_ring(R.data), b)))
end


function (R::FqMPolyRing)(a::FqMPolyRingElem)
  parent(a) == R || error("Unable to coerce polynomial")
  return a
end

function (R::FqMPolyRing)(a::Vector{FqFieldElem}, b::Vector{Vector{Int}})
  F = base_ring(R.data)
  ad = elem_type(F)[if parent(ai) != base_ring(R)
                      error("coefficient is in the wrong field")
                    else
                      _unchecked_coerce(F, ai)
                    end for ai in a]
  return FqMPolyRingElem(R, R.data(ad, b))
end

###############################################################################
#
#  Ad hoc exact division
#
###############################################################################

function divexact(a::FqMPolyRingElem, b::FqFieldElem; check::Bool=true)
  return a*inv(b)
end

function divexact(a::FqMPolyRingElem, b::IntegerUnion; check::Bool=true)
  return a*inv(base_ring(a)(b))
end
