/*******************************************************************************
 * Copyright (c) 2010 Neil Bartlett.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Neil Bartlett - initial API and implementation
 ******************************************************************************/
package bndtools.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;

public class DependencyUtils {

    public static interface Processor<T> {
        void process(T obj, IProgressMonitor monitor) throws CoreException;
    }

    public static <T> void processDependencyMap(Collection<T> input, Map<T, Set<T>> dependencies, Processor<T> processor, IProgressMonitor monitor) throws CoreException, CircularDependencyException {
        SubMonitor progress = SubMonitor.convert(monitor, input.size());
        Set<T> processed = new TreeSet<T>();
        Stack<T> callStack = new Stack<T>();
        for (T selected : input) {
            processDependencyMap(selected, callStack, dependencies, processed, processor, progress);
        }
    }

    private static <T> void processDependencyMap(T selected, Stack<T> callStack, Map<T, Set<T>> dependencies, Set<T> processed, Processor<T> processor, SubMonitor subMonitor) throws CoreException, CircularDependencyException {
        if (processed.contains(selected))
            return;

        // Check for cycles
        int stackIndex = callStack.indexOf(selected);
        if (stackIndex != -1) {
            List<T> subList = callStack.subList(stackIndex, callStack.size());
            List<T> cycle = new ArrayList<T>(subList.size() + 1);
            cycle.addAll(subList);
            cycle.add(selected);

            throw new CircularDependencyException(cycle);
        }
        try {
            callStack.push(selected);

            Set<T> selectedDeps = dependencies.get(selected);

            // Process dependencies first
            if (selectedDeps != null) {
                for (T dep : selectedDeps) {
                    processDependencyMap(dep, callStack, dependencies, processed, processor, subMonitor);
                }
            }

            // Process the selection
            processor.process(selected, subMonitor.newChild(1));
            processed.add(selected);
        } finally {
            callStack.pop();
        }
    }
}
