/*******************************************************************************
 * Copyright (c) 2011, 2015 VMware Inc.
 *
 * 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:
 *   VMware Inc. - initial contribution
 *******************************************************************************/

package org.eclipse.equinox.internal.region.hook;

import java.util.Arrays;
import java.util.Collection;
import org.eclipse.equinox.region.*;
import org.osgi.framework.Bundle;
import org.osgi.framework.hooks.bundle.CollisionHook;

/**
 * {@link RegionBundleCollisionHook} manages the collision policy of duplicate bundles.
 * 
 * <strong>Concurrent Semantics</strong><br />
 * Thread safe.
 */
public final class RegionBundleCollisionHook implements CollisionHook {

	private final RegionDigraph regionDigraph;

	private final ThreadLocal<Region> threadLocal;

	public RegionBundleCollisionHook(RegionDigraph regionDigraph, ThreadLocal<Region> threadLocal) {
		this.regionDigraph = regionDigraph;
		this.threadLocal = threadLocal;
	}

	private Region getInstallRegion(Bundle originBundle) {
		/*
		 * BundleIdBasedRegion sets thread local to install bundles into arbitrary regions. If this is not set, the
		 * bundle inherits the region of the origin bundle.
		 */
		Region installRegion = this.threadLocal.get();
		if (installRegion != null) {
			return installRegion;
		}
		return this.regionDigraph.getRegion(originBundle);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates) {
		Region targetRegion = getInstallRegion(target);
		if (targetRegion == null) {
			// don't know if this is a collision or not just return all
			return;
		}

		// First check the collision candidates from the perspective of the
		// installing/updating targetRegion
		VisitorFromTarget fromTarget = new VisitorFromTarget(collisionCandidates);
		targetRegion.visitSubgraph(fromTarget);
		Collection<Bundle> collisions = fromTarget.getAllowed();

		if (collisions.isEmpty()) {
			// must do a sanity check to make sure the newly installed/updated bundle 
			// does not collide from the perspective of the collision candidate regions
			for (Bundle collisionCandidate : collisionCandidates) {
				Region candidateRegion = regionDigraph.getRegion(collisionCandidate);
				if (candidateRegion == null) {
					// we assume the candidate has been uninstalled; do not consider the candidate as a collision
					continue;
				}
				// we know the collision candidates all have the BSN/Version that collide.
				// we use the collision candidate and pretend it is part of the target region
				// to see if we can see it from the candidateRegion
				VisitorFromCandidate fromCandidate = new VisitorFromCandidate(Arrays.asList(collisionCandidate), targetRegion);
				candidateRegion.visitSubgraph(fromCandidate);
				collisions = fromCandidate.getAllowed();
				if (!collisions.isEmpty()) {
					// we can break at the first one since it only takes one to fail the install/update
					break;
				}
			}
		}

		collisionCandidates.retainAll(collisions);
	}

	class VisitorFromTarget extends RegionDigraphVisitorBase<Bundle> {

		VisitorFromTarget(Collection<Bundle> candidates) {
			super(candidates);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected boolean contains(Region region, Bundle candidate) {
			return region.contains(candidate);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected boolean isAllowed(Bundle candidate, RegionFilter filter) {
			return filter.isAllowed(candidate);
		}

	}

	class VisitorFromCandidate extends RegionDigraphVisitorBase<Bundle> {
		private final Region targetRegion;

		VisitorFromCandidate(Collection<Bundle> candidates, Region targetRegion) {
			super(candidates);
			this.targetRegion = targetRegion;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected boolean contains(Region region, Bundle candidate) {
			return region.equals(targetRegion);
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		protected boolean isAllowed(Bundle candidate, RegionFilter filter) {
			return filter.isAllowed(candidate);
		}

	}
}
