/*
 * 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.felix.dm.itest.api;

import java.util.List;
import java.util.Properties;

import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
import org.apache.felix.dm.ConfigurationDependency;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.ServiceDependency;
import org.apache.felix.dm.context.AbstractDependency;
import org.apache.felix.dm.context.DependencyContext;
import org.apache.felix.dm.diagnostics.CircularDependency;
import org.apache.felix.dm.diagnostics.DependencyGraph;
import org.apache.felix.dm.diagnostics.DependencyGraph.ComponentState;
import org.apache.felix.dm.diagnostics.DependencyGraph.DependencyState;
import org.apache.felix.dm.diagnostics.MissingDependency;
import org.apache.felix.dm.itest.util.TestBase;

public class DiagnosticsTest extends TestBase {
	
	// there are two components (TestComponent and HelloWorldServiceFactory) created by 
    // org.apache.felix.dm.itest.bundle
	// These component is always registered, so we only need to take them into 
	// account when we build the graph with ALL components
	private static final int InitialComponentCount = 2;
	
	private boolean checkComponentCount(int expected, int count) {
		return count == expected + InitialComponentCount;
	}
	
	public void testWithoutComponents() throws Exception {
		
		DependencyGraph graph = DependencyGraph.getGraph(
				ComponentState.ALL, 
				DependencyState.ALL);
	
		assertTrue(checkComponentCount(0, graph.getAllComponents().size()));
		assertTrue(graph.getAllDependencies().isEmpty());
	}
	
	public void testSingleComponent() throws Exception {
		
		DependencyManager dm = getDM();
		
		Component component = dm.createComponent()
								.setImplementation(Object.class);
							
		dm.add(component);
		
		DependencyGraph graph = DependencyGraph.getGraph(
				ComponentState.ALL, DependencyState.ALL);
		
		assertTrue(checkComponentCount(1, graph.getAllComponents().size()));
		assertTrue(graph.getAllDependencies().isEmpty());
		
		graph = DependencyGraph.getGraph(
				ComponentState.UNREGISTERED, DependencyState.ALL_UNAVAILABLE);
		
		assertTrue(graph.getAllComponents().isEmpty());
		assertTrue(graph.getAllDependencies().isEmpty());
	}

	public void testServiceDependencyMissing() throws Exception {
		
		DependencyManager dm = getDM();
		
		ServiceDependency serviceDependency1 = dm.createServiceDependency()
				.setService(S1.class)
				.setRequired(true);
		ServiceDependency serviceDependency2 = dm.createServiceDependency()
				.setService(S2.class)
				.setRequired(true);
		
		Component component1 = dm.createComponent()
				.setImplementation(C0.class)
				.add(serviceDependency1);
		Component component2 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null)
				.add(serviceDependency2);
		
		dm.add(component1);
		dm.add(component2);
		
		DependencyGraph graph = DependencyGraph.getGraph(
				ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
		
		assertEquals(2, graph.getAllComponents().size());
		assertEquals(2, graph.getAllDependencies().size());
		
		List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
		assertEquals(1, missingDependencies.size());
		assertEquals(S2.class.getName(), missingDependencies.get(0).getName());
		
		assertTrue(graph.getMissingDependencies("configuration").isEmpty());
		assertTrue(graph.getMissingDependencies("bundle").isEmpty());
		assertTrue(graph.getMissingDependencies("resource").isEmpty());
	}
	
	public void testConfigurationDependencyMissing() throws Exception {
		
		DependencyManager dm = getDM();
		
		ConfigurationDependency configurationDependency1 = dm.createConfigurationDependency()
				.setPid("missing.configuration.pid");
		
		Component component1 = dm.createComponent()
				.setImplementation(Object.class)
				.add(configurationDependency1);
		m_dm.add(component1);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
		
		assertEquals(1, graph.getAllComponents().size());
		assertEquals(1, graph.getAllDependencies().size());
		
		List<MissingDependency> missingServiceDependencies = graph.getMissingDependencies("service");
		assertTrue(missingServiceDependencies.isEmpty());
		
		List<MissingDependency> missingConfigDependencies = graph.getMissingDependencies("configuration");
		assertEquals(1, missingConfigDependencies.size());
		
		MissingDependency missingConfigDependency = missingConfigDependencies.get(0);
		assertEquals("missing.configuration.pid", missingConfigDependency.getName());
	}

	public void testProvidersWithoutProperties() throws Exception {
		DependencyManager dm = getDM();
		
		ServiceDependency serviceDependency1 = dm.createServiceDependency()
				.setService(S1.class)
				.setRequired(true);
		
		Component component1 = dm.createComponent()
				.setImplementation(C0.class)
				.add(serviceDependency1);
		
		Component component2 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null);
		Component component3 = dm.createComponent()
				.setImplementation(S1Impl2.class)
				.setInterface(S1.class.getName(), null);
		
		dm.add(component1);
		dm.add(component2);
		dm.add(component3);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
		
		List<ComponentDeclaration> providers = graph.getProviders(serviceDependency1);
		assertEquals(2, providers.size());
		assertTrue(providers.contains(component2));
		assertTrue(providers.contains(component3));
	}
	
	public void testProvidersWithProperties() throws Exception {
		
		DependencyManager dm = getDM();
		
		ServiceDependency serviceDependency1 = dm.createServiceDependency()
				.setService(S1.class, "(key=value)")
				.setRequired(true);
		
		Component component1 = dm.createComponent()
				.setImplementation(C0.class)
				.add(serviceDependency1);

		Properties component2Properties = new Properties();
		component2Properties.put("key", "value");
		Properties component4Properties = new Properties();
		component4Properties.put("key", "otherValue");

		Component component2 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), component2Properties);
		Component component3 = dm.createComponent()
				.setImplementation(S1Impl2.class)
				.setInterface(S1.class.getName(), null);
		Component component4 = dm.createComponent()
				.setImplementation(S1Impl3.class)
				.setInterface(S1.class.getName(), component4Properties);

		m_dm.add(component1);
		m_dm.add(component2);
		m_dm.add(component3);
		m_dm.add(component4);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
		
		List<ComponentDeclaration> providers = graph.getProviders(serviceDependency1);
		assertEquals(1, providers.size());
		assertTrue(providers.contains(component2));
		assertFalse(providers.contains(component3));
		assertFalse(providers.contains(component4));
		
	}

	public void testCircularDependencies() throws Exception {
		
		DependencyManager dm = getDM();
		
		Component component0 = dm.createComponent()
				.setImplementation(C0.class)
				.add(dm.createServiceDependency()
						.setService(S1.class)
						.setRequired(true));

		Component component1 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null)
				.add(dm.createServiceDependency()
						.setService(S2.class)
						.setRequired(true));
		Component component2 = dm.createComponent()
				.setImplementation(S2Impl1.class)
				.setInterface(S2.class.getName(), null)
				.add(dm.createServiceDependency()
						.setService(S1.class)
						.setRequired(true));
		
		m_dm.add(component0);
		m_dm.add(component1);
		m_dm.add(component2);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED,DependencyState.REQUIRED_UNAVAILABLE);
		List<CircularDependency> circularDependencies = graph.getCircularDependencies();
		
		assertEquals(1, circularDependencies.size());
		
		List<ComponentDeclaration> circularDependencyComponents = circularDependencies.get(0).getComponents(); 
		assertTrue(circularDependencyComponents.contains(component1));
		assertTrue(circularDependencyComponents.contains(component2));
		assertFalse(circularDependencyComponents.contains(component0));
		
	}
	
	public void testWithTwoProvidersOneUnavailable() {
		DependencyManager dm = getDM();
		
		Component component0 = dm.createComponent()
				.setImplementation(C0.class)
				.add(dm.createServiceDependency()
						.setService(S1.class)
						.setRequired(true));
		Component component1 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null);
		Component component2 = dm.createComponent()
				.setImplementation(S1Impl2.class)
				.setInterface(S1.class.getName(), null)
				.add(dm.createServiceDependency()
						.setService(S2.class)
						.setRequired(true));
		
		dm.add(component0);
		dm.add(component1);
		dm.add(component2);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
		
		assertEquals(1, graph.getAllComponents().size());
		List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
		assertEquals(1, missingDependencies.size());
	
		MissingDependency missingDependency = missingDependencies.get(0);
		assertTrue(missingDependency.getName().equals(S2.class.getName()));
		
	}
	
	public void testGraphsWithSeveralComponentDependencyCombinations() throws Exception {
		
		DependencyManager dm = getDM();
		
		Component component0 = dm.createComponent()
				.setImplementation(C0.class)
				.add(dm.createServiceDependency()
						.setService(S1.class)
						.setRequired(true))
				.add(dm.createServiceDependency()
						.setService(S3.class)
						.setRequired(true))
				.add(dm.createServiceDependency()
						.setService(S4.class)
						.setRequired(true));
		Component component1 = dm.createComponent()
				.setImplementation(C1.class)
				.add(dm.createServiceDependency()
						.setService(S5.class)
						.setRequired(true))
				.add(dm.createServiceDependency()
						.setService(S6.class)
						.setRequired(true))
				.add(dm.createServiceDependency()
						.setService(S7.class)
						.setRequired(true));
		Component s1Impl1 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null)
				.add(dm.createServiceDependency()
						.setService(S2.class)
						.setRequired(true));
		Component s1Impl2 = dm.createComponent()
				.setImplementation(S1Impl2.class)
				.setInterface(S1.class.getName(), null);

		Component s3Impl1 = dm.createComponent()
				.setImplementation(S3Impl1.class)
				.setInterface(S3.class.getName(), null)
				.add(dm.createConfigurationDependency()
						.setPid("missing.config.pid"));
		Component s3s4Impl = dm.createComponent()
				.setImplementation(S3S4Impl.class)
				.setInterface( new String[] {S3.class.getName(), S4.class.getName()}, null);
		Component s4Impl1 = dm.createComponent()
				.setImplementation(S4Impl1.class)
				.setInterface(S4.class.getName(), null);
		Component s5Impl1 = dm.createComponent()
				.setImplementation(S5Impl1.class)
				.setInterface(S5.class.getName(), null);
		Component s6s7Impl = dm.createComponent()
				.setImplementation(S6S7Impl.class)
				.setInterface( new String[] {S6.class.getName(), S7.class.getName()}, null)
				.add(dm.createServiceDependency()
						.setService(S8.class)
						.setRequired(true));
		Component s8Impl1 = dm.createComponent()
				.setImplementation(S8Impl1.class)
				.setInterface(S8.class.getName(), null)
				.add(dm.createServiceDependency()
						.setService(S6.class)
						.setRequired(true));
		dm.add(component0);
		dm.add(component1);
		dm.add(s1Impl1); dm.add(s1Impl2);
		dm.add(s3Impl1); dm.add(s3s4Impl);
		dm.add(s4Impl1); dm.add(s5Impl1);
		dm.add(s6s7Impl); dm.add(s8Impl1);
		
		
		// graph containing all components and dependencies
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL);
		
		List<ComponentDeclaration> allComponents = graph.getAllComponents();
		assertTrue(checkComponentCount(10, allComponents.size()));
		
		List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
		assertEquals(1, missingDependencies.size());
		
		missingDependencies = graph.getMissingDependencies("configuration");
		assertEquals(1, missingDependencies.size());
		
		List<CircularDependency> circularDependencies = graph.getCircularDependencies();
		assertEquals(1, circularDependencies.size());
		CircularDependency circularDependency = circularDependencies.get(0);

		assertEquals(3, circularDependency.getComponents().size());
		assertTrue(circularDependency.getComponents().contains(s6s7Impl));
		assertTrue(circularDependency.getComponents().contains(s8Impl1));

		// graph containing unregistered components and unavailable required dependencies
		graph = null;
		graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE);
	
		List<ComponentDeclaration> unregComponents = graph.getAllComponents();
		assertEquals(5, unregComponents.size());
		assertTrue(unregComponents.contains(s1Impl1));
		assertTrue(unregComponents.contains(s3Impl1));
		assertTrue(unregComponents.contains(component1));
		assertTrue(unregComponents.contains(s6s7Impl));
		assertTrue(unregComponents.contains(s8Impl1));
		assertFalse(unregComponents.contains(component0));
		
		circularDependencies = graph.getCircularDependencies();
		assertEquals(1, circularDependencies.size());
		circularDependency = circularDependencies.get(0);

		assertEquals(3, circularDependency.getComponents().size());
		assertTrue(circularDependency.getComponents().contains(s6s7Impl));
		assertTrue(circularDependency.getComponents().contains(s8Impl1));
		
		missingDependencies = graph.getMissingDependencies("service");
		assertEquals(1, missingDependencies.size());	
		
		missingDependencies = graph.getMissingDependencies("configuration");
		assertEquals(1, missingDependencies.size());

		// call getCircularDependencies again on the same graph
		circularDependencies = graph.getCircularDependencies();
		assertEquals(1, circularDependencies.size());
		circularDependency = circularDependencies.get(0);

		assertEquals(3, circularDependency.getComponents().size());
		assertTrue(circularDependency.getComponents().contains(s6s7Impl));
		assertTrue(circularDependency.getComponents().contains(s8Impl1));
		
		List<MissingDependency> allMissingDependencies = graph.getMissingDependencies(null);
		assertEquals(2, allMissingDependencies.size());

	}
	
	public void testMissingCustomDependencies() {
		
		DependencyManager dm = getDM();
		
		Component component0 = dm.createComponent()
				.setImplementation(C0.class)
				.add(dm.createServiceDependency()
						.setService(S1.class)
						.setRequired(true))
				.add(dm.createServiceDependency()
						.setService(S2.class)
						.setRequired(true));
		

		Component component1 = dm.createComponent()
				.setImplementation(S1Impl1.class)
				.setInterface(S1.class.getName(), null)
				.add(ToggleDependency.create());
		
		m_dm.add(component0);
		m_dm.add(component1);
		
		DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED,DependencyState.REQUIRED_UNAVAILABLE);
		
		List<MissingDependency> missingCustomDependencies = graph.getMissingCustomDependencies();
		assertEquals(1, missingCustomDependencies.size());
		
		MissingDependency missingCustomeDependency = missingCustomDependencies.get(0);
		assertTrue(missingCustomeDependency.getType().equals("toggle"));
		assertTrue(missingCustomeDependency.getName().equals("false"));
		
		List<MissingDependency> missingDependencies = graph.getMissingDependencies("service");
		assertEquals(1, missingDependencies.size());
	}
	
	
	static interface S1 {}
	static interface S2 {}
	static interface S3 {}
	static interface S4 {}
	static interface S5 {}
	static interface S6 {}
	static interface S7 {}
	static interface S8 {}
	static class C0 {
		public C0() {}
	}
	static class C1 {
		public C1() {}
	}
	static class S1Impl1 implements S1 {
		public S1Impl1(){}
	}
	static class S1Impl2 implements S1 {
		public S1Impl2() {}
	}
	static class S1Impl3 implements S1 {
		public S1Impl3() {}
	}
	static class S2Impl1 implements S2 {
		public S2Impl1() {}
	}
	static class S3Impl1 implements S3 {
		public S3Impl1() {}
	}
	static class S3S4Impl implements S3, S4 {
		public S3S4Impl() {}
	}
	static class S4Impl1 implements S4 {
		public S4Impl1() {}
	}
	static class S5Impl1 implements S5 {
		public S5Impl1() {}
	}
	static class S6S7Impl implements S6, S7 {
		public S6S7Impl() {}
	}
	static class S8Impl1 implements S8 {
		public S8Impl1() {}
	}
	
	static class CA {
		public CA(){}
	}
	
	static interface ToggleDependency extends Dependency {
		static ToggleDependency create() {
			return new ToggleDependencyImpl();
		}
		
	}
	
	static class ToggleDependencyImpl extends AbstractDependency<ToggleDependencyImpl> implements ToggleDependency {
		
		public ToggleDependencyImpl() {
			setRequired(true);
		}
		
		public ToggleDependencyImpl(ToggleDependencyImpl prototype) {
			super(prototype);
		}

		@Override
		public Class<?> getAutoConfigType() {
			return null;
		}

		@Override
		public DependencyContext createCopy() {
			return new ToggleDependencyImpl(this);
		}

		@Override
		public String getSimpleName() {
	        return "" + isAvailable();
		}

		@Override
		public String getType() {
	        return "toggle";
		}
		
	}

}
