/*******************************************************************************
 * Copyright (c) 2006, 2017 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:
 *   David Knibb               initial implementation
 *   Matthew Webster           Eclipse 3.2 changes
 *   Heiko Seeberger           Enhancements for service dynamics
 *   Martin Lippert            extracted weaving and caching service factories
 *******************************************************************************/

package org.eclipse.equinox.weaving.adaptors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.equinox.service.weaving.ICachingService;
import org.eclipse.equinox.service.weaving.ICachingServiceFactory;
import org.eclipse.equinox.service.weaving.ISupplementerRegistry;
import org.eclipse.equinox.service.weaving.IWeavingService;
import org.eclipse.equinox.service.weaving.IWeavingServiceFactory;
import org.eclipse.osgi.internal.loader.ModuleClassLoader;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.service.startlevel.StartLevel;
import org.osgi.util.tracker.ServiceTracker;

public class WeavingAdaptorFactory {

	private static final Collection<String> IGNORE_WEAVING_SERVICE_BUNDLES = Arrays
			.asList(new String[] { "org.eclipse.equinox.weaving.aspectj", //$NON-NLS-1$
					"org.eclipse.equinox.weaving.caching", //$NON-NLS-1$
					"org.eclipse.equinox.weaving.caching.j9", //$NON-NLS-1$
					"org.eclipse.equinox.simpleconfigurator", //$NON-NLS-1$
					"org.eclipse.equinox.common" }); //$NON-NLS-1$

	private ServiceTracker<ICachingServiceFactory, ICachingServiceFactory> cachingServiceFactoryTracker;

	private PackageAdmin packageAdminService;

	private StartLevel startLevelService;

	private ISupplementerRegistry supplementerRegistry;

	private ServiceTracker<IWeavingServiceFactory, IWeavingServiceFactory> weavingServiceFactoryTracker;

	private ServiceListener weavingServiceListener;

	private final Map<Bundle, IWeavingService> weavingServices = new ConcurrentHashMap<>();

	public WeavingAdaptorFactory() {
	}

	public void dispose(final BundleContext context) {

		context.removeServiceListener(weavingServiceListener);
		if (Debug.DEBUG_WEAVE)
			Debug.println("> Removed service listener for weaving service."); //$NON-NLS-1$

		weavingServiceFactoryTracker.close();
		if (Debug.DEBUG_WEAVE)
			Debug.println("> Closed service tracker for weaving service."); //$NON-NLS-1$

		cachingServiceFactoryTracker.close();
		if (Debug.DEBUG_CACHE)
			Debug.println("> Closed service tracker for caching service."); //$NON-NLS-1$
	}

	protected ICachingService getCachingService(final ModuleClassLoader loader, final Bundle bundle,
			final IWeavingService weavingService) {
		if (Debug.DEBUG_CACHE)
			Debug.println("> WeavingAdaptorFactory.getCachingService() bundle=" //$NON-NLS-1$
					+ bundle + ", weavingService=" + weavingService); //$NON-NLS-1$
		ICachingService service = null;
		String key = ""; //$NON-NLS-1$

		if (weavingService != null) {
			key = weavingService.getKey();
		}
		final ICachingServiceFactory cachingServiceFactory = cachingServiceFactoryTracker.getService();
		if (cachingServiceFactory != null) {
			service = cachingServiceFactory.createCachingService(loader, bundle, key);
		}
		if (Debug.DEBUG_CACHE)
			Debug.println("< WeavingAdaptorFactory.getCachingService() service=" //$NON-NLS-1$
					+ service + ", key='" + key + "'"); //$NON-NLS-1$ //$NON-NLS-2$
		return service;
	}

	public Bundle getHost(final Bundle fragment) {
		if (Debug.DEBUG_GENERAL)
			Debug.println("> WeavingAdaptorFactory.getHost() fragment=" + fragment); //$NON-NLS-1$

		Bundle host = null;
		if (packageAdminService != null)
			host = packageAdminService.getHosts(fragment)[0];

		if (Debug.DEBUG_GENERAL)
			Debug.println("< WeavingAdaptorFactory.getHost() " + host); //$NON-NLS-1$
		return host;
	}

	protected IWeavingService getWeavingService(final ModuleClassLoader loader) {
		if (Debug.DEBUG_WEAVE)
			Debug.println("> WeavingAdaptorFactory.getWeavingService() baseClassLoader=" //$NON-NLS-1$
					+ loader);

		final Generation generation = loader.getClasspathManager().getGeneration();
		final Bundle bundle = loader.getBundle();

		IWeavingService weavingService = null;
		if (!IGNORE_WEAVING_SERVICE_BUNDLES.contains(bundle.getSymbolicName())) {
			final IWeavingServiceFactory weavingServiceFactory = weavingServiceFactoryTracker.getService();
			if (weavingServiceFactory != null) {
				weavingService = weavingServiceFactory.createWeavingService(loader, bundle, generation.getRevision(),
						supplementerRegistry);

				if (weavingService != null) {
					weavingServices.put(bundle, weavingService);
				}
			}
		}
		if (Debug.DEBUG_WEAVE)
			Debug.println("< WeavingAdaptorFactory.getWeavingService() service=" //$NON-NLS-1$
					+ weavingService);
		return weavingService;
	}

	public void initialize(final BundleContext context, final ISupplementerRegistry supplementerRegistry) {
		if (Debug.DEBUG_GENERAL)
			Debug.println("> WeavingAdaptorFactory.initialize() context=" //$NON-NLS-1$
					+ context);
		this.supplementerRegistry = supplementerRegistry;

		initializePackageAdminService(context);
		initializeStartLevelService(context);

		// Service tracker for weaving service
		weavingServiceFactoryTracker = new ServiceTracker<>(context, IWeavingServiceFactory.class, null);
		weavingServiceFactoryTracker.open();
		if (Debug.DEBUG_WEAVE)
			Debug.println("> Opened service tracker for weaving service."); //$NON-NLS-1$

		// Service listener for weaving service
		weavingServiceListener = new ServiceListener() {

			@Override
			public void serviceChanged(final ServiceEvent event) {
				if (event.getType() == ServiceEvent.REGISTERED) {
					final List<Bundle> bundlesToRefresh = new ArrayList<>();

					synchronized (weavingServices) {
						final Iterator<Bundle> bundleEntries = weavingServices.keySet().iterator();
						while (bundleEntries.hasNext()) {
							final Bundle bundle = bundleEntries.next();
							bundleEntries.remove();
							bundlesToRefresh.add(bundle);
							if (Debug.DEBUG_WEAVE)
								Debug.println("> Updated bundle " //$NON-NLS-1$
										+ bundle.getSymbolicName());
						}
					}

					if (bundlesToRefresh.size() > 0) {
						supplementerRegistry
								.refreshBundles(bundlesToRefresh.toArray(new Bundle[bundlesToRefresh.size()]));
					}
				}
				if (event.getType() == ServiceEvent.UNREGISTERING && startLevelService != null
						&& startLevelService.getStartLevel() > 0) {
					final List<Bundle> bundlesToRefresh = new ArrayList<>();

					synchronized (weavingServices) {
						final Iterator<Bundle> bundleEntries = weavingServices.keySet().iterator();
						while (bundleEntries.hasNext()) {
							final Bundle bundle = bundleEntries.next();
							bundleEntries.remove();
							bundlesToRefresh.add(bundle);
							if (Debug.DEBUG_WEAVE)
								Debug.println("> Updated bundle " //$NON-NLS-1$
										+ bundle.getSymbolicName());
						}
					}
					if (bundlesToRefresh.size() > 0) {
						supplementerRegistry
								.refreshBundles(bundlesToRefresh.toArray(new Bundle[bundlesToRefresh.size()]));
					}
				}
			}
		};

		// if (System.getProperty(WEAVING_SERVICE_DYNAMICS_PROPERTY, "false")
		// .equals("true")) {
		try {
			context.addServiceListener(weavingServiceListener, "(" //$NON-NLS-1$
					+ Constants.OBJECTCLASS + "=" //$NON-NLS-1$
					+ IWeavingServiceFactory.class.getName() + ")"); //$NON-NLS-1$
		} catch (final InvalidSyntaxException e) { // This is correct!
		}

		// Service tracker for caching service
		cachingServiceFactoryTracker = new ServiceTracker<>(context, ICachingServiceFactory.class, null);
		cachingServiceFactoryTracker.open();
		if (Debug.DEBUG_CACHE)
			Debug.println("> Opened service tracker for caching service."); //$NON-NLS-1$
	}

	private void initializePackageAdminService(final BundleContext context) {
		if (Debug.DEBUG_GENERAL)
			Debug.println("> AdaptorFactory.initializePackageAdminService() context=" //$NON-NLS-1$
					+ context);

		final ServiceReference<PackageAdmin> ref = context.getServiceReference(PackageAdmin.class);
		if (ref != null) {
			packageAdminService = context.getService(ref);
		}

		if (Debug.DEBUG_GENERAL)
			Debug.println("< AdaptorFactory.initializePackageAdminService() " //$NON-NLS-1$
					+ packageAdminService);
	}

	private void initializeStartLevelService(final BundleContext context) {
		if (Debug.DEBUG_GENERAL)
			Debug.println("> AdaptorFactory.initializeStartLevelService() context=" //$NON-NLS-1$
					+ context);

		final ServiceReference<StartLevel> ref = context.getServiceReference(StartLevel.class);
		if (ref != null) {
			startLevelService = context.getService(ref);
		}

		if (Debug.DEBUG_GENERAL)
			Debug.println("< AdaptorFactory.initializeStartLevelService() " //$NON-NLS-1$
					+ startLevelService);
	}
}
