/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.extender.internal.dependencies;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import junit.framework.TestCase;

import org.eclipse.gemini.blueprint.extender.internal.DependencyMockBundle;
import org.eclipse.gemini.blueprint.extender.internal.dependencies.shutdown.ServiceDependencySorter;
import org.osgi.framework.Bundle;
import org.springframework.util.ObjectUtils;

/**
 * Base testing suite for ordering bundles based on service dependencies. To
 * visualize the graph, see the .dot files in the same folder which can read
 * through Graphviz tool.
 *
 * @author Costin Leau
 */
public abstract class AbstractServiceDependencySorterTest extends TestCase {

	protected ServiceDependencySorter sorter;

	private int count = 1;

	protected void setUp() throws Exception {
		sorter = createSorter();
	}

	protected abstract ServiceDependencySorter createSorter();

	protected void tearDown() throws Exception {
		sorter = null;
	}

	// A -> B -> C
	public void testSimpleTree() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");
		DependencyMockBundle c = new DependencyMockBundle("C");

		// A -> B -> C
		a.setDependentOn(b);
		b.setDependentOn(c);

		testDependencyTreeWithShuffle(new Bundle[]{c, b, a}, new Bundle[]{a, b, c});
	}

	// A -> B, C, D
	// B -> C, E
	// C -> E
	// D -> B
	public void testMediumTree() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");
		DependencyMockBundle c = new DependencyMockBundle("C");
		DependencyMockBundle d = new DependencyMockBundle("D");
		DependencyMockBundle e = new DependencyMockBundle("E");

		a.setDependentOn(new Bundle[]{d, c, b});
		b.setDependentOn(new Bundle[]{e, c});
		c.setDependentOn(e);
		d.setDependentOn(b);

		testDependencyTreeWithShuffle(new Bundle[]{e, c, b, d, a}, new Bundle[]{a, b, c, d, e});
	}

	// A -> B, C, D
	// B -> C
	// D -> B, E
	// E -> F, G
	// F -> G
	// H -> G
	// I -> H, J

	// depending on the order there are multiple shutdown orders
	public void testLargeTree() {
		DependencyMockBundle a = new DependencyMockBundle("a");
		DependencyMockBundle b = new DependencyMockBundle("b");
		DependencyMockBundle c = new DependencyMockBundle("c");
		DependencyMockBundle d = new DependencyMockBundle("d");
		DependencyMockBundle e = new DependencyMockBundle("e");
		DependencyMockBundle f = new DependencyMockBundle("f");
		DependencyMockBundle g = new DependencyMockBundle("g");
		DependencyMockBundle h = new DependencyMockBundle("h");
		DependencyMockBundle i = new DependencyMockBundle("i");
		DependencyMockBundle j = new DependencyMockBundle("j");

		a.setDependentOn(new Bundle[]{b, c, d});
		b.setDependentOn(c);
		d.setDependentOn(new Bundle[]{b, e});
		e.setDependentOn(new Bundle[]{f, g});
		f.setDependentOn(g);
		h.setDependentOn(g);
		i.setDependentOn(new Bundle[]{h, j});

		testDependencyTree(new Bundle[]{g, f, e, c, b, d, a, h, j, i}, new Bundle[]{a, b, c, d, e, f,
			g, h, i, j});
	}

	// A -> B,D
	// B -> C, E
	// C
	// D -> B, C
	// E -> C
	public void testComplexTree() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");
		DependencyMockBundle c = new DependencyMockBundle("C");
		DependencyMockBundle d = new DependencyMockBundle("D");
		DependencyMockBundle e = new DependencyMockBundle("E");

		a.setDependentOn(new Bundle[]{b, d});
		b.setDependentOn(new Bundle[]{c, e});
		d.setDependentOn(new Bundle[]{b, c});
		e.setDependentOn(new Bundle[]{c});

		testDependencyTreeWithShuffle(new Bundle[]{c, e, b, d, a}, new Bundle[]{a, b, c, d, e});
	}

	// Although this is an interesting test, the shutdown logic does not require that
	// it pass and the current algorithm does not handle this case.
	public void XtestMissingMiddle() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");
		DependencyMockBundle D = new DependencyMockBundle("D");
		DependencyMockBundle E = new DependencyMockBundle("E");

		// Sets dependency A -> B -> C -> D -> E
		B.setDependentOn(A);
		C.setDependentOn(new Bundle[]{B});
		D.setDependentOn(new Bundle[]{C});
		E.setDependentOn(new Bundle[]{D});

		testDependencyTree(new Bundle[]{A, C, E}, new Bundle[]{C, E, A});
	}

	// //////////
	// CIRCULAR TREES
	// /////////

	// A -> B (id = 1)
	// B -> A (id = 2)
	// B -> A has to be stopped first since it was created last (highest id)
	public void testSimpleCircularTreeTieOnServiceRankingUsingServiceId() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");

		b.setDependentOn(a, 0, 2);
		a.setDependentOn(b, 0, 1);

		Bundle[] expectedVer1 = new Bundle[]{a, b};
		Bundle[] expectedVer2 = new Bundle[]{b, a};

		// we should get the same order always (B should be stopped first)
		assertTrue(Arrays.equals(expectedVer2, sorter.computeServiceDependencyGraph(expectedVer1)));
		assertTrue(Arrays.equals(expectedVer2, sorter.computeServiceDependencyGraph(expectedVer2)));
	}

	public void testSimpleCircularTreeUsingServiceRanking() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");

		b.setDependentOn(a);
		a.setDependentOn(b);

		testDependencyTreeWithShuffle(new Bundle[]{a, b}, new Bundle[]{a, b});
	}

	// A -> B, C
	// B -> C, D
	// C -> D
	// D -> A
	public void testMediumCircularCycle() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");
		DependencyMockBundle c = new DependencyMockBundle("C");
		DependencyMockBundle d = new DependencyMockBundle("D");

		a.setDependentOn(new Bundle[]{b, c});
		b.setDependentOn(new Bundle[]{c, d});
		c.setDependentOn(d);
		d.setDependentOn(a);

		testDependencyTreeWithShuffle(new Bundle[]{d, c, b, a}, new Bundle[]{a, b, c, d});
	}

	// A -> B, D
	// B -> C, E
	// C -> D
	// D -> B, C
	// E -> C
	// E should be stopped first, since it is the last service started
	public void testComplexCyclicTree() {
		DependencyMockBundle a = new DependencyMockBundle("A");
		DependencyMockBundle b = new DependencyMockBundle("B");
		DependencyMockBundle c = new DependencyMockBundle("C");
		DependencyMockBundle d = new DependencyMockBundle("D");
		DependencyMockBundle e = new DependencyMockBundle("E");

		a.setDependentOn(new Bundle[]{b, d});
		b.setDependentOn(new Bundle[]{c, e});
		c.setDependentOn(d);
		d.setDependentOn(new Bundle[]{b, c});
		e.setDependentOn(c);

		testDependencyTreeWithShuffle(new Bundle[]{e, d, c, b, a}, new Bundle[]{a, b, c, d, e});
	}

	public void testCircularReferenceId() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");
		DependencyMockBundle D = new DependencyMockBundle("D");
		DependencyMockBundle E = new DependencyMockBundle("E");

		// Sets dependency A -> B -> C -> D -> E -> A
		// A has lowest id so gets shutdown last (started first).
		A.setDependentOn(new Bundle[]{E}, 0, 0);
		B.setDependentOn(new Bundle[]{A}, 0, 1);
		C.setDependentOn(new Bundle[]{B}, 0, 2);
		D.setDependentOn(new Bundle[]{C}, 0, 3);
		E.setDependentOn(new Bundle[]{D}, 0, 4);

		testDependencyTreeWithShuffle(new Bundle[]{E, D, C, B, A}, new Bundle[]{E, D, C, B, A});
	}

	public void tstCircularReferenceIdMulti() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");

		// Sets dependency A -> C -> B -> A
		// A has lowest id so gets shutdown last (started first).
		// B should go first since its service was started last (id = 4)
		A.setDependentOn(new Bundle[]{C}, 0, 0);
		B.setDependentOn(new Bundle[]{A, A}, new int[]{0, 0}, new long[]{4, 1});
		C.setDependentOn(new Bundle[]{B}, 0, 2);

		testDependencyTreeWithShuffle(new Bundle[]{B, C, A}, new Bundle[]{C, B, A});
	}

	public void testCircularReferenceRankMulti() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");

		// Sets dependency A -> B -> C -> A
		// B has highest rank so gets shutdown last (its started first).
		// C should go second since its service has the second ranking (2)
		// which means A should go first
		A.setDependentOn(new Bundle[]{B}, 0, 0);
		B.setDependentOn(new Bundle[]{C, C}, new int[]{0, 3}, new long[]{0, 0});
		C.setDependentOn(new Bundle[]{A}, 2, 0);

		testDependencyTreeWithShuffle(new Bundle[]{A, C, B}, new Bundle[]{C, B, A});
	}

	public void testCircularReferenceReference() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");
		DependencyMockBundle D = new DependencyMockBundle("D");
		DependencyMockBundle E = new DependencyMockBundle("E");

		// Sets dependency A -> B -> C -> D -> E -> A
		// A has higher ranking so gets shutdown last

		// A -> E
		// B -> A
		// C -> B
		// D -> C
		// E -> D
		// E -> D -> C -> B -> A -> E
		A.setDependentOn(new Bundle[]{E}, 4, 0);
		B.setDependentOn(new Bundle[]{A}, 3, 1);
		C.setDependentOn(new Bundle[]{B}, 2, 2);
		D.setDependentOn(new Bundle[]{C}, 1, 3);
		E.setDependentOn(new Bundle[]{D}, 0, 4);

		testDependencyTreeWithShuffle(new Bundle[]{E, D, C, B, A}, new Bundle[]{E, D, C, B, A});
	}

	public void testForest() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");
		DependencyMockBundle D = new DependencyMockBundle("D");
		DependencyMockBundle E = new DependencyMockBundle("E");
		DependencyMockBundle F = new DependencyMockBundle("F");
		DependencyMockBundle G = new DependencyMockBundle("G");
		DependencyMockBundle H = new DependencyMockBundle("H");
		DependencyMockBundle I = new DependencyMockBundle("I");
		DependencyMockBundle J = new DependencyMockBundle("J");

		// Sets dependency A -> B -> C, B -> D -> E
		A.setDependentOn(new Bundle[]{B});
		B.setDependentOn(new Bundle[]{C, D});
		D.setDependentOn(new Bundle[]{E});

		// Sets dependency F -> G, F -> H
		F.setDependentOn(new Bundle[]{G, H});

		// Sets dependency I -> J
		I.setDependentOn(new Bundle[]{J});

		testDependencyTree(new Bundle[]{G, H, F, E, D, J, C, B, A, I}, new Bundle[]{F, D, J, B, E, A,
			H, I, G, C});
	}

	public void testInversedForest() throws Exception {
		DependencyMockBundle A = new DependencyMockBundle("A");
		DependencyMockBundle B = new DependencyMockBundle("B");
		DependencyMockBundle C = new DependencyMockBundle("C");
		DependencyMockBundle D = new DependencyMockBundle("D");
		DependencyMockBundle E = new DependencyMockBundle("E");
		DependencyMockBundle F = new DependencyMockBundle("F");
		DependencyMockBundle G = new DependencyMockBundle("G");
		DependencyMockBundle H = new DependencyMockBundle("H");
		DependencyMockBundle I = new DependencyMockBundle("I");
		DependencyMockBundle J = new DependencyMockBundle("J");

		// C -> B -> A, E -> D -> B
		B.setDependentOn(new Bundle[]{A});
		C.setDependentOn(new Bundle[]{B});
		D.setDependentOn(new Bundle[]{B});
		E.setDependentOn(new Bundle[]{D});

		// Sets dependency G -> F, H -> F
		G.setDependentOn(new Bundle[]{F});
		H.setDependentOn(new Bundle[]{F});

		// Sets dependency J -> I
		J.setDependentOn(new Bundle[]{I});

		testDependencyTree(new Bundle[]{F, A, B, D, I, J, E, H, G, C}, new Bundle[]{F, D, J, B, E, A,
			H, I, G, C});
	}

	/**
	 * Test the resulting tree after shuffling the input bundles several times.
	 *
	 * @param expected
	 * @param bundles
	 * @return
	 */
	protected void testDependencyTree(Bundle[] expected, Bundle[] bundles) {
		Bundle[] tree = sorter.computeServiceDependencyGraph(bundles);
		assertTrue("array [" + ObjectUtils.nullSafeToString(tree) + "] does not match ["
			+ ObjectUtils.nullSafeToString(expected) + "] for input [" + ObjectUtils.nullSafeToString(bundles)
			+ "]", Arrays.equals(expected, tree));

	}

	protected void testDependencyTreeWithShuffle(Bundle[] expected, Bundle[] bundles) {
		List input = new ArrayList(bundles.length);

		for (int i = 0; i < bundles.length; i++) {
			input.add(bundles[i]);
		}

		// shuffle based on the number of elements
		for (int i = 0; i < bundles.length; i++) {
			testDependencyTree(expected, (Bundle[]) input.toArray(new Bundle[bundles.length]));
			Collections.shuffle(input);
		}

		count += bundles.length;
	}

	public int countTestCases() {
		return count;
	}

}
