/*******************************************************************************
 * Copyright (c) 2013, 2015 IBM Corporation 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:
 *     IBM Corporation - 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.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.util.tracker.BundleTrackerCustomizer;

/**
 * A simple {@link BundleTrackerCustomizer} which is able to resolve a bundle by its symbolic name.
 */
/*
 * TODO: This implementation can probably be removed after a switch to OSGi 6 which will have the
 * possibility to lookup bundles with the FrameworkWiring#findProviders(Requirement) method.
 */
final class BundleFinder implements BundleTrackerCustomizer<List<Bundle>> {

	/** Map of bundle symbolic name to the corresponding bundles (hint: different versions). */
	private final ConcurrentMap<String, List<Bundle>> trackedBundles = new ConcurrentHashMap<>();

	/**
	 * Resolves the latest bundle with the given bundle symbolic name.
	 * <p>
	 * The latest means the bundle with the highest version.
	 * </p>
	 * @param symbolicName
	 *            the bundle symbolic name
	 * @return the latest bundle with the given bundle symbolic name
	 */
	public Bundle findBundle(String symbolicName) {
		List<Bundle> bundlesWithSameSymName = trackedBundles.get(symbolicName);
		if (bundlesWithSameSymName == null)
			return null;

		List<Bundle> snapshot = new ArrayList<>(bundlesWithSameSymName);

		switch (snapshot.size()) {
		case 0:
			return null;
		case 1:
			return snapshot.get(0);
		default:
			snapshot.sort(VersionComperator.INSTANCE); // sort the snapshot by version
			return snapshot.get(0); // the highest is the first entry in the list
		}
	}

	@Override
	public List<Bundle> addingBundle(Bundle bundle, BundleEvent event) {
		String bundleSymName = bundle.getSymbolicName();
		if (bundleSymName == null) {
			return null;
		}

		List<Bundle> bundlesWithSameSymName = trackedBundles.get(bundleSymName);
		if (bundlesWithSameSymName == null) {
			bundlesWithSameSymName = new CopyOnWriteArrayList<>();

			if (trackedBundles.putIfAbsent(bundleSymName, bundlesWithSameSymName) != null) {
				// some other thread has won the race, so we use his List object
				bundlesWithSameSymName = trackedBundles.get(bundleSymName);
			}
		}

		bundlesWithSameSymName.add(bundle);

		// return the list to mark that we want to be informed about other changes of the bundle and
		// to remove the bundle in the #removedBundle() method without a lookup in the
		// #trackedBundles map
		return bundlesWithSameSymName;
	}

	@Override
	public void modifiedBundle(Bundle bundle, BundleEvent event, List<Bundle> bundlesWithSameSymName) {
		// not of interest
	}

	@Override
	public void removedBundle(Bundle bundle, BundleEvent event, List<Bundle> bundlesWithSameSymName) {
		// the object is the list which was returned inside the #addingBundle() method and so we
		// don't need a lookup in the #trackedBundles to find the list
		bundlesWithSameSymName.remove(bundle);
	}

	/**
	 * A simple {@link Comparator} which orders the bundles by their version in ascending order.
	 */
	private static final class VersionComperator implements Comparator<Bundle> {
		/** A Singleton instance of this {@link Comparator} (the compare is done state-less). */
		public static final Comparator<Bundle> INSTANCE = new VersionComperator();

		private VersionComperator() {
		}

		@Override
		public int compare(Bundle bundle1, Bundle bundle2) {
			if (bundle1 == null) {
				return bundle2 == null ? 0 : 1; // null elements at the end of the list
			}

			if (bundle2 == null) {
				return -1; // null elements at the end of the list
			}

			return bundle2.getVersion().compareTo(bundle1.getVersion()); // newest version first
		}
	}
}
