/* 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.
 */
package org.jgrapht.alg;

import java.util.*;

import org.jgrapht.*;
import org.jgrapht.alg.util.*;


/**
 * Used to calculate Tarjan's Lowest Common Ancestors Algorithm
 *
 * @author Leo Crawford
 */

public class TarjanLowestCommonAncestor<V, E>
{
    

    private Graph<V, E> g;

    

    /**
     * Create an instance with a reference to the graph that we will find LCAs
     * for
     */
    public TarjanLowestCommonAncestor(Graph<V, E> g)
    {
        this.g = g;
    }

    

    /**
     * Calculate the LCM between <code>a</code> and <code>b</code> treating
     * <code>start</code> as the root we want to search from.
     */
    public V calculate(V start, V a, V b)
    {
        List<LcaRequestResponse<V>> list =
            new LinkedList<LcaRequestResponse<V>>();
        list.add(new LcaRequestResponse<V>(a, b));
        return calculate(start, list).get(0);
    }

    /**
     * Calculate the LCMs between a set of pairs (<code>a</code> and <code>
     * b</code>) treating <code>start</code> as the root we want to search from,
     * and setting the LCA of each pair in its LCA field
     */
    public List<V> calculate(V start, List<LcaRequestResponse<V>> lrr)
    {
        return new Worker(lrr).calculate(start);
    }

    

    /* The worker class keeps the state whilst doing calculations. */
    private class Worker
    {
        // The implementation of makeFind as referred to by <block>It uses the
        // MakeSet, Find, and Union functions of a disjoint-set forest.
        // MakeSet(u) removes u to a singleton set, Find(u) returns the standard
        // representative of the set containing u, and Union(u,v) merges the set
        // containing u with the set containing v. </block>
        // (http://en.wikipedia.org/wiki/Tarjan's_off-line_lowest_common_ancestors_algorithm)
        private UnionFind<V> uf = new UnionFind<V>(Collections.<V>emptySet());

        // the ancestors. instead of <code>u.ancestor = x</code> we do
        // <code>ancestors.put(u,x)</code>
        private Map<V, V> ancestors = new HashMap<V, V>();

        // instead of u.colour = black we do black.add(u)
        private Set<V> black = new HashSet<V>();

        // the two vertex that we want to find the LCA for
        private List<LcaRequestResponse<V>> lrr;
        private MultiMap<V> lrrMap;

        private Worker(List<LcaRequestResponse<V>> lrr)
        {
            this.lrr = lrr;
            this.lrrMap = new MultiMap<V>();

            // put in the reverse links from a and b entries back to the
            // LcaRequestReponse they're contained in
            for (LcaRequestResponse<V> r : lrr) {
                lrrMap.getOrCreate(r.getA()).add(r);
                lrrMap.getOrCreate(r.getB()).add(r);
            }
        }

        /**
         * Calculates the LCM as described by
         * http://en.wikipedia.org/wiki/Tarjan's_off-line_lowest_common_ancestors_algorithm
         * <code>function TarjanOLCA(u) MakeSet(u); u.ancestor := u; for each v
         * in u.children do TarjanOLCA(v); Union(u,v); Find(u).ancestor := u;
         * u.colour := black; for each v such that {u,v} in P do if v.colour ==
         * black print "Tarjan's Lowest Common Ancestor of " + u + " and " + v +
         * " is " + Find(v).ancestor + ".";</code>
         *
         * @param u the starting node (called recursively)
         *
         * @return the LCM if found, if not null
         */
        private List<V> calculate(final V u)
        {
            uf.addElement(u);
            ancestors.put(u, u);
            for (E vEdge : g.edgesOf(u)) {
                if (g.getEdgeSource(vEdge).equals(u)) {
                    V v = g.getEdgeTarget(vEdge);
                    calculate(v);
                    uf.union(u, v);
                    ancestors.put(uf.find(u), u);
                }
            }
            black.add(u);

            Set<LcaRequestResponse<V>> requestsForNodeU = lrrMap.get(u);
            if (requestsForNodeU != null) {
                for (LcaRequestResponse<V> rr : requestsForNodeU) {
                    if (black.contains(rr.getB()) && rr.getA().equals(u)) {
                        rr.setLca(ancestors.get(uf.find(rr.getB())));
                    }
                    if (black.contains(rr.getA()) && rr.getB().equals(u)) {
                        rr.setLca(ancestors.get(uf.find(rr.getA())));
                    }
                }

                // once we've dealt with it - remove it (to save memory?)
                lrrMap.remove(u);
            }
            

            List<V> result = new LinkedList<V>();
            for (LcaRequestResponse<V> current : lrr) {
                result.add(current.getLca());
            }
            return result;
        }
    }

    public static class LcaRequestResponse<V>
    {
        private V a, b, lca;

        public LcaRequestResponse(V a, V b)
        {
            this.a = a;
            this.b = b;
        }

        public V getA()
        {
            return a;
        }

        public V getB()
        {
            return b;
        }

        public V getLca()
        {
            return lca;
        }

        void setLca(V lca)
        {
            this.lca = lca;
        }
    }

    @SuppressWarnings("serial")
    private static final class MultiMap<V>
        extends HashMap<V, Set<LcaRequestResponse<V>>>
    {
        public Set<LcaRequestResponse<V>> getOrCreate(V key)
        {
            if (!containsKey(key)) {
                put(key, new HashSet<LcaRequestResponse<V>>());
            }
            return get(key);
        }
    }
}

// End TarjanLowestCommonAncestor.java
