/*
 * Copyright 2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gradle.api.internal;

import java.util.*;

/**
 * Groups the nodes of a graph based on their reachability from a set of starting nodes.
 */
public class GraphAggregator<N> {
    private final CachingDirectedGraphWalker<N, N> graphWalker;

    public GraphAggregator(DirectedGraph<N, ?> graph) {
        graphWalker = new CachingDirectedGraphWalker<N, N>(new ConnectedNodesAsValuesDirectedGraph<N>(graph));
    }

    public Result<N> group(Collection<? extends N> startNodes, Collection<? extends N> allNodes) {
        Map<N, Set<N>> reachableByNode = new HashMap<N, Set<N>>();
        Set<N> topLevelNodes = new LinkedHashSet<N>(allNodes);
        for (N node : allNodes) {
            Set<N> reachableNodes = graphWalker.add(node).findValues();
            reachableByNode.put(node, reachableNodes);
            topLevelNodes.removeAll(reachableNodes);
        }
        topLevelNodes.addAll(startNodes);
        Map<N, Set<N>> nodes = new HashMap<N, Set<N>>();
        for (N node : topLevelNodes) {
            nodes.put(node, calculateReachableNodes(reachableByNode, node, topLevelNodes));
        }
        return new Result<N>(nodes, topLevelNodes);
    }

    private Set<N> calculateReachableNodes(Map<N, Set<N>> nodes, N node, Set<N> topLevelNodes) {
        Set<N> reachableNodes = nodes.get(node);
        reachableNodes.add(node);
        Set<N> reachableStartNodes = new LinkedHashSet<N>(topLevelNodes);
        reachableStartNodes.retainAll(reachableNodes);
        reachableStartNodes.remove(node);
        for (N startNode : reachableStartNodes) {
            reachableNodes.removeAll(calculateReachableNodes(nodes, startNode, topLevelNodes));
        }
        return reachableNodes;
    }

    public static class Result<N> {
        private final Map<N, Set<N>> nodes;
        private final Set<N> topLevelNodes;

        public Result(Map<N, Set<N>> nodes, Set<N> topLevelNodes) {
            this.nodes = nodes;
            this.topLevelNodes = topLevelNodes;
        }

        public Set<N> getNodes(N startNode) {
            return nodes.get(startNode);
        }

        public Set<N> getTopLevelNodes() {
            return topLevelNodes;
        }
    }

    private static class ConnectedNodesAsValuesDirectedGraph<N> implements DirectedGraph<N, N> {
        private final DirectedGraph<N, ?> graph;

        private ConnectedNodesAsValuesDirectedGraph(DirectedGraph<N, ?> graph) {
            this.graph = graph;
        }

        public void getNodeValues(N node, Collection<N> values, Collection<N> connectedNodes) {
            Set<N> edges = new LinkedHashSet<N>();
            graph.getNodeValues(node, new ArrayList(), edges);
            values.addAll(edges);
            connectedNodes.addAll(edges);
        }
    }
}
