"""
    FockSpace <: HilbertSpace

[`HilbertSpace`](@ref) defining a Fock space for bosonic operators.
See also: [`Destroy`](@ref), [`Create`](@ref)
"""
struct FockSpace{S} <: ConcreteHilbertSpace
    name::S
end
Base.:(==)(h1::T,h2::T) where T<:FockSpace = (h1.name==h2.name)

"""
    Destroy <: QSym

Bosonic operator on a [`FockSpace`](@ref) representing the quantum harmonic
oscillator annihilation operator.
"""
struct Destroy{H<:HilbertSpace,S,A,M} <: QSym
    hilbert::H
    name::S
    aon::A
    metadata::M
    function Destroy{H,S,A,M}(hilbert::H, name::S, aon::A, metadata::M) where {H,S,A,M}
        @assert has_hilbert(FockSpace,hilbert,aon)
        new(hilbert,name,aon,metadata)
    end
end

"""
    Create <: QSym

Bosonic operator on a [`FockSpace`](@ref) representing the quantum harmonic
oscillator creation operator.
"""
struct Create{H<:HilbertSpace,S,A,M} <: QSym
    hilbert::H
    name::S
    aon::A
    metadata::M
    function Create{H,S,A,M}(hilbert::H, name::S, aon::A, metadata::M) where {H,S,A,M}
        @assert has_hilbert(FockSpace,hilbert,aon)
        new(hilbert,name,aon,metadata)
    end
end

for T in (:Create, :Destroy)
    @eval Base.isequal(a::$T, b::$T) = isequal(a.hilbert, b.hilbert) && isequal(a.name, b.name) && isequal(a.aon, b.aon)
end

for f in [:Destroy,:Create]
    @eval $(f)(hilbert::H, name::S, aon::A; metadata::M=NO_METADATA) where {H,S,A,M} = $(f){H,S,A,M}(hilbert,name,aon,metadata)
    @eval $(f)(hilbert::FockSpace, name; metadata=NO_METADATA) = $(f)(hilbert,name,1; metadata)
    @eval function $(f)(hilbert::H, name::S, aon::A; metadata::M=NO_METADATA) where {H<:ProductSpace,S,A<:Int,M}
        if hilbert.spaces[aon] isa ClusterSpace
            hilbert.spaces[get_i(aon)].op_name[] = name
            op = $(f)(hilbert.spaces[aon].original_space, name; metadata)
            return _cluster(hilbert, op, aon) #todo OK?
        else
            return $(f){H,S,A,M}(hilbert,name,aon,metadata)
        end
    end
    @eval function $(f)(hilbert::ProductSpace, name; metadata=NO_METADATA)
        i = findall(x->isa(x,FockSpace) || isa(x,ClusterSpace{<:FockSpace}),hilbert.spaces)
        if length(i)==1
            return $(f)(hilbert, name, i[1]; metadata)
        else
            isempty(i) && error("Can only create $($(f)) on FockSpace! Not included in $(hilbert)")
            length(i)>1 && error("More than one FockSpace in $(hilbert)! Specify on which Hilbert space $($(f)) should be created with $($(f))(hilbert,name,i)!")
        end
    end
    @eval function Base.hash(op::T, h::UInt) where T<:($(f))
        hash($(f), hash(op.hilbert, hash(op.name, hash(op.aon, h))))
    end
end

Base.adjoint(op::Destroy) = Create(op.hilbert,op.name,acts_on(op); op.metadata)
Base.adjoint(op::Create) = Destroy(op.hilbert,op.name,acts_on(op); op.metadata)

# Commutation relation in simplification
function *(a::Destroy,b::Create)
    check_hilbert(a,b)
    aon_a = acts_on(a)
    aon_b = acts_on(b)
    if aon_a == aon_b
        return b*a + 1
    elseif aon_a < aon_b
        return QMul(1, [a,b])
    else
        return QMul(1, [b,a])
    end
end
ismergeable(::Destroy,::Create) = true
