/*******************************************************************************
 * Copyright (c) 2000, 2020 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
 *******************************************************************************/
package org.eclipse.jdt.ui.leaktest;

import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;

import org.junit.Before;

import org.eclipse.jdt.testplugin.util.DisplayHelper;

import org.eclipse.swt.widgets.Display;

import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

import org.eclipse.jdt.ui.leaktest.reftracker.ReferenceTracker;
import org.eclipse.jdt.ui.leaktest.reftracker.ReferenceVisitor;
import org.eclipse.jdt.ui.leaktest.reftracker.ReferencedObject;

/**
 * Base class for leak test cases.
 */
public class LeakTestCase {
	public static class MulipleCollectorVisitor extends ReferenceVisitor {
		private ReferenceVisitor[] fVisitors;

		public MulipleCollectorVisitor(ReferenceVisitor[] visitors) {
			fVisitors= visitors;
		}

		@Override
		public boolean visit(ReferencedObject object, Class<?> clazz, boolean firstVisit) {
			boolean visitChildren= false;
			for (ReferenceVisitor visitor : fVisitors) {
				boolean res= visitor.visit(object, clazz, firstVisit);
				visitChildren |= res;
			}
			return visitChildren;
		}

	}

	private InstancesOfTypeCollector collect(String requestedTypeName) {
		InstancesOfTypeCollector requestor= new InstancesOfTypeCollector(requestedTypeName, false);
		calmDown();
		new ReferenceTracker(requestor).start(getClass().getClassLoader());
		return requestor;
	}

	private InstancesOfTypeCollector[] collect(String[] requestedTypeNames) {
		final InstancesOfTypeCollector[] requestors= new InstancesOfTypeCollector[requestedTypeNames.length];
		for (int i= 0; i < requestors.length; i++) {
			requestors[i]= new InstancesOfTypeCollector(requestedTypeNames[i], false);
		}
		calmDown();
		ReferenceVisitor visitor= new ReferenceVisitor() {
			@Override
			public boolean visit(ReferencedObject object, Class<?> clazz, boolean firstVisit) {
				for (InstancesOfTypeCollector requestor : requestors) {
					requestor.visit(object, clazz, firstVisit);
				}
				return true;
			}
		};

		new ReferenceTracker(visitor).start(getClass().getClassLoader());
		return requestors;
	}

	/*
	 * @see junit.framework.TestCase#setUp()
	 * @since 3.4
	 */
	@Before
	public void setUp() throws Exception {
		// Ensure active page to allow test being run
		IWorkbenchWindow activeWindow= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
		if (activeWindow.getActivePage() == null) {
			activeWindow.openPage(null);
		}
	}

	private void calmDown() {
		// Make sure we wait > 500, to allow e.g. TextViewer.queuePostSelectionChanged(boolean)
		// and OpenStrategy to time out and release references in delayed runnables.
		new DisplayHelper() {
			@Override
			protected boolean condition() {
				return false;
			}
		}.waitForCondition(Display.getCurrent(), 1000);

		for (int i= 0; i < 10; i++) {
			System.gc();
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
			}
		}
	}

  	/**
   	 * Asserts that the instance count of the given class is as expected.
   	 *
	 * @param clazz the class of the instances to count
  	 * @param expected the expected instance count
	 */
  	public void assertInstanceCount(final Class<?> clazz, final int expected) {
  		int numTries= 2;
  		while (true) {
	  		InstancesOfTypeCollector requestor= collect(clazz.getName());
			int actual= requestor.getNumberOfResults();
			if (expected == actual) {
				return;
			}
			numTries--;
			assertNotEquals("Expected instance count: " + expected + ", actual: " + actual + "\n" + requestor.getResultString(), 0, numTries);
  		}
	}


  	/**
   	 * Asserts that the instance count of the given class is as expected.
   	 *
	 * @param classNames the types names of the instances to count
  	 * @param expected the expected instance count
	 */
  	public void assertInstanceCount(final String[] classNames, final int[] expected) {
  		int numTries= 2;
  		while (true) {
	  		InstancesOfTypeCollector[] requestors= collect(classNames);

	  		boolean success= true;
	  		for (int k= 0; success && k < requestors.length; k++) {
				if (expected[k] != requestors[k].getNumberOfResults()) {
					success= false;
				}
			}
	  		if (success)
	  			return;

	  		numTries--;
			if (numTries == 0) {
	  			StringBuilder buf= new StringBuilder();
		  		for (int k= 0; k < requestors.length; k++) {
					int actual= requestors[k].getNumberOfResults();
					if (expected[k] != actual) {
						buf.append("Expected instance count: " + expected[k] + ", actual: " + actual + "\n" + requestors[k].getResultString()).append("\n---------------------\n");
					}
				}
  				fail(buf.toString());
			}
  		}
	}

	/**
	 * Returns the number of instances of a given class that are live (not garbage).
	 * @param clazz The class of the instances to count
	 * @return Returns the current number of instances of the given class or <code>-1</code> if
	 * no connection is established.
	 */
	protected int getInstanceCount(Class<?> clazz) {
  		InstancesOfTypeCollector requestor= collect(clazz.getName());
		return requestor.getNumberOfResults();
	}

	/**
	 * Assert that two counts are different. The method does not fail if the profile connection has
	 * not established (e.g. because profiling is not supported on the given platform).
	 *
	 * @param startCount the start count
	 * @param endCount the end count
	 */
	protected void assertDifferentCount(int startCount, int endCount) {
		assertDifferentCount(null, startCount, endCount);
	}

	/**
	 * Assert that two counts are different. The method does not fail if the profile connection has
	 * not established (e.g. because profiling is not supported on the given platform)
	 *
	 * @param message Message to be printed if the test fails.
	 *
	 * @param startCount the start count
	 * @param endCount the end count
	 */
	protected void assertDifferentCount(String message, int startCount, int endCount) {
		if (startCount == endCount) {
			String str= message != null ? message + ": " : "";
			fail(str + "instance count is not different: (" + startCount + " / " + endCount + " )");
		}
	}

	/**
	 * Assert that two counts are equal. The method does not fail if the profile connection has not
	 * established (e.g. because profiling is not supported on the given platform).
	 *
	 * @param startCount the start count
	 * @param endCount the end count
	 */
	protected void assertEqualCount(int startCount, int endCount) {
		assertEqualCount(null, startCount, endCount);
	}

	/**
	 * Assert that two counts are equal. The method does not fail if the profile connection has not
	 * established (e.g. because profiling is not supported on the given platform).
	 *
	 * @param message Message to be printed if the test fails.
	 * @param startCount the start count
	 * @param endCount the end count
	 */
	protected void assertEqualCount(String message, int startCount, int endCount) {
		if (startCount != endCount) {
			// only compare if connection could be established
			String str= message != null ? message + ": " : "";
			fail(str + "instance count is not the same: (" + startCount + " / " + endCount + " )");
		}
	}
}
