# Karp, R. M.
# A characterization of the minimum cycle mean in a digraph
# Discrete Mathematics, 1978, 23, 309 - 311
function _karp_minimum_cycle_mean(
    g::AbstractGraph, distmx::AbstractMatrix{T}, component::Vector{U}
) where {T<:Number} where {U<:Integer}
    v2j = Dict{U,Int}()
    for (j, v) in enumerate(component)
        v2j[v] = j
    end
    n = length(component)
    F = fill(Inf, n + 1, n)
    F[1, 1] = 0.0
    for i in 2:(n + 1)
        for (j, v) in enumerate(component)
            for u in inneighbors(g, v)
                k = get(v2j, u, 0)
                if !iszero(k)
                    F[i, j] = min(F[i, j], F[i - 1, k] + distmx[u, v])
                end
            end
        end
    end

    # Extracting the cycle of minimal mean is not explained in Karp's paper.
    # Let
    # V* = argmin_v max_k (F_n(v) - F_k(v)) / (n - k)
    # Intuitively, one would think that it can be found by walking backward
    # using the F matrix starting from a vertex v ∈ V*.
    # However, it may not work as shown by the example in the tests.
    #
    # Fortunately, one can show that it works if we pick the v ∈ argmin_{v ∈ V*} F_n(v)
    # Indeed, suppose we walk backward and find v_0, ..., v_n=v.
    # Since there are n+1 nodes, there must be a cycle.
    # If the cycle is not of minimum cycle mean, then if we remove this cycle
    # and appends nodes of one of the cycle of minimum cycle mean containing v such that
    # the length is again n+1, we get a new walk of length n+1 ending at a node v' of the cycle.
    # Moreover, we know that v' ∈ V* and F_n(v') < F_n(v). Therefore if v minimizes F_n(v) among the nodes of V*, this cannot happen.

    # Find jbest ∈ V*
    λmin = Inf
    jbest = 0

    for j in 1:n
        λ = maximum(map(i -> (F[n + 1, j] - F[i, j]) / (n + 1 - i), 1:n))
        if λ < λmin || (isfinite(λ) && λ ≈ λmin && F[n + 1, j] < F[n + 1, jbest])
            λmin = λ
            jbest = j
        end
    end

    iszero(jbest) && return U[], Inf

    # Backward walk from jbest
    walk = zeros(Int, n + 1)
    walk[n + 1] = jbest
    for i in n:-1:1
        v = component[walk[i + 1]]
        dmin = Inf
        for u in inneighbors(g, v)
            j = get(v2j, u, 0)
            if !iszero(j)
                dcur = F[i, j] + distmx[u, v]
                if dcur < dmin
                    walk[i] = j
                    dmin = dcur
                end
            end
        end
    end

    # Extract cycle in the walk
    invmap = zeros(Int, n)
    I = 1:0
    for i in (n + 1):-1:1
        if iszero(invmap[walk[i]])
            invmap[walk[i]] = i
        else
            I = (i + 1):invmap[walk[i]]
            break
        end
    end
    return component[walk[I]], λmin
end

"""
    karp_minimum_cycle_mean(g[, distmx])

Return minimum cycle mean of the directed graph `g` with optional edge weights contained in `distmx`.

### References
- [Karp](http://dx.doi.org/10.1016/0012-365X(78)90011-0).
"""
function karp_minimum_cycle_mean end
@traitfn function karp_minimum_cycle_mean(
    g::::IsDirected, distmx::AbstractMatrix=weights(g)
)
    cycle = Int[]
    λmin = Inf
    for component in strongly_connected_components(g)
        c, λ = _karp_minimum_cycle_mean(g, distmx, component)
        if λ < λmin
            cycle = c
            λmin = λ
        end
    end
    return cycle, λmin
end
