/* ==========================================
 * JGraphT : a free Java graph-theory library
 * ==========================================
 *
 * Project Info:  http://jgrapht.sourceforge.net/
 * Project Creator:  Barak Naveh (http://sourceforge.net/users/barak_naveh)
 *
 * (C) Copyright 2003-2012, by Barak Naveh and Contributors.
 *
 * This program and the accompanying materials are dual-licensed under
 * either
 *
 * (a) the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation, or (at your option) any
 * later version.
 *
 * or (per the licensee's choosing)
 *
 * (b) the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation.
 */
/* -------------------------
 * EdmondsBlossomShrinking.java
 * -------------------------
 * (C) Copyright 2012-2012, by Alejandro Ramon Lopez del Huerto and Contributors.
 *
 * Original Author:  Alejandro Ramon Lopez del Huerto
 * Contributor(s):
 *
 * Changes
 * -------
 * 24-Jan-2012 : Initial revision (ARLH);
 *
 */
package org.jgrapht.alg;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import org.jgrapht.UndirectedGraph;
import org.jgrapht.alg.interfaces.MatchingAlgorithm;
import org.jgrapht.util.ArrayDeque;
import org.jgrapht.util.ArrayUnenforcedSet;


/**
 * An implementation of Edmonds Blossom Shrinking algorithm for constructing
 * maximum matchings on graphs. The algorithm runs in time O(V^4).
 *
 * @author Alejandro R. Lopez del Huerto
 * @since Jan 24, 2012
 */
public class EdmondsBlossomShrinking<V, E>
    implements MatchingAlgorithm<V, E>
{
    

    // ~ Instance fields
    // --------------------------------------------------------

    private UndirectedGraph<V, E> graph;

    private Set<E> matching;

    private Map<V, V> match;
    private Map<V, V> p;
    private Map<V, V> base;
    private Queue<V> q;
    private Set<V> used;
    private Set<V> blossom;

    

    // ~ Constructors
    // ----------------------------------------------------------------

    @Deprecated public EdmondsBlossomShrinking()
    {
    }

    public EdmondsBlossomShrinking(final UndirectedGraph<V, E> G)
    {
        this.graph = G;
    }

    

    // ~ Deprecated Methods
    // ----------------------------------------------------------------

    /**
     * See `getMatching` as preferred alternative to this one
     */
    @Deprecated public Set<E> findMatch(final UndirectedGraph<V, E> g)
    {
        return new EdmondsBlossomShrinking<V, E>(g).getMatching();
    }

    // ~ Methods
    // ----------------------------------------------------------------

    @Override public Set<E> getMatching()
    {
        if (matching == null) {
            matching = findMatch();
        }
        return Collections.unmodifiableSet(matching);
    }

    /**
     * Runs the algorithm on the input graph and returns the match edge set.
     *
     * @return set of Edges
     */
    private Set<E> findMatch()
    {
        Set<E> result = new ArrayUnenforcedSet<E>();
        match = new HashMap<V, V>();
        p = new HashMap<V, V>();
        q = new ArrayDeque<V>();
        base = new HashMap<V, V>();
        used = new HashSet<V>();
        blossom = new HashSet<V>();

        for (V i : graph.vertexSet()) {
            if (!match.containsKey(i)) {
                V v = findPath(i);
                while (v != null) {
                    V pv = p.get(v);
                    V ppv = match.get(pv);
                    match.put(v, pv);
                    match.put(pv, v);
                    v = ppv;
                }
            }
        }

        Set<V> seen = new HashSet<V>();
        for (V v : graph.vertexSet()) {
            if (!seen.contains(v) && match.containsKey(v)) {
                seen.add(v);
                seen.add(match.get(v));
                result.add(graph.getEdge(v, match.get(v)));
            }
        }

        return result;
    }

    private V findPath(V root)
    {
        used.clear();
        p.clear();
        base.clear();

        for (V i : graph.vertexSet()) {
            base.put(i, i);
        }

        used.add(root);
        q.add(root);
        while (!q.isEmpty()) {
            V v = q.remove();
            for (E e : graph.edgesOf(v)) {
                V to = graph.getEdgeSource(e);
                if (to == v) {
                    to = graph.getEdgeTarget(e);
                }
                if ((base.get(v) == base.get(to)) || (match.get(v) == to)) {
                    continue;
                }
                if ((to == root)
                    || ((match.containsKey(to))
                        && (p.containsKey(match.get(to)))))
                {
                    V curbase = lca(graph, v, to);
                    blossom.clear();
                    markPath(v, curbase, to);
                    markPath(to, curbase, v);

                    for (V i : graph.vertexSet()) {
                        if (base.containsKey(i)
                            && blossom.contains(base.get(i)))
                        {
                            base.put(i, curbase);
                            if (!used.contains(i)) {
                                used.add(i);
                                q.add(i);
                            }
                        }
                    }
                } else if (!p.containsKey(to)) {
                    p.put(to, v);
                    if (!match.containsKey(to)) {
                        return to;
                    }
                    to = match.get(to);
                    used.add(to);
                    q.add(to);
                }
            }
        }
        return null;
    }

    private void markPath(V v, V b, V child)
    {
        while (base.get(v) != b) {
            blossom.add(base.get(v));
            blossom.add(base.get(match.get(v)));
            p.put(v, child);
            child = match.get(v);
            v = p.get(match.get(v));
        }
    }

    private V lca(UndirectedGraph<V, E> g, V a, V b)
    {
        Set<V> seen = new HashSet<V>();
        for (;;) {
            a = base.get(a);
            seen.add(a);
            if (!match.containsKey(a)) {
                break;
            }
            a = p.get(match.get(a));
        }
        for (;;) {
            b = base.get(b);
            if (seen.contains(b)) {
                return b;
            }
            b = p.get(match.get(b));
        }
    }
}

// End EdmondsBlossomShrinking.java
