/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.aries.subsystem.itests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.equinox.region.Region;
import org.eclipse.equinox.region.RegionDigraph;
import org.eclipse.equinox.region.RegionFilter;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.Version;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemConstants;

public class RootSubsystemTest extends SubsystemTest {
	/*
	 * Subsystem-SymbolicName: application.a.esa
	 * Subsystem-Content: bundle.a.jar
	 */
	private static final String APPLICATION_A = "application.a.esa";
	/*
	 * Bundle-SymbolicName: bundle.a.jar
	 * Import-Package: org.osgi.framework
	 */
	private static final String BUNDLE_A = "bundle.a.jar";
	
	@Override
	public void createApplications() throws Exception {
		createBundleA();
		createApplicationA();
	}
	
	private static void createApplicationA() throws IOException {
		createApplicationAManifest();
		createSubsystem(APPLICATION_A, BUNDLE_A);
	}
	
	private static void createApplicationAManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, APPLICATION_A);
		createManifest(APPLICATION_A + ".mf", attributes);
	}
	
	private void createBundleA() throws IOException {
		createBundle(name(BUNDLE_A), importPackage("org.osgi.framework"));
	}
	
	// TODO Test root subsystem headers.
	
	@Test
	public void testDoNotStartExtraneousRootRegionBundles() throws Exception {
		bundleContext.installBundle(new File(BUNDLE_A).toURI().toURL().toString());
		getSubsystemCoreBundle().stop();
		getSubsystemCoreBundle().start();
		Bundle bundleA = context().getBundleByName(BUNDLE_A);
		assertTrue("Extraneous root region bundle should not be started", (bundleA.getState() & (Bundle.INSTALLED | Bundle.RESOLVED)) != 0);
	}
	
	@Test
	public void testId() {
		assertEquals("Wrong root ID", getRootSubsystem().getSubsystemId(), 0);
	}
	
	@Test
	public void testLocation() {
		assertEquals("Wrong root location", getRootSubsystem().getLocation(), "subsystem://?Subsystem-SymbolicName=org.osgi.service.subsystem.root&Subsystem-Version=1.0.0");
	}
	
	@Test
	public void testRegionContextBundle() throws BundleException {
		assertRegionContextBundle(getRootSubsystem());
		getSubsystemCoreBundle().stop();
		getSubsystemCoreBundle().start();
		assertRegionContextBundle(getRootSubsystem());
	}
	
	@Test
	public void testServiceEvents() throws Exception {
		Subsystem root = getRootSubsystem();
		Bundle core = getSubsystemCoreBundle();
		// TODO Temporary(?) workaround to allow time for any tardy service
		// events to arrive so they can be cleared. So far, this sleep has only
		// been necessary on the IBM 6.0 64-bit JDK.
		Thread.sleep(1000);
		subsystemEvents.clear();
		core.stop();
		assertServiceEventsStop(root);
		core.uninstall();
		core = bundleContext.installBundle(normalizeBundleLocation(core));
		core.start();
		// There should be install events since the persisted root subsystem was
		// deleted when the subsystems implementation bundle was uninstalled.
		assertServiceEventsInstall(root);
		assertServiceEventsResolve(root);
		assertServiceEventsStart(root);
		core.stop();
		assertServiceEventsStop(root);
		core.start();
		// There should be no install events or RESOLVING event since there
		// should be a persisted root subsystem already in the RESOLVED state.
		assertServiceEventResolved(root, ServiceEvent.REGISTERED);
		assertServiceEventsStart(root);
	}
	
	@Test
	public void testSymbolicName() {
		assertEquals("Wrong root symbolic name", getRootSubsystem().getSymbolicName(), "org.osgi.service.subsystem.root");
	}
	
	@Test
	public void testUninstallRootRegionBundleWithNoBundleEventHook() throws Exception {
		// Install an extraneous bundle into the root region. The bundle will
		// be recorded in the root subsystem's persistent memory.
		Bundle bundleA = bundleContext.installBundle(new File(BUNDLE_A).toURI().toURL().toString());
		try {
			Bundle core = getSubsystemCoreBundle();
			// Stop the subsystems bundle in order to unregister the bundle
			// event hook.
			core.stop();
			// Uninstall the bundle so it won't be there on restart.
			bundleA.uninstall();
			try {
				// Start the subsystems bundle and ensure the root subsystem
				// recovers from the uninstalled bundle being in persistent
				// memory.
				core.start();
			}
			catch (BundleException e) {
				fail("Could not start subsystems bundle after uninstalling a root region bundle with no bundle event hook registered");
			}
		}
		finally {
			if (Bundle.UNINSTALLED != bundleA.getState())
				bundleA.uninstall();
		}
	}
	
	@Test
	public void testVersion() {
		assertEquals("Wrong root version", getRootSubsystem().getVersion(), Version.parseVersion("1.0.0"));
	}
	
	/*
	 * The root subsystem should be associated with the region in which the
	 * subsystems implementation bundle is installed.
	 */
	@Test
	public void testRegion() throws Exception {
		RegionDigraph digraph = context().getService(RegionDigraph.class);
		Bundle core = getSubsystemCoreBundle();
		Region kernel = digraph.getRegion(core);
		Subsystem root = getRootSubsystem();
		Bundle rootRegionContext = root.getBundleContext().getBundle();
		// Get the region containing the subsystem's region context bundle, 
		// which is the same thing as getting the region with which the 
		// subsystem is associated.
		Region region = digraph.getRegion(root.getBundleContext().getBundle());
		assertEquals("Wrong region", kernel, region);
		// Uninstall the core bundle to remove the persisted root subsystem.
		core.uninstall();
		// Clean up the lingering region context bundle.
		rootRegionContext.uninstall();
		// Create a new region and install the core bundle into it.
		Region user = digraph.createRegion("user");
		// Allow everything from the kernel region into the user region so the 
		// core bundle will resolve.
		user.connectRegion(
				kernel, 
				digraph.createRegionFilterBuilder().allowAll(RegionFilter.VISIBLE_ALL_NAMESPACE).build());
		// Allow everything from the user region into the kernel region so the
		// root subsystem service can be found.
		kernel.connectRegion(
				user, 
				digraph.createRegionFilterBuilder().allowAll(RegionFilter.VISIBLE_ALL_NAMESPACE).build());
		core = user.installBundle(normalizeBundleLocation(core.getLocation()));
		user = digraph.getRegion(core);
		core.start();
		root = getRootSubsystem();
		region = digraph.getRegion(root.getBundleContext().getBundle());
		// The root subsystem should now be in the new region.
		assertEquals("Wrong region", user, region);
		// Extra test. Install application A into the root region (user) and 
		// make sure it resolves. Although the system bundle is in the kernel 
		// region and not a constituent of the root subsystem, the capability 
		// should still be found and used.
		try {
			Subsystem applicationA = installSubsystemFromFile(root, APPLICATION_A);
			uninstallSubsystemSilently(applicationA);
		}
		catch (Exception e) {
			e.printStackTrace();
			fail("Subsystem should have installed");
		}
	}
}
