/*******************************************************************************
 * Copyright (c) 2014, 2015 Manumitting Technologies Inc and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Brian de Alwis (MTI) - initial API and implementation
 *     René Brandstetter - Bug 419749
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
 ******************************************************************************/

package org.eclipse.e4.ui.internal.workbench;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.osgi.framework.Bundle;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;

/**
 * Sort {@link IExtension}s by their plug-in dependencies such that any extension from bundle A
 * should be sorted before any extensions from any bundles that depend on A.
 * <p>
 * Consider having extensions from bundles A, B, and C, where A depends on B, and B depends on C.
 * The result of this sort should be the extensions from C, then from B, and finally from A.
 */
public class ExtensionsSort extends TopologicalSort<IExtension, Bundle> {

	@Override
	protected Bundle getId(IExtension extension) {
		IContributor contributor = extension.getContributor();
		return Activator.getDefault().getBundleForName(contributor.getName());
	}

	/**
	 * Returns the bundles that the given bundle requires.
	 *
	 * @return An unmodifiable {@link Iterable} set of bundles currently required by the geiven
	 *         bundle. An empty {@link Iterable} will be returned if the given {@code Bundle} object
	 *         has become stale or there are no bundles required.
	 * @throws NullPointerException
	 *             if {@code bundle} is <code>null</code>
	 */
	@Override
	protected Collection<Bundle> getRequirements(Bundle bundle) {
		// punt to getDependents()
		return null;
	}

	/**
	 * Recursively collects all bundles which depend-on/require the given {@link BundleWiring}.
	 * <p>
	 * All re-exports will be followed and also be contained in the result.
	 * </p>
	 *
	 * @param dependents
	 *            the result which will contain all the bundles which require the given
	 *            {@link BundleWiring}
	 * @param providerWiring
	 *            the {@link BundleWiring} for which the requirers should be resolved
	 * @throws NullPointerException
	 *             if either the requiring or the providerWiring is <code>null</code>
	 */
	private static void addDependents(Set<Bundle> dependents, BundleWiring providerWiring) {
		List<BundleWire> requirerWires = providerWiring
				.getProvidedWires(BundleNamespace.BUNDLE_NAMESPACE);
		if (requirerWires == null) {
			// we don't hold locks while checking the graph, just return if no longer isInUse
			return;
		}
		for (BundleWire requireBundleWire : requirerWires) {
			Bundle requirer = requireBundleWire.getRequirer().getBundle();
			if (dependents.contains(requirer)) {
				continue;
			}
			dependents.add(requirer);
			String reExport = requireBundleWire.getRequirement().getDirectives()
					.get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE);
			if (BundleNamespace.VISIBILITY_REEXPORT.equals(reExport)) {
				addDependents(dependents, requireBundleWire.getRequirerWiring());
			}
		}
	}

	/**
	 * Returns the bundles that currently require the given bundle.
	 * <p>
	 * If {@code bundle} is required and then re-exported by another bundle then all the requiring
	 * bundles of the re-exporting bundle are included in the returned array as they transitively
	 * depend on {@code bundle}.
	 * </p>
	 *
	 * @return An unmodifiable {@link Iterable} set of bundles currently requiring {@code bundle}.
	 *         An empty {@link Iterable} will be returned if the {@code bundle} has become stale or
	 *         has no dependents.
	 * @throws NullPointerException
	 *             if {@code bundle} is <code>null</code>
	 */
	@Override
	protected Collection<Bundle> getDependencies(Bundle bundle) {
		BundleWiring providerWiring = bundle.adapt(BundleWiring.class);
		if (!providerWiring.isInUse()) {
			return Collections.emptySet();
		}

		Set<Bundle> required = new HashSet<>();
		addDependents(required, providerWiring);
		return Collections.unmodifiableSet(required);
	}
}
