###############################################################################
#
#   Default methods for ideals
#
###############################################################################

###############################################################################
#
#   Type and parent functions
#
###############################################################################

# fundamental interface
@doc raw"""
    ideal_type(R)
    ideal_type(::Type{T}) where T

Return the type of ideals over the given ring `R`, respectively over rings of type `T`.
"""
ideal_type(x) = ideal_type(typeof(x))
ideal_type(T::DataType) = throw(MethodError(ideal_type, (T,)))

base_ring_type(::Type{<:IdealSet{T}}) where {T} = parent_type(T)

elem_type(::Type{<:IdealSet{T}}) where {T} = ideal_type(parent_type(T))

###############################################################################
#
#   Ideal constructors
#
###############################################################################

@doc raw"""
    ideal(R::Ring, elms::AbstractVector{<:RingElement})
    ideal(R::Ring, elms...)
    ideal(elms::AbstractVector{<:RingElement})
    ideal(elms...)

Return the `R`-ideal generated by `elms`.

If `R` is omitted then `elms` must be non-empty, otherwise an error is raised.
"""
ideal

# All constructors ultimately delegate to a method
#   ideal(R::T, xs::Vector{U}) where T <: NCRing
# and U === elem_type(T)

# the following helper enables things like `ideal(R, [])` or `ideal(R, [1])`
# the type check ensures we don't run into an infinite recursion
function ideal(R::NCRing, xs::AbstractVector{T}; kw...) where T<:NCRingElement
  xs isa Vector{elem_type(R)} && error("ideals unsupported for ring $R")
  return ideal(R, elem_type(R)[R(x) for x in xs]; kw...)
end

function ideal(R::NCRing, x...; kw...)
  return ideal(R, elem_type(R)[R(z) for z in x]; kw...)
end

function ideal(x::T, y::T...; kw...) where T<:NCRingElement
  return ideal(parent(x), x, y...; kw...)
end

function ideal(xs::AbstractVector{T}; kw...) where T<:NCRingElement
  @req !is_empty(xs) "Empty collection, cannot determine parent ring. Try ideal(ring, xs) instead of ideal(xs)"
  return ideal(parent(xs[1]), xs; kw...)
end

###############################################################################
#
#   Basic predicates
#
###############################################################################

@doc raw"""
    iszero(I::Ideal)

Check if the ideal `I` is the zero ideal.
"""
iszero(I::Ideal) = all(iszero, gens(I))

@doc raw"""
    Base.issubset(I::T, J::T) where {T <: Ideal}

Return `true` if the ideal `I` is a subset of the ideal `J`.
An exception is thrown if the ideals are not defined over the same base ring.
"""
function Base.issubset(I::T, J::T) where {T <: Ideal}
  I === J && return true
  check_base_ring(I, J)
  return all(in(J), gens(I))
end

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

function Base.:(==)(I::T, J::T) where {T <: Ideal}
  return is_subset(I, J) && is_subset(J, I)
end

function Base.:hash(I::T, h::UInt) where {T <: Ideal}
  h = hash(base_ring(I), h)
  return h
end

###############################################################################
#
#   Binary operations
#
###############################################################################

function Base.:+(I::T, J::T) where {T <: Ideal}
  check_base_ring(I, J)
  return ideal(base_ring(I), vcat(gens(I), gens(J)))
end

# The following method for I*J is only applicable to commutative ideals
function Base.:*(I::T, J::T) where {T <: Ideal{<:RingElement}}
  check_base_ring(I, J)
  return ideal(base_ring(I), [x*y for x in gens(I) for y in gens(J)])
end

function Base.:^(I::Ideal, k::Integer)
  k >= 0 || error("exponent must be non-negative")
  k == 1 && return I

  # exponentiate by square-and-multiply
  R = base_ring(I)
  J = ideal(R, one(R))
  while k > 0
    while iseven(k)
      I *= I
      k >>= 1
    end
    J *= I
    k -= 1
  end

  return J
end

###############################################################################
#
#   Ad hoc binary operations
#
###############################################################################

function *(R::Ring, x::RingElement)
  return ideal(R, x)
end

function *(x::RingElement, R::Ring)
  return ideal(R, x)
end

function *(I::Ideal{T}, p::T) where T <: RingElement
  R = base_ring(I)
  iszero(p) && return ideal(R, T[])
  return ideal(R, [v*p for v in gens(I)])
end

function *(p::T, I::Ideal{T}) where T <: RingElement
  return I*p
end

function *(I::Ideal{T}, p::RingElement) where T <: RingElement
  R = base_ring(I)
  iszero(p*one(R)) && return ideal(R, T[])
  return ideal(R, [v*p for v in gens(I)])
end

function *(p::RingElement, I::Ideal{<:RingElement})
  return I*p
end
