/*
 * 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.assertNull;
import static org.junit.Assert.fail;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.aries.subsystem.core.archive.SubsystemImportServiceHeader;
import org.apache.aries.subsystem.itests.util.GenericMetadataWrapper;
import org.apache.aries.util.manifest.ManifestHeaderProcessor;
import org.apache.aries.util.manifest.ManifestHeaderProcessor.GenericMetadata;
import org.junit.Test;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.service.subsystem.Subsystem;
import org.osgi.service.subsystem.SubsystemConstants;
import org.osgi.service.subsystem.SubsystemException;

public class ServiceDependencyTest extends SubsystemTest {
	/*
	 * Subsystem-SymbolicName: application.a.esa
	 * Subsystem-Content: bundle.a.jar
	 */
	private static final String APPLICATION_A = "application.a.esa";
	/*
	 * Subsystem-SymbolicName: application.b.esa
	 * Subsystem-Content: bundle.b.jar
	 */
	private static final String APPLICATION_B = "application.b.esa";
	/*
	 * Subsystem-SymbolicName: application.b.esa
	 * Subsystem-Content: bundle.a.jar, bundle.b.jar
	 */
	private static final String APPLICATION_C = "application.c.esa";
	/*
	 * Subsystem-SymbolicName: application.d.esa
	 * Subsystem-Content: bundle.a.jar, composite.a.esa
	 */
	private static final String APPLICATION_D = "application.d.esa";
	/*
	 * Subsystem-SymbolicName: composite.a.esa
	 * Subsystem-Content: bundle.b.jar
	 */
	private static final String COMPOSITE_A = "composite.a.esa";
	/*
	 * Bundle-SymbolicName: bundle.a.jar
	 * Bundle-Blueprint: OSGI-INF/blueprint/*.xml
	 *
	 * <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
	 * 		<reference interface="bundle.b" filter="(&(active=true)(mode=shared))"/>
	 * 		<service interface="bundle.a" ref="bundle.a"/>
	 * </blueprint>
	 */
	private static final String BUNDLE_A = "bundle.a.jar";
	/*
	 * Bundle-SymbolicName: bundle.b.jar
	 * Bundle-Blueprint: OSGI-INF/blueprint/*.xml
	 *
	 * <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
	 * 		<reference interface="bundle.a" availability="optional"/>
	 * 		<service ref="bundle.b">
	 * 			<interfaces>
	 * 				<value>bundle.b</value>
	 * 				<value>bundle.b1</value>
	 * 			</interfaces>
	 * 			<service-properties>
	 * 				<entry key="active">
	 * 					<value type="java.lang.Boolean">true</value>
	 * 				</entry>
	 * 				<entry key="mode" value="shared"/>
	 * 			</service-properties>
	 * 		</service>
	 * </blueprint>
	 */
	private static final String BUNDLE_B = "bundle.b.jar";

	private static void createApplicationA() throws IOException {
		createApplicationAManifest();
		createSubsystem(APPLICATION_A, BUNDLE_A);
	}

	private static void createApplicationB() throws IOException {
		createApplicationBManifest();
		createSubsystem(APPLICATION_B, BUNDLE_B);
	}

	private static void createApplicationC() throws IOException {
		createApplicationCManifest();
		createSubsystem(APPLICATION_C, BUNDLE_A, BUNDLE_B);
	}

	private static void createApplicationD() throws IOException {
		createApplicationDManifest();
		createSubsystem(APPLICATION_D, BUNDLE_A, COMPOSITE_A);
	}

	private static void createApplicationAManifest() throws IOException {
		createBasicApplicationManifest(APPLICATION_A);
	}

	private static void createApplicationBManifest() throws IOException {
		createBasicApplicationManifest(APPLICATION_B);
	}

	private static void createApplicationCManifest() throws IOException {
		createBasicApplicationManifest(APPLICATION_C);
	}

	private static void createApplicationDManifest() throws IOException {
		createBasicApplicationManifest(APPLICATION_D);
	}

	private static void createBasicApplicationManifest(String symbolicName) throws IOException {
		createBasicSubsystemManifest(symbolicName, null, null);
	}

	private static void createBasicSubsystemManifest(String symbolicName, Version version, String type) throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, symbolicName);
		if (version != null)
			attributes.put(SubsystemConstants.SUBSYSTEM_VERSION, version.toString());
		if (type != null)
			attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, type);
		createManifest(symbolicName + ".mf", attributes);
	}



	private static void createBundleA() throws IOException {
		createBlueprintBundle(
				BUNDLE_A,
				new StringBuilder()
					.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
					.append("<blueprint ")
					.append("xmlns=\"http://www.osgi.org/xmlns/blueprint/v1.0.0\">")
					.append("<reference ")
					.append("interface=\"bundle.b\" ")
					.append("filter=\"(active=true)(mode=shared)\"")
					.append("/>")
					.append("<service ")
					.append("interface=\"bundle.a\" ")
					.append("ref=\"bundle.a\"")
					.append("/>")
					.append("</blueprint>")
					.toString());
	}

	private static void createBundleB() throws IOException {
		createBlueprintBundle(
				BUNDLE_B,
				new StringBuilder()
					.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
					.append("<blueprint ")
					.append("xmlns=\"http://www.osgi.org/xmlns/blueprint/v1.0.0\">")
					.append("<reference ")
					.append("interface=\"bundle.a\" ")
					.append("availability=\"optional\"")
					.append("/>")
					.append("<service ref=\"bundle.b\">")
					.append("<interfaces>")
					.append("<value>bundle.b</value>")
					.append("<value>bundle.b1</value>")
					.append("</interfaces>")
					.append("<service-properties>")
					.append("<entry key=\"active\">")
					.append("<value type=\"java.lang.Boolean\">true</value>")
					.append("</entry>")
					.append("<entry key=\"mode\" value=\"shared\"/>")
					.append("</service-properties>")
					.append("</service>")
					.append("</blueprint>")
					.toString());
	}

	private static void createCompositeA() throws IOException {
		createCompositeAManifest();
		createSubsystem(COMPOSITE_A, BUNDLE_B);
	}

	private static void createCompositeAManifest() throws IOException {
		Map<String, String> attributes = new HashMap<String, String>();
		attributes.put(SubsystemConstants.SUBSYSTEM_SYMBOLICNAME, COMPOSITE_A);
		attributes.put(SubsystemConstants.SUBSYSTEM_TYPE, SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE);
		attributes.put(
				SubsystemConstants.SUBSYSTEM_EXPORTSERVICE,
				"bundle.b;filter:=\"(&(active=true)(mode=shared))\"");
		createManifest(COMPOSITE_A + ".mf", attributes);
	}

	@Override
	public void createApplications() throws Exception {
		createBundleA();
		createBundleB();
		createApplicationA();
		createApplicationB();
		createApplicationC();
		createCompositeA();
		createApplicationD();
	}

	//@Test
	public void testImportServiceDependencySatisfiedByChild() throws Exception {
		try {
			Subsystem subsystem = installSubsystemFromFile(APPLICATION_D);
			try {
				assertNull(
						"Generated application Subsystem-ImportService header when dependency satisfied by child",
						subsystem.getSubsystemHeaders(null).get(SubsystemConstants.SUBSYSTEM_IMPORTSERVICE));
				assertSubsystemExportServiceHeader(
						subsystem.getChildren().iterator().next(),
						"bundle.b;filter:=\"(&(active=true)(mode=shared))\"");
			}
			finally {
				uninstallSubsystemSilently(subsystem);
			}
		}
		catch (SubsystemException e) {
			e.printStackTrace();
			fail("Installation must succeed if missing service dependency is satisfied");
		}
	}

	@Test
	public void testImportServiceDependencySatisfiedByContent() throws Exception {
		try {
			Subsystem subsystem = installSubsystemFromFile(APPLICATION_C);
			try {
				assertNull(
						"Generated application Subsystem-ImportService header when dependency satisfied by content",
						subsystem.getSubsystemHeaders(null).get(SubsystemConstants.SUBSYSTEM_IMPORTSERVICE));
			}
			finally {
				uninstallSubsystemSilently(subsystem);
			}
		}
		catch (SubsystemException e) {
			e.printStackTrace();
			fail("Installation must succeed if service dependency is satisfied");
		}
	}

	@Test
	public void testImportServiceDependencySatisfiedByParent() throws Exception {
		try {
			Subsystem parent = installSubsystemFromFile(APPLICATION_B);
			try {
				Subsystem child = installSubsystemFromFile(parent, APPLICATION_A);
				try {
					assertSubsystemImportServiceHeader(child, "bundle.b;filter:=\"(&(active=true)(mode=shared))\";resolution:=mandatory;cardinality:=single;effective:=active");
				}
				finally {
					uninstallSubsystemSilently(child);
				}
			}
			catch (SubsystemException e) {
				e.printStackTrace();
				fail("Installation must succeed if service dependency is satisfied");
			}
			finally {
				uninstallSubsystemSilently(parent);
			}
		}
		catch (SubsystemException e) {
			e.printStackTrace();
			fail("Installation must succeed if missing service dependency is optional");
		}
	}

	@Test
	public void testMissingImportServiceDependencyMandatory() throws Exception {
		try {
			Subsystem subsystem = installSubsystemFromFile(APPLICATION_A);
			uninstallSubsystemSilently(subsystem);
			fail("Installation must fail due to missing service dependency");
		}
		catch (SubsystemException e) {
			// Okay.
		}
	}

	@Test
	public void testMissingImportServiceDependencyOptional() throws Exception {
		try {
			Subsystem subsystem = installSubsystemFromFile(APPLICATION_B);
			try {
				assertSubsystemImportServiceHeader(subsystem, "bundle.a;resolution:=optional;cardinality:=single;effective:=active");
			}
			finally {
				uninstallSubsystemSilently(subsystem);
			}
		}
		catch (SubsystemException e) {
			e.printStackTrace();
			fail("Installation must succeed if missing service dependency is optional");
		}
	}

	private void assertSubsystemExportServiceHeader(Subsystem subsystem, String value) throws InvalidSyntaxException {
		String header = assertHeaderExists(subsystem, SubsystemConstants.SUBSYSTEM_EXPORTSERVICE);
		List<GenericMetadata> actual = ManifestHeaderProcessor.parseRequirementString(header);
		List<GenericMetadata> expected = ManifestHeaderProcessor.parseRequirementString(value);
		assertEquals("Wrong number of clauses", expected.size(), actual.size());
		for (int i = 0; i < expected.size(); i++)
			assertEquals("Wrong clause", new GenericMetadataWrapper(expected.get(i)), new GenericMetadataWrapper(actual.get(i)));
	}

	private void assertSubsystemImportServiceHeader(Subsystem subsystem, String value) throws InvalidSyntaxException {
		String header = assertHeaderExists(subsystem, SubsystemConstants.SUBSYSTEM_IMPORTSERVICE);
		SubsystemImportServiceHeader actual = new SubsystemImportServiceHeader(header);
		SubsystemImportServiceHeader expected = new SubsystemImportServiceHeader(value);
		Collection<SubsystemImportServiceHeader.Clause> actualClauses = actual.getClauses();
		Collection<SubsystemImportServiceHeader.Clause> expectedClauses = expected.getClauses();
		assertEquals("Wrong number of clauses", expectedClauses.size(), actualClauses.size());
		Iterator<SubsystemImportServiceHeader.Clause> actualItr = actualClauses.iterator();
		Iterator<SubsystemImportServiceHeader.Clause>  expectedItr = expectedClauses.iterator();
		while (expectedItr.hasNext()) {
			assertEquals("Wrong clause", expectedItr.next(), actualItr.next());
		}
	}
}
