/*******************************************************************************
 * Copyright (c) 2006, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Alexander Kurtakov <akurtako@redhat.com> - bug 458490
 *******************************************************************************/
package org.eclipse.core.tests.runtime;

import java.util.*;
import junit.framework.TestCase;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.junit.Assert;

/**
 *
 */
public class SubProgressTest extends TestCase {

	private long startTime;
	/**
	 * <p>Depth of the chain chain of progress monitors. In all of the tests, we create
	 * a nested chain of progress monitors rathar than a single monitor, to test its
	 * scalability under recursion. We pick a number representing a moderately deep
	 * recursion, but is still small enough that it could correspond to a real call stack
	 * without causing overflow.</p>
	 *
	 * <p>Note: changing this constant will invalidate comparisons with old performance data.</p>
	 */
	public static final int CHAIN_DEPTH = 100;
	/**
	 * <p>Number of calls to worked() within each test. This was chosen to be significantly larger
	 * than 1000 to test how well the monitor can optimize unnecessary resolution
	 * in reported progress, but small enough that the test completes in a reasonable
	 * amount of time.</p>
	 *
	 * <p>Note: changing this constant will invalidate comparisons with old performance data.</p>
	 */
	public static final int PROGRESS_SIZE = 100000;

	public SubProgressTest() {
		super();
	}

	public SubProgressTest(String name) {
		super(name);
	}

	@Override
	protected void setUp() throws Exception {
		startTime = System.currentTimeMillis();
		super.setUp();
	}

	@Override
	protected void tearDown() throws Exception {
		long endTime = System.currentTimeMillis();
		SubMonitorTest.reportPerformance(getClass().getName(), getName(), startTime, endTime);
		super.tearDown();
	}

	/**
	 * Calls done on the given progress monitor and all of its parents, to a maximum
	 * of the given depth.
	 *
	 * @deprecated to suppress deprecation warnings
	 *
	 * @param monitor
	 * @param depth
	 */
	public static void callDoneOnChain(IProgressMonitor monitor, int depth) {
		IProgressMonitor current = monitor;
		for (int count = 0; count < depth; count++) {
			current.done();
			if (!(current instanceof SubProgressMonitor))
				return;
			SubProgressMonitor cur = (SubProgressMonitor) current;
			current = cur.getWrappedProgressMonitor();
		}
	}

	/**
	 * Test behaviors that subclasses of SubProgressMonitor will expect from their base class.
	 * @deprecated to suppress deprecation warnings
	 */
	public void testCustomSubclass() {
		TestProgressMonitor top = new TestProgressMonitor();
		top.beginTask("", 1000);

		SubProgressSubclass customSubclass = new SubProgressSubclass(top, 1000);
		customSubclass.beginTask("", 10000);

		for (int count = 0; count < 10000; count++)
			customSubclass.worked(1);

		Assert.assertEquals("If there is a custom subclass of SubProgressMonitor, all calls to worked() should delegate to internalWorked", 10000, customSubclass.internalWorkedCalls);
		customSubclass.done();
		top.done();
	}

	/**
	 * Tests the style bits in SubProgressMonitor
	 * @deprecated to suppress deprecation warnings
	 */
	public void testStyles() {

		int[] styles = new int[] {0, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK | SubProgressMonitor.SUPPRESS_SUBTASK_LABEL};

		HashMap<String, String[]> expected = new HashMap<>();
		expected.put("style 0 below style 2", new String[] {"setTaskName0", "", "setTaskName1"});
		expected.put("style 2 below style 0", new String[] {"setTaskName1", "beginTask1 ", "setTaskName1"});
		expected.put("style 6 below style 0", new String[] {"setTaskName1", "beginTask1 ", "setTaskName1"});
		expected.put("style 2 below style 4", new String[] {"setTaskName1", "beginTask0 beginTask1 ", "setTaskName1"});
		expected.put("style 0 below style 0", new String[] {"setTaskName0", "subTask1", "setTaskName1"});
		expected.put("style 6 as top-level monitor", new String[] {"", "", "setTaskName0"});
		expected.put("style 6 below style 2", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 6 below style 6", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 0 below style 6", new String[] {"setTaskName0", "", "setTaskName1"});
		expected.put("style 4 below style 2", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 0 as top-level monitor", new String[] {"", "subTask0", "setTaskName0"});
		expected.put("style 0 below style 4", new String[] {"setTaskName0", "beginTask0 subTask1", "setTaskName1"});
		expected.put("style 4 below style 0", new String[] {"setTaskName1", "beginTask1 subTask1", "setTaskName1"});
		expected.put("style 4 as top-level monitor", new String[] {"", "beginTask0 subTask0", "setTaskName0"});
		expected.put("style 2 below style 6", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 4 below style 6", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 2 below style 2", new String[] {"setTaskName1", "", "setTaskName1"});
		expected.put("style 2 as top-level monitor", new String[] {"", "", "setTaskName0"});
		expected.put("style 6 below style 4", new String[] {"setTaskName1", "beginTask0 beginTask1 ", "setTaskName1"});
		expected.put("style 4 below style 4", new String[] {"setTaskName1", "beginTask0 beginTask1 subTask1", "setTaskName1"});
		HashMap<String, String[]> results = new HashMap<>();

		for (int style : styles) {
			TestProgressMonitor top = new TestProgressMonitor();
			top.beginTask("", 100);
			SubProgressMonitor child = new SubProgressMonitor(top, 100, style);

			String testName = "style " + style + " as top-level monitor";
			results.put(testName, runChildTest(0, top, child, 100 * styles.length));

			for (int innerStyle : styles) {
				SubProgressMonitor innerChild = new SubProgressMonitor(child, 100, innerStyle);
				testName = "style " + innerStyle + " below style " + style;
				results.put(testName, runChildTest(1, top, innerChild, 100));
				innerChild.done();
			}
			child.done();
		}

		String failure = null;
		// Output the code for the observed results, in case one of them has changed intentionally
		for (Map.Entry<String, String[]> entry : results.entrySet()) {
			String[] expectedResult = expected.get(entry.getKey());
			String[] value = entry.getValue();
			if (compareArray(value, expectedResult))
				continue;

			System.out.print("expected.put(\"" + entry.getKey() + "\", new String[] {");
			failure = entry.getKey();
			String list = concatArray(value);
			System.out.println(list + "});");
		}

		if (failure != null) // Now actually throw an assertation if one of the results failed
			Assert.assertEquals(failure, concatArray(expected.get(failure)), concatArray(results.get(failure)));
	}

	private boolean compareArray(String[] value, String[] expectedResult) {
		if (value.length != expectedResult.length)
			return false;
		for (int i = 0; i < expectedResult.length; i++) {
			String next = expectedResult[i];
			if (!next.equals(value[i]))
				return false;
		}
		return true;
	}

	private String concatArray(String[] value) {
		StringBuilder buf = new StringBuilder();
		boolean isFirst = true;
		for (String nextValue : value) {
			if (!isFirst)
				buf.append(", ");
			isFirst = false;
			buf.append("\"" + nextValue + "\"");
		}
		String list = buf.toString();
		return list;
	}

	private String[] runChildTest(int depth, TestProgressMonitor root, IProgressMonitor child, int ticks) {
		ArrayList<String> results = new ArrayList<>();
		child.beginTask("beginTask" + depth, ticks);
		results.add(root.getTaskName());
		child.subTask("subTask" + depth);
		results.add(root.getSubTaskName());
		child.setTaskName("setTaskName" + depth);
		results.add(root.getTaskName());
		return results.toArray(new String[results.size()]);
	}

	/**
	 * Tests SubProgressMonitor nesting when using the default constructor. (Tests
	 * parents in floating point mode)
	 * @deprecated to suppress deprecation warnings
	 */
	public void testConstructorNestingFP() {
		TestProgressMonitor top = new TestProgressMonitor();
		top.beginTask("", 2000);

		// Create an SPM, put it in floating-point mode, and consume half its work
		SubProgressMonitor fpMonitor = new SubProgressMonitor(top, 1000);
		fpMonitor.beginTask("", 100);
		fpMonitor.internalWorked(50.0);
		fpMonitor.internalWorked(-10.0); // should have no effect

		Assert.assertEquals(500.0, top.getTotalWork(), 0.01d);

		// Create a child monitor, and ensure that it grabs the correct amount of work
		// from the parent.
		SubProgressMonitor childMonitor = new SubProgressMonitor(fpMonitor, 20);
		childMonitor.beginTask("", 100);
		childMonitor.worked(100);
		childMonitor.done();

		Assert.assertEquals(700.0, top.getTotalWork(), 0.01d);

		// Create a child monitor, and ensure that it grabs the correct amount of work
		// from the parent.
		SubProgressMonitor childMonitor2 = new SubProgressMonitor(fpMonitor, 30);
		childMonitor2.beginTask("", 100);
		childMonitor2.worked(100);
		childMonitor2.done();

		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);

		// Ensure that creating another child will have no effect
		SubProgressMonitor childMonitor3 = new SubProgressMonitor(fpMonitor, 10);
		childMonitor3.beginTask("", 100);
		childMonitor3.worked(100);
		childMonitor3.done();

		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
		fpMonitor.worked(100);
		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
		fpMonitor.done();
		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
	}

	/**
	 * Tests SubProgressMonitor nesting when using the default constructor. Tests constructors
	 * in int mode.
	 * @deprecated to suppress deprecation warnings
	 */
	public void testConstructorNestingInt() {
		TestProgressMonitor top = new TestProgressMonitor();
		top.beginTask("", 2000);

		// Create an SPM leave it in int mode, and consume half its work
		SubProgressMonitor fpMonitor = new SubProgressMonitor(top, 1000);
		fpMonitor.beginTask("", 100);
		fpMonitor.worked(50);

		Assert.assertEquals(500.0, top.getTotalWork(), 0.01d);

		// Create a child monitor, and ensure that it grabs the correct amount of work
		// from the parent.
		SubProgressMonitor childMonitor = new SubProgressMonitor(fpMonitor, 20);
		childMonitor.beginTask("", 100);
		childMonitor.worked(100);
		childMonitor.done();

		Assert.assertEquals(700.0, top.getTotalWork(), 0.01d);

		// Create a child monitor, and ensure that it grabs the correct amount of work
		// from the parent.
		SubProgressMonitor childMonitor2 = new SubProgressMonitor(fpMonitor, 30);
		childMonitor2.beginTask("", 100);
		childMonitor2.worked(100);
		childMonitor2.done();

		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);

		// Ensure that creating another child will have no effect
		SubProgressMonitor childMonitor3 = new SubProgressMonitor(fpMonitor, 10);
		childMonitor3.beginTask("", 100);
		childMonitor3.worked(100);
		childMonitor3.done();

		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
		fpMonitor.worked(100);
		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
		fpMonitor.done();
		Assert.assertEquals(1000.0, top.getTotalWork(), 0.01d);
	}

	/**
	 * Tests the automatic cleanup when progress monitors are created via their constructor
	 * @deprecated to suppress deprecation warnings
	 */
	public void testParallelChildren() {
		TestProgressMonitor top = new TestProgressMonitor();
		top.beginTask("", 1000);
		SubProgressMonitor mon = new SubProgressMonitor(top, 1000);
		mon.beginTask("", 1000);

		SubProgressMonitor monitor1 = new SubProgressMonitor(mon, 200);
		SubProgressMonitor monitor2 = new SubProgressMonitor(mon, 200);

		Assert.assertEquals("Ensure no work has been reported yet", 0.0, top.getTotalWork(), 0.01d);
		monitor1.beginTask("", 1000);
		Assert.assertEquals("Ensure no work has been reported yet", 0.0, top.getTotalWork(), 0.01d);
		monitor2.beginTask("", 1000);
		Assert.assertEquals("Should not have cleaned up monitor 1", 0.0, top.getTotalWork(), 0.01d);
		monitor1.done();

		Assert.assertEquals("Should have cleaned up monitor 1", 200.0, top.getTotalWork(), 0.01d);
		monitor1.worked(1000);
		Assert.assertEquals("Monitor1 shouldn't report work once it's complete", 200.0, top.getTotalWork(), 0.01d);
		monitor2.worked(500);
		Assert.assertEquals(300.0, top.getTotalWork(), 0.01d);

		// Create a monitor that will leak - monitors won't be auto-completed until their done methods are
		// called
		SubProgressMonitor monitor3 = new SubProgressMonitor(mon, 300);
		Assert.assertEquals("Monitor2 should not have been cleaned up yet", 300.0, top.getTotalWork(), 0.01d);
		SubProgressMonitor monitor4 = new SubProgressMonitor(mon, 300);
		monitor4.beginTask("", 100);
		mon.done();
		Assert.assertNotNull(monitor3);

		Assert.assertEquals("All leaked work should have been collected", 1000.0, top.getTotalWork(), 0.01d);
	}

	/**
	 * @deprecated to suppress deprecation warnings
	 */
	public void testCancellation() {
		TestProgressMonitor root = new TestProgressMonitor();
		root.beginTask("", 1000);

		SubProgressMonitor spm = new SubProgressMonitor(root, 1000);

		// Test that changes at the root propogate to the child
		root.setCanceled(true);
		Assert.assertTrue(spm.isCanceled());
		root.setCanceled(false);
		Assert.assertFalse(spm.isCanceled());

		// Test that changes to the child propogate to the root
		spm.setCanceled(true);
		Assert.assertTrue(root.isCanceled());
		spm.setCanceled(false);
		Assert.assertFalse(root.isCanceled());

		// Test a chain of depth 2
		spm.beginTask("", 1000);
		SubProgressMonitor spm2 = new SubProgressMonitor(spm, 1000);

		// Test that changes at the root propogate to the child
		root.setCanceled(true);
		Assert.assertTrue(spm2.isCanceled());
		root.setCanceled(false);
		Assert.assertFalse(spm2.isCanceled());

		// Test that changes to the child propogate to the root
		spm2.setCanceled(true);
		Assert.assertTrue(root.isCanceled());
		spm2.setCanceled(false);
		Assert.assertFalse(root.isCanceled());
	}

	/**
	 * Tests creating progress monitors under a custom progress monitor
	 * parent. This is the same as the performance test as the same name,
	 * but it verifies correctness rather than performance.
	 */
	public void testCreateChildrenUnderCustomParent() {
		TestProgressMonitor monitor = new TestProgressMonitor();
		createChildrenUnderParent(monitor, SubProgressTest.PROGRESS_SIZE);

		// We don't actually expect the progress to be optimal in this case since the progress monitor wouldn't
		// know what it was rooted under and would have had to report more progress than necessary... but we
		// should be able to check that there was no redundancy.

		Assert.assertTrue(monitor.getRedundantWorkCalls() == 0);
		Assert.assertTrue(monitor.getWorkCalls() >= 100);
	}

	/**
	 * Creates a chain of n nested progress monitors. Calls beginTask on all monitors
	 * except for the innermost one.
	 *
	 * @deprecated to suppress deprecation warnings
	 *
	 * @param parent
	 * @param depth
	 * @return the innermost SubProgressMonitor
	 */
	private static SubProgressMonitor createSubProgressChain(IProgressMonitor parent, int depth) {
		SubProgressMonitor current;
		do {
			parent.beginTask("", 100);
			current = new SubProgressMonitor(parent, 100);
			parent = current;
			depth--;
		} while (depth > 0);
		return current;
	}

	/**
	 * Reports progress by iterating over a loop of the given size, reporting 1 progress
	 * at each iteration. Simulates the progress of worked(int) in loops.
	 *
	 * @param monitor progress monitor (callers are responsible for calling done() if necessary)
	 * @param loopSize size of the loop
	 */
	private static void reportWorkInLoop(IProgressMonitor monitor, int loopSize) {
		monitor.beginTask("", loopSize);
		for (int i = 0; i < loopSize; i++)
			monitor.worked(1);
	}

	/**
	 * Reports progress by iterating over a loop of the given size, reporting 1 progress
	 * at each iteration. Simulates the progress of internalWorked(double) in loops.
	 *
	 * @param monitor progress monitor (callers are responsible for calling done() if necessary)
	 * @param loopSize size of the loop
	 */
	private static void reportFloatingPointWorkInLoop(IProgressMonitor monitor, int loopSize) {
		monitor.beginTask("", loopSize);
		for (int i = 0; i < loopSize; i++)
			monitor.internalWorked(1.0d);
	}

	/**
	 * Reports progress by creating a balanced binary tree of progress monitors. Simulates
	 * mixed usage of IProgressMonitor in a typical usage. Calls isCanceled once each time work
	 * is reported. Half of the work is reported using internalWorked and half is reported using worked,
	 * to simulate mixed usage of the progress monitor.
	 *
	 * @deprecated to suppress deprecation warnings
	 *
	 * @param monitor progress monitor (callers are responsible for calling done() if necessary)
	 * @param loopSize total size of the recursion tree
	 */
	public static void reportWorkInBalancedTree(IProgressMonitor monitor, int loopSize) {
		monitor.beginTask("", 100);
		int leftBranch = loopSize / 2;
		int rightBranch = loopSize - leftBranch;

		if (leftBranch > 1) {
			SubProgressMonitor leftProgress = new SubProgressMonitor(monitor, 50);
			reportWorkInBalancedTree(leftProgress, leftBranch);
			leftProgress.done();
		} else {
			monitor.worked(25);
			monitor.internalWorked(25.0);
			monitor.isCanceled();
		}

		if (rightBranch > 1) {
			SubProgressMonitor rightProgress = new SubProgressMonitor(monitor, 50);
			reportWorkInBalancedTree(rightProgress, rightBranch);
			rightProgress.done();
		} else {
			monitor.worked(25);
			monitor.internalWorked(25.0);
			monitor.isCanceled();
		}
	}

	/**
	 * Creates a balanced binary tree of progress monitors, without calling worked. Tests
	 * progress monitor creation and cleanup time, and ensures that excess progress is
	 * being collected when IProgressMonitor.done() is called.
	 *
	 * @deprecated to suppress deprecation warnings
	 *
	 * @param monitor progress monitor (callers are responsible for calling done() if necessary)
	 * @param loopSize total size of the recursion tree
	 */
	public static void createBalancedTree(IProgressMonitor monitor, int loopSize) {
		monitor.beginTask("", 100);
		int leftBranch = loopSize / 2;
		int rightBranch = loopSize - leftBranch;

		if (leftBranch > 1) {
			SubProgressMonitor leftProgress = new SubProgressMonitor(monitor, 50);
			createBalancedTree(leftProgress, leftBranch);
			leftProgress.done();
		}

		if (rightBranch > 1) {
			SubProgressMonitor rightProgress = new SubProgressMonitor(monitor, 50);
			createBalancedTree(rightProgress, rightBranch);
			rightProgress.done();
		}
	}

	/**
	 * The innermost loop for the looping test. We make this a static method so
	 * that it can be used both in this performance test and in the correctness test.
	 *
	 * @deprecated to suppress deprecation warnings
	 */
	public static void runTestWorked(IProgressMonitor monitor) {
		SubProgressMonitor nestedMonitor = createSubProgressChain(monitor, SubProgressTest.CHAIN_DEPTH);
		reportWorkInLoop(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
		callDoneOnChain(nestedMonitor, SubProgressTest.CHAIN_DEPTH + 1);
	}

	/**
	 * The innermost loop for the looping test. We make this a static method so
	 * that it can be used both in this performance test and in the correctness test.
	 *
	 * @deprecated to suppress deprecation warnings
	 */
	public static void runTestInternalWorked(IProgressMonitor monitor) {
		SubProgressMonitor nestedMonitor = createSubProgressChain(monitor, SubProgressTest.CHAIN_DEPTH);
		reportFloatingPointWorkInLoop(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
		callDoneOnChain(nestedMonitor, SubProgressTest.CHAIN_DEPTH + 1);
	}

	/**
	 * The innermost loop for the recursion test. We make this a static method so
	 * that it can be used both in this performance test and in the correctness test.
	 *
	 * @deprecated to suppress deprecation warnings
	 */
	public static void runTestTypicalUsage(IProgressMonitor monitor) {
		SubProgressMonitor nestedMonitor = createSubProgressChain(monitor, SubProgressTest.CHAIN_DEPTH);
		reportWorkInBalancedTree(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
		callDoneOnChain(nestedMonitor, SubProgressTest.CHAIN_DEPTH + 1);
	}

	/**
	 * <p>The innermost loop for the create tree test. We make this a static method so
	 * that it can be used both in this performance test and in the correctness test.</p>
	 *
	 * <p>The performance test ensures that it is fast to create a lot of progress monitors.</p>
	 *
	 * <p>The correctness test ensures that creating and destroying SubProgressMonitors
	 * is enough to report progress, even if worked(int) and worked(double) are never called</p>
	 *
	 * @deprecated to suppress deprecation warnings
	 */
	public static void runTestCreateTree(IProgressMonitor monitor) {
		SubProgressMonitor nestedMonitor = createSubProgressChain(monitor, SubProgressTest.CHAIN_DEPTH);
		createBalancedTree(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
		callDoneOnChain(nestedMonitor, SubProgressTest.CHAIN_DEPTH + 1);
	}

	/**
	 * Creates and destroys the given number of child progress monitors under the given parent.
	 *
	 * @param monitor monitor to create children under. The caller must call done on this monitor
	 * if necessary.
	 * @param progressSize total number of children to create.
	 *
	 * @deprecated to suppress deprecation warnings
	 */
	private static void createChildrenUnderParent(IProgressMonitor monitor, int progressSize) {
		monitor.beginTask("", progressSize);

		for (int count = 0; count < progressSize; count++) {
			SubProgressMonitor mon = new SubProgressMonitor(monitor, 1);
			mon.beginTask("", 100);
			mon.done();
		}
	}

	/**
	 * Test SubProgressMonitor's created with negative a work value.
	 */
	public void testNegativeWorkValues() {
		TestProgressMonitor top = new TestProgressMonitor();
		top.beginTask("", 10);

		SubProgressMonitor childMonitor = new SubProgressMonitor(top, IProgressMonitor.UNKNOWN); // -1
		childMonitor.beginTask("", 10);
		childMonitor.worked(5);
		Assert.assertEquals(0.0, top.getTotalWork(), 0.01d);
		childMonitor.done();
		Assert.assertEquals(0.0, top.getTotalWork(), 0.01d);

		top.done();
	}

}
