/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.util.Collection;

import org.apache.aries.subsystem.core.internal.ResourceHelper;
import org.junit.Test;
import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
import org.ops4j.pax.exam.spi.reactors.PerMethod;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.resource.Resource;
import org.osgi.service.subsystem.Subsystem;

import junit.framework.AssertionFailedError;

@ExamReactorStrategy(PerMethod.class)
public class FeatureTest extends SubsystemTest {
	
	@Override
	public void createApplications() throws Exception {
		createApplication("feature2", new String[]{"tb2.jar", "tb3.jar"});
		createApplication("feature1", new String[]{"tb1.jar", "feature2.esa", "tb3.jar"});
		createApplication("feature3", new String[]{"tb3.jar"});
	}

	@Test
	public void testFeature1() throws Exception {
		Subsystem feature1 = installSubsystemFromFile("feature1.esa");
		Subsystem feature2 = null;
		AssertionError error = null;
		try {
			assertSymbolicName("org.apache.aries.subsystem.feature1", feature1);
			assertVersion("1.0.0", feature1);
			assertConstituents(3, feature1);
			assertChildren(1, feature1);
			feature2 = feature1.getChildren().iterator().next();
			assertEvent(feature2, Subsystem.State.INSTALLING, 5000);
			assertEvent(feature2, Subsystem.State.INSTALLED, 5000);
			assertSymbolicName("org.apache.aries.subsystem.feature2", feature2);
			assertVersion("1.0.0", feature2);
			assertConstituent(feature2, "org.apache.aries.subsystem.itests.tb2", Version.parseVersion("2.0.0"), IdentityNamespace.TYPE_BUNDLE);
			assertConstituent(feature2, "org.apache.aries.subsystem.itests.tb3", Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
			assertConstituents(2, feature2);
			assertChildren(0, feature2);
			startSubsystem(feature1);
			assertEvent(feature2, Subsystem.State.RESOLVING, 5000);
			assertEvent(feature2, Subsystem.State.RESOLVED, 5000);
			assertEvent(feature2, Subsystem.State.STARTING, 5000);
			assertEvent(feature2, Subsystem.State.ACTIVE, 5000);
			stopSubsystem(feature1);
			assertEvent(feature2, Subsystem.State.STOPPING, 5000);
			assertEvent(feature2, Subsystem.State.RESOLVED, 5000);
		}
		catch (AssertionError e) {
			error = e;
			throw e;
		}
		finally {
			try {
				uninstallSubsystem(feature1);
				if (feature2 != null) {
					assertEvent(feature2, Subsystem.State.INSTALLED, 5000);
					assertEvent(feature2, Subsystem.State.UNINSTALLING, 5000);
					assertEvent(feature2, Subsystem.State.UNINSTALLED, 5000);
					assertNotChild(feature1, feature2);
				}
			}
			catch (AssertionError e) {
				if (error == null)
					throw e;
				e.printStackTrace();
			}
		}
	}
	
	@Test
	public void testPersistence() throws Exception {
		Subsystem feature3Before = installSubsystemFromFile("feature3.esa");
		Subsystem feature3After = null;
		AssertionError error = null;
		try {
			assertFeature3(feature3Before);
			// Uninstall then reinstall the subsystem for a more robust test of the subsystem ID persistence.
			uninstallSubsystem(feature3Before);
			feature3Before = installSubsystemFromFile("feature3.esa");
			assertLastId(2);
			assertFeature3(feature3Before);
			Bundle bundle = getSubsystemCoreBundle();
			bundle.stop();
			resetLastId();
			bundle.start();
			Subsystem root = getRootSubsystem();
			assertChildren(1, root);
			feature3After = root.getChildren().iterator().next();
			assertLastId(2);
			assertFeature3(feature3After);
		}
		catch (AssertionError e) {
			error = e;
			throw e;
		}
		finally {
			try {
				if (feature3After != null) {
					uninstallSubsystem(feature3After);
				}
			}
			catch (AssertionError e) {
				if (error == null)
					throw e;
				e.printStackTrace();
			}
		}
	}
	
	@Test
	public void testSharedContent() throws Exception {
		Subsystem feature1 = installSubsystemFromFile("feature1.esa");
		AssertionError error = null;
		try {
			assertConstituent(feature1, "org.apache.aries.subsystem.itests.tb3", Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
			Subsystem feature2 = feature1.getChildren().iterator().next();
			// TODO This needs to be better implemented and put into a utility method on the superclass.
			while (!feature2.getState().equals(Subsystem.State.INSTALLED))
				Thread.sleep(100);
			assertConstituent(feature2, "org.apache.aries.subsystem.itests.tb3", Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
			uninstallSubsystem(feature2);
			assertNotChild(feature1, feature2);
			assertConstituent(feature1, "org.apache.aries.subsystem.itests.tb3", Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
		}
		catch (AssertionError e) {
			error = e;
			throw e;
		}
		finally {
			try {
				uninstallSubsystem(feature1);
			}
			catch (AssertionError e) {
				if (error == null)
					throw e;
				e.printStackTrace();
			}
		}
	}
	
	private void assertContainsConstituent(Collection<Resource> constituents, Resource constituent) {
		for (Resource resource : constituents) {
			if (ResourceHelper.areEqual(constituent, resource))
				return;
		}
		fail("Constituent not found");
	}
	
	private void assertContainsChild(Collection<Subsystem> children, Subsystem child) {
		for (Subsystem subsystem : children) {
			try {
				assertSubsystemsEquals(child, subsystem);
				return;
			}
			catch (AssertionError e) {}
		}
		fail("Child not found");
	}
	
	private void assertSubsystemsEquals(Subsystem subsystem1, Subsystem subsystem2) {
		assertChildrenEqual(subsystem1.getChildren(), subsystem2.getChildren());
		assertConstituentsEqual(subsystem1.getConstituents(), subsystem2.getConstituents());
		assertEquals("Headers were not equal", subsystem1.getSubsystemHeaders(null), subsystem2.getSubsystemHeaders(null));
		assertEquals("Locations were not equal", subsystem1.getLocation(), subsystem2.getLocation());
		assertParentsEqual(subsystem1.getParents(), subsystem2.getParents());
		assertEquals("States were not equal", subsystem1.getState(), subsystem2.getState());
		assertEquals("IDs were not equal", subsystem1.getSubsystemId(), subsystem2.getSubsystemId());
		assertEquals("Symbolic names were not equal", subsystem1.getSymbolicName(), subsystem2.getSymbolicName());
		assertEquals("Versions were not equal", subsystem1.getVersion(), subsystem2.getVersion());
	}
	
	private void assertParentsEqual(Subsystem parent1, Subsystem parent2) {
		if (parent1 == null || parent2 == null) {
			assertTrue("Parents were not equal", parent1 == null && parent2 == null);
			return;
		}
		assertConstituentsEqual(parent1.getConstituents(), parent2.getConstituents());
		assertEquals("Headers were not equal", parent1.getSubsystemHeaders(null), parent2.getSubsystemHeaders(null));
		assertEquals("Locations were not equal", parent1.getLocation(), parent2.getLocation());
		assertParentsEqual(parent1.getParents(), parent2.getParents());
		assertEquals("States were not equal", parent1.getState(), parent2.getState());
		assertEquals("IDs were not equal", parent1.getSubsystemId(), parent2.getSubsystemId());
		assertEquals("Symbolic names were not equal", parent1.getSymbolicName(), parent2.getSymbolicName());
		assertEquals("Versions were not equal", parent1.getVersion(), parent2.getVersion());
	}
	
	private void assertParentsEqual(Subsystem parent1, Collection<Subsystem> parents2) {
		for (Subsystem parent2 : parents2) {
			try {
				assertParentsEqual(parent1, parent2);
				return;
			}
			catch (AssertionFailedError e) {}
		}
		fail("Parent not found: " + parent1.getSymbolicName());
	}
	
	private void assertParentsEqual(Collection<Subsystem> parents1, Collection<Subsystem> parents2) {
		assertEquals("Size not equal", parents1.size(), parents2.size());
		for (Subsystem parent1 : parents1) {
			assertParentsEqual(parent1, parents2);
		}
	}
	
	private void assertConstituentsEqual(Collection<Resource> resources1, Collection<Resource> resources2) {
		assertEquals("Constituent size does not match", resources1.size(), resources2.size());
		for (Resource resource : resources1) {
			assertContainsConstituent(resources2, resource);
		}
	}
	
	private void assertChildrenEqual(Collection<Subsystem> subsystems1, Collection<Subsystem> subsystems2) {
		assertEquals("Children size does not match", subsystems1.size(), subsystems2.size());
		for (Subsystem subsystem : subsystems1) {
			assertContainsChild(subsystems2, subsystem);
		}
	}
	
	private void assertFeature3(Subsystem subsystem) {
		assertChildren(0, subsystem);
		assertConstituents(1, subsystem);
		assertConstituent(subsystem, "org.apache.aries.subsystem.itests.tb3", Version.parseVersion("1.0.0"), IdentityNamespace.TYPE_BUNDLE);
//		subsystem.getHeaders();
//		subsystem.getHeaders("");
//		subsystem.getState();
		assertSymbolicName("org.apache.aries.subsystem.feature3", subsystem);
		assertVersion("0.0.0", subsystem);
	}
}
