/*******************************************************************************
 * Copyright (c) 2005, 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.equinox.internal.app;

import java.security.AccessController;
import java.security.PrivilegedAction;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.spi.RegistryContributor;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.service.debug.DebugOptions;
import org.eclipse.osgi.service.environment.EnvironmentInfo;
import org.osgi.framework.*;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

public class Activator implements BundleActivator, ServiceTrackerCustomizer {
	public static final String PI_APP = "org.eclipse.equinox.app"; //$NON-NLS-1$
	public static boolean DEBUG = false;
	private volatile static BundleContext _context;
	// PackageAdmin is a system service that never goes away as long 
	// as the framework is active.  No need to track it!!
	private volatile static PackageAdmin _packageAdmin;
	private volatile static EclipseAppContainer container;
	// tracks the FrameworkLog service
	private volatile static ServiceTracker _frameworkLogTracker;
	// tracks the extension registry and app launcher services
	private ServiceTracker registryTracker;
	private IExtensionRegistry registry;

	@Override
	public void start(BundleContext bc) {
		_context = bc;
		// doing simple get service here because we expect the PackageAdmin service to always be available
		ServiceReference ref = bc.getServiceReference(PackageAdmin.class.getName());
		if (ref != null)
			_packageAdmin = (PackageAdmin) bc.getService(ref);
		_frameworkLogTracker = new ServiceTracker(bc, FrameworkLog.class.getName(), null);
		_frameworkLogTracker.open();
		getDebugOptions(bc);
		processCommandLineArgs(bc);
		// set the app manager context before starting the container
		AppPersistence.start(bc);
		// we must have an extension registry started before we can start the container
		registryTracker = new ServiceTracker(bc, IExtensionRegistry.class.getName(), this);
		registryTracker.open();
		// start the app commands for the console
		try {
			AppCommands.create(bc);
		} catch (NoClassDefFoundError e) {
			// catch incase CommandProvider is not available
		}
	}

	@Override
	public void stop(BundleContext bc) {
		// stop the app commands for the console
		try {
			AppCommands.destroy(bc);
		} catch (NoClassDefFoundError e) {
			// catch incase CommandProvider is not available
		}
		// close the registry tracker; this will stop the container if it was started
		registryTracker.close();
		registryTracker = null;
		// unset the app manager context after the container has been stopped
		AppPersistence.stop();
		if (_frameworkLogTracker != null) {
			_frameworkLogTracker.close();
			_frameworkLogTracker = null;
		}
		_packageAdmin = null; // we do not unget PackageAdmin here; let the framework do it for us
		_context = null;
	}

	private void getDebugOptions(BundleContext context) {
		ServiceReference debugRef = context.getServiceReference(DebugOptions.class.getName());
		if (debugRef == null)
			return;
		DebugOptions debugOptions = (DebugOptions) context.getService(debugRef);
		DEBUG = debugOptions.getBooleanOption(PI_APP + "/debug", false); //$NON-NLS-1$
		context.ungetService(debugRef);
	}

	private static EnvironmentInfo getEnvironmentInfo() {
		BundleContext bc = Activator.getContext();
		if (bc == null)
			return null;
		ServiceReference infoRef = bc.getServiceReference(EnvironmentInfo.class.getName());
		if (infoRef == null)
			return null;
		EnvironmentInfo envInfo = (EnvironmentInfo) bc.getService(infoRef);
		if (envInfo == null)
			return null;
		bc.ungetService(infoRef);
		return envInfo;
	}

	private void processCommandLineArgs(BundleContext bc) {
		EnvironmentInfo envInfo = Activator.getEnvironmentInfo();
		if (envInfo != null)
			CommandLineArgs.processCommandLine(envInfo);
	}

	@Override
	public Object addingService(ServiceReference reference) {
		BundleContext context = _context;
		if (context == null)
			return null; // really should never happen since we close the tracker before nulling out context
		Object service = null;
		EclipseAppContainer startContainer = null;
		synchronized (this) {
			if (container != null)
				return null; // container is already started; do nothing

			service = context.getService(reference);
			if (registry == null && service instanceof IExtensionRegistry) {
				registry = (IExtensionRegistry) service;
				// create and start the app container
				container = new EclipseAppContainer(context, registry);
				startContainer = container;
			}
		}
		// must not start the container while holding a lock because this will register additional services
		if (startContainer != null) {
			startContainer.start();
			return service;
		}
		// this means there is more than one registry; we don't need a second one
		if (service != null)
			context.ungetService(reference);
		return null;
	}

	@Override
	public void modifiedService(ServiceReference reference, Object service) {
		// do nothing
	}

	@Override
	public void removedService(ServiceReference reference, Object service) {
		EclipseAppContainer currentContainer = null;
		synchronized (this) {
			// either the registry or launcher is going away
			if (service == registry)
				registry = null;
			if (container == null)
				return; // do nothing; we have not started the container yet
			currentContainer = container;
			container = null;
		}
		// stop the app container outside the sync block
		if (currentContainer != null)
			currentContainer.stop();
	}

	// helper used to protect callers from permission checks when opening service trackers
	static void openTracker(final ServiceTracker tracker, final boolean allServices) {
		if (System.getSecurityManager() == null)
			tracker.open(allServices);
		else
			AccessController.doPrivileged(new PrivilegedAction() {
				@Override
				public Object run() {
					tracker.open(allServices);
					return null;
				}
			});
	}

	// helper used to protect callers from permission checks when get services
	static Object getService(final ServiceTracker tracker) {
		if (System.getSecurityManager() == null)
			return tracker.getService();
		return AccessController.doPrivileged(new PrivilegedAction() {
			@Override
			public Object run() {
				return tracker.getService();
			}
		});
	}

	// helper used to protect callers from permission checks when getting locations
	static String getLocation(final Bundle bundle) {
		if (System.getSecurityManager() == null)
			return bundle.getLocation();
		return (String) AccessController.doPrivileged(new PrivilegedAction() {
			@Override
			public Object run() {
				return bundle.getLocation();
			}
		});
	}

	// helper method to get a bundle from a contributor.
	static Bundle getBundle(IContributor contributor) {
		if (contributor instanceof RegistryContributor) {
			try {
				long id = Long.parseLong(((RegistryContributor) contributor).getActualId());
				BundleContext context = _context;
				if (context != null)
					return context.getBundle(id);
			} catch (NumberFormatException e) {
				// try using the name of the contributor below
			}
		}
		PackageAdmin packageAdmin = _packageAdmin;
		if (packageAdmin == null)
			return null;
		Bundle[] bundles = packageAdmin.getBundles(contributor.getName(), null);
		if (bundles == null)
			return null;
		//Return the first bundle that is not installed or uninstalled
		for (Bundle bundle : bundles) {
			if ((bundle.getState() & (Bundle.INSTALLED | Bundle.UNINSTALLED)) == 0) {
				return bundle;
			}
		}
		return null;
	}

	static BundleContext getContext() {
		return _context;
	}

	public static EclipseAppContainer getContainer() {
		return container;
	}

	static void log(FrameworkLogEntry entry) {
		ServiceTracker frameworkLogTracker = _frameworkLogTracker;
		FrameworkLog log = frameworkLogTracker == null ? null : (FrameworkLog) frameworkLogTracker.getService();
		if (log != null)
			log.log(entry);
	}

	static void setProperty(String key, String value) {
		EnvironmentInfo envInfo = getEnvironmentInfo();
		if (envInfo != null)
			envInfo.setProperty(key, value);
		else
			System.getProperties().setProperty(key, value);
	}
}
