
"""
    compute(::ComputeTaskQED_P, data::QEDParticleValue)

Return the particle as is and initialize the Value.
"""
function compute(::ComputeTaskQED_P, data::QEDParticleValue{P})::QEDParticleValue{P} where {P <: QEDParticle}
    # TODO do we actually need this for anything?
    return QEDParticleValue{P}(data.p, one(DiracMatrix))
end

"""
    compute(::ComputeTaskQED_U, data::QEDParticleValue)

Compute an outer edge. Return the particle value with the same particle and the value multiplied by an outer_edge factor.
"""
function compute(::ComputeTaskQED_U, data::QEDParticleValue{P})::QEDParticleValue{P} where {P <: QEDParticle}
    return QEDParticleValue{P}(
        data.p,
        base_state(particle(data.p), direction(data.p), momentum(data.p), spin_or_pol(data.p)), # will return a SLorentzVector{ComplexF64}, BiSpinor or AdjointBiSpinor
    )
end

"""
    compute(::ComputeTaskQED_V, data1::QEDParticleValue, data2::QEDParticleValue)

Compute a vertex. Preserve momentum and particle types (e + gamma->p etc.) to create resulting particle, multiply values together and times a vertex factor.
"""
function compute(
    ::ComputeTaskQED_V,
    data1::QEDParticleValue{P1},
    data2::QEDParticleValue{P2},
)::QEDParticleValue where {P1 <: QEDParticle, P2 <: QEDParticle}
    p3 = QED_preserve_momentum(data1.p, data2.p)
    P3 = interaction_result(P1, P2)
    # TODO: which of data1 and data2 is left and right might depend on their types?
    dataOut = QEDParticleValue{P3}(P3(p3), data1.v * ABC_vertex() * data2.v)
    return dataOut
end

"""
    compute(::ComputeTaskQED_S2, data1::QEDParticleValue, data2::QEDParticleValue)

Compute a final inner edge (2 input particles, no output particle).

For valid inputs, both input particles should have the same momenta at this point.

12 FLOP.
"""
function compute(
    ::ComputeTaskQED_S2,
    data1::ParticleValue{P1},
    data2::ParticleValue{P2},
)::ComplexF64 where {P1 <: QEDParticle, P2 <: QEDParticle}
    @assert issame(P1, propagation_result(P2))
    # TODO: assert that data1 and data2
    inner = QED_inner_edge(data1.p)
    return data1.v * inner * data2.v
end

"""
    compute(::ComputeTaskQED_S1, data::QEDParticleValue)

Compute inner edge (1 input particle, 1 output particle).

11 FLOP.
"""
function compute(::ComputeTaskQED_S1, data::QEDParticleValue{P})::QEDParticleValue where {P <: QEDParticle}
    # TODO invert P for result (incoming becomes outgoing, outgoing becomes incoming)
    newP = propagation_result(P)
    return QEDParticleValue{newP}(newP(data.p), data.v * QED_inner_edge(data.p))
end

"""
    compute(::ComputeTaskQED_Sum, data::Vector{Float64})

Compute a sum over the vector. Use an algorithm that accounts for accumulated errors in long sums with potentially large differences in magnitude of the summands.

Linearly many FLOP with growing data.
"""
function compute(::ComputeTaskQED_Sum, data::Vector{Float64})::Float64
    return sum_kbn(data)
end
