/*******************************************************************************
 * Copyright (c) 2004, 2018 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
 *******************************************************************************/
package org.eclipse.core.tests.harness;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.eclipse.core.runtime.FileLocator;
import org.junit.Assert;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;

public class BundleTestingHelper {

	public static Bundle[] getBundles(BundleContext context, String symbolicName, String version) {
		ServiceReference<PackageAdmin> packageAdminReference = context.getServiceReference(PackageAdmin.class);
		if (packageAdminReference == null) {
			throw new IllegalStateException("No package admin service found");
		}
		PackageAdmin packageAdmin = context.getService(packageAdminReference);
		Bundle[] result = packageAdmin.getBundles(symbolicName, version);
		context.ungetService(packageAdminReference);
		return result;
	}

	/**
	 * @deprecated
	 */
	@Deprecated
	public static Bundle installBundle(BundleContext context, String location) throws BundleException, MalformedURLException, IOException {
		return installBundle("", context, location);
	}

	public static Bundle installBundle(String tag, BundleContext context, String location) throws BundleException, MalformedURLException, IOException {
		URL entry = context.getBundle().getEntry(location);
		if (entry == null) {
			Assert.fail(tag + " entry " + location + " could not be found in " + context.getBundle().getSymbolicName());
		}
		Bundle installed = context.installBundle(FileLocator.toFileURL(entry).toExternalForm());
		return installed;
	}

	/**
	 * Do PackageAdmin.refreshPackages() in a synchronous way.  After installing
	 * all the requested bundles we need to do a refresh and want to ensure that
	 * everything is done before returning.
	 * @param bundles
	 * TODO remove this since all we wanted was to resolve bundles, what is done by #resolveBundles in this class
	 */
	//copied from EclipseStarter
	public static void refreshPackages(BundleContext context, Bundle[] bundles) {
		if (bundles.length == 0) {
			return;
		}
		ServiceReference<PackageAdmin> packageAdminRef = context.getServiceReference(PackageAdmin.class);
		PackageAdmin packageAdmin = null;
		if (packageAdminRef != null) {
			packageAdmin = context.getService(packageAdminRef);
			if (packageAdmin == null) {
				return;
			}
		}
		// TODO this is such a hack it is silly.  There are still cases for race conditions etc
		// but this should allow for some progress...
		// (patch from John A.)
		final boolean[] flag = new boolean[] {false};
		FrameworkListener listener = event -> {
			if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
				synchronized (flag) {
					flag[0] = true;
					flag.notifyAll();
				}
			}
		};
		context.addFrameworkListener(listener);
		packageAdmin.refreshPackages(bundles);
		synchronized (flag) {
			while (!flag[0]) {
				try {
					flag.wait();
				} catch (InterruptedException e) {
					// who cares....
				}
			}
		}
		context.removeFrameworkListener(listener);
		context.ungetService(packageAdminRef);
	}

	public static boolean  resolveBundles(BundleContext context, Bundle[] bundles) {
		ServiceReference<PackageAdmin> packageAdminRef = context.getServiceReference(PackageAdmin.class);
		PackageAdmin packageAdmin = null;
		if (packageAdminRef != null) {
			packageAdmin = context.getService(packageAdminRef);
			if (packageAdmin == null) {
				return false;
			}
		}
		boolean result = packageAdmin.resolveBundles(bundles);
		context.ungetService(packageAdminRef);
		return result;
	}

	public static void runWithBundles(String tag, Runnable runnable, BundleContext context, String[] locations, TestRegistryChangeListener listener) {
		if (listener != null) {
			listener.register();
		}
		try {
			Bundle[] installed = new Bundle[locations.length];
			for (int i = 0; i < locations.length; i++) {
				try {
					installed[i] = installBundle(tag + ".setup.0", context, locations[i]);
					Assert.assertEquals(tag + ".setup.1." + locations[i], Bundle.INSTALLED, installed[i].getState());
				} catch (BundleException e) {
					CoreTest.fail(tag + ".setup.2" + locations[i], e);
				} catch (IOException e) {
					CoreTest.fail(tag + ".setup.3" + locations[i], e);
				}
			}
			if (listener != null) {
				listener.reset();
			}
			if (!BundleTestingHelper.resolveBundles(context, installed)) {
				Assert.fail(tag + ".setup.resolveBundles");
			}
			if (listener != null) {
				// ensure the contributions were properly added
				Assert.assertTrue(tag + ".setup.4", listener.eventReceived(installed.length * 10000));
			}
			try {
				runnable.run();
			} finally {
				if (listener != null) {
					listener.reset();
				}
				// remove installed bundles
				for (int i = 0; i < installed.length; i++) {
					try {
						installed[i].uninstall();
					} catch (BundleException e) {
						CoreTest.fail(tag + ".tearDown.1." + locations[i], e);
					}
				}
				BundleTestingHelper.resolveBundles(context, installed);
				if (listener != null) {
					// ensure the contributions were properly added
					Assert.assertTrue(tag + ".tearDown.2", listener.eventReceived(installed.length * 10000));
				}
			}
		} finally {
			if (listener != null) {
				listener.unregister();
			}
		}
	}

}
