/*******************************************************************************
 * Copyright (c) 2009, 2013 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.debug.tests.launching;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.internal.core.LaunchManager;
import org.eclipse.debug.tests.launching.CancellingLaunchDelegate.CancellingLaunch;
import org.junit.Test;

/**
 *
 * Variety of tests for the {@link org.eclipse.debug.internal.core.LaunchManager}
 *
 * @since 3.6
 */
@SuppressWarnings("deprecation")
public class LaunchManagerTests extends AbstractLaunchTest {


	/**
	 * Tests generating a valid launch configuration name
	 */
	@Test
	public void testGenereateConfigName() {
		String configname = "launch_configuration"; //$NON-NLS-1$
		String name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertTrue("the name nust be '" + configname + "'", name.equals(configname)); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Tests generating a launch configuration name with an unsupported char
	 * using the deprecated method
	 */
	@Test
	public void testGenereateConfigNameBadChar() {
		String configname = "config:name"; //$NON-NLS-1$
		String name = getLaunchManager().generateUniqueLaunchConfigurationNameFrom(configname);
		assertEquals("config name should be '" + configname + "'", configname, name); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Tests that a valid name is accepted as is.
	 */
	@Test
	public void testGenerateValidName() {
		String configname = "thisisavalidname"; //$NON-NLS-1$
		String name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertEquals("Should be the same as the seed", configname, name); //$NON-NLS-1$
	}

	/**
	 * Tests generating a launch configuration name using a name that is an OS
	 * reserved name. Win 32 test only.
	 */
	@Test
	public void testGenerateConfigNameReservedName() {
		if(Platform.OS_WIN32.equals(Platform.getOS())) {
			String configname = "aux"; //$NON-NLS-1$
			String name = getLaunchManager().generateUniqueLaunchConfigurationNameFrom(configname);
			assertEquals("config name should be 'aux'", configname, name); //$NON-NLS-1$
		}
	}

	/**
	 * Tests generating a configuration name that contains an invalid character
	 */
	@Test
	public void testGenerateBadConfigName() {
		String configname = "config:name"; //$NON-NLS-1$
		String name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertEquals("config name should be 'config_name'", "config_name", name); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Tests generating a name that conflicts with an OS reserved name. This
	 * test is for windows only as there are no reserved names on other OS's.
	 */
	@Test
	public void testGenerateConflictingName() {
		if(Platform.OS_WIN32.equals(Platform.getOS())) {
			String configname = "aux"; //$NON-NLS-1$
			String name = getLaunchManager().generateLaunchConfigurationName(configname);
			assertEquals("config name should be 'launch_configuration'", "launch_configuration", name); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	/**
	 * Tests generating a configuration name that contains an invalid character
	 * and where there is another config with the replaced name already
	 */
	@Test
	public void testGenerateBadCharConflict() throws Exception {
		String configname = "config:name"; //$NON-NLS-1$
		String name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertEquals("config name should be 'config_name'", "config_name", name); //$NON-NLS-1$ //$NON-NLS-2$
		getLaunchConfiguration(name);
		name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertEquals("config name should be 'config_name (1)'", "config_name (1)", name); //$NON-NLS-1$ //$NON-NLS-2$
		ILaunchConfiguration config = getLaunchConfiguration("config_name"); //$NON-NLS-1$
		config.delete();
	}

	/**
	 * Tests generating a name that conflicts with an OS reserved name and that
	 * there is a config with the replaced name already. This test is for
	 * windows only as there are no reserved names on other OS's.
	 */
	@Test
	public void testGenerateBadNameConflict() throws Exception {
		if(Platform.OS_WIN32.equals(Platform.getOS())) {
			String configname = "com2"; //$NON-NLS-1$
			String name = getLaunchManager().generateLaunchConfigurationName(configname);
			assertEquals("config name should be 'launch_configuration'", "launch_configuration", name); //$NON-NLS-1$ //$NON-NLS-2$
			getLaunchConfiguration(name);
			name = getLaunchManager().generateLaunchConfigurationName(configname);
			assertEquals("config name should be 'launch_configuration (1)'", "launch_configuration (1)", name); //$NON-NLS-1$ //$NON-NLS-2$
			ILaunchConfiguration config = getLaunchConfiguration("launch_configuration"); //$NON-NLS-1$
			config.delete();
		}
	}

	/**
	 * Tests the
	 * {@link org.eclipse.debug.core.ILaunchManager#isValidLaunchConfigurationName(String)}
	 * method for correctness
	 */
	@Test
	public void testValidateConfigGoodName() {
		String configname = "configname"; //$NON-NLS-1$
		try {
			getLaunchManager().isValidLaunchConfigurationName(configname);
		}
		catch(IllegalArgumentException iae) {
			fail("the config name should not have thrown an exception during validation"); //$NON-NLS-1$
		}
	}

	/**
	 * Tests the
	 * {@link org.eclipse.debug.core.ILaunchManager#isValidLaunchConfigurationName(String)}
	 * method for correctness
	 */
	@Test
	public void testValidateConfigBadCharName() {
		String configname = "config:name"; //$NON-NLS-1$
		try {
			getLaunchManager().isValidLaunchConfigurationName(configname);
		}
		catch(IllegalArgumentException iae) {
			return;
		}
		fail("the config name should have thrown an exception during validation"); //$NON-NLS-1$
	}

	/**
	 * Tests the
	 * {@link org.eclipse.debug.core.ILaunchManager#isValidLaunchConfigurationName(String)}
	 * method for correctness
	 */
	@Test
	public void testValidateConfigBadName() {
		if(Platform.OS_WIN32.equals(Platform.getOS())) {
			String configname = "com1"; //$NON-NLS-1$
			try {
				getLaunchManager().isValidLaunchConfigurationName(configname);
			}
			catch(IllegalArgumentException iae) {
				return;
			}
			fail("the config name should have thrown an exception during validation"); //$NON-NLS-1$
		}
	}

	/**
	 * Tests that generating a configuration name when there exists a
	 * configuration with that name already properly updates a '(N)' counter at
	 * the end
	 */
	@Test
	public void testGenerateNameExistingConfig() throws Exception {
		String configname = "x.y.z.configname"; //$NON-NLS-1$
		getLaunchConfiguration(configname);
		String name = getLaunchManager().generateLaunchConfigurationName(configname);
		assertEquals("the configuration name should have been " + configname + " (1)", configname + " (1)", name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		getLaunchConfiguration(name);
		name = getLaunchManager().generateLaunchConfigurationName(name);
		assertEquals("the configuration name should have been " + configname + " (2)", configname + " (2)", name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		ILaunchConfiguration config = getLaunchConfiguration(configname);
		if(config != null) {
			config.delete();
		}
		config = getLaunchConfiguration(configname + " (1)"); //$NON-NLS-1$
		if(config != null) {
			config.delete();
		}
	}

	/**
	 * Tests that removing an accelerator properly removes it without affecting the base string (readable) value
	 */
	@Test
	public void testRemoveAcc() {
		String text = "&Remove"; //$NON-NLS-1$
		String label = LaunchManager.removeAccelerators(text);
		assertEquals("the label should be 'Remove'", "Remove", label); //$NON-NLS-1$ //$NON-NLS-2$
		text = "Remo&ve"; //$NON-NLS-1$
		label = LaunchManager.removeAccelerators(text);
		assertEquals("the label should be 'Remove'", "Remove", label); //$NON-NLS-1$ //$NON-NLS-2$
	}

	/**
	 * Create a new configuration that will cancel one of the three checks: 1.
	 * preLaunchCheck 2. finalLaunchCheck 3. buildForLaunch
	 *
	 * @param pre If the prelaunchCheck should cancel
	 * @param fin If the fnalLaunchCheck should cancel
	 * @param build If the buildForLaunch check should cancel
	 * @return the new {@link ILaunchConfiguration}
	 * @since 3.9.100
	 */
	ILaunchConfiguration getCancellingConfiguration(boolean pre, boolean fin, boolean build) throws Exception {
		ILaunchConfigurationType type = getLaunchManager().getLaunchConfigurationType("cancelling.type"); //$NON-NLS-1$
		if (type != null) {
			ILaunchConfigurationWorkingCopy copy = type.newInstance(null, getLaunchManager().generateLaunchConfigurationName("cancelling")); //$NON-NLS-1$
			copy.setAttribute("cancel.preLaunchCheck", !pre); //$NON-NLS-1$
			copy.setAttribute("cancel.finalLaunchCheck", !fin); //$NON-NLS-1$
			copy.setAttribute("cancel.buildForLaunch", !build); //$NON-NLS-1$
			return copy.doSave();
		}
		return null;
	}

	/**
	 * Checks if the expected number of cancelled launches appear in the manager
	 *
	 * @param count the expected count
	 * @since 3.9.100
	 */
	void hasCancellingLaunches(int count) {
		ILaunch[] launches = getLaunchManager().getLaunches();
		int num = 0;
		for (ILaunch launche : launches) {
			if (launche instanceof CancellingLaunch) {
				num++;
			}
		}
		assertEquals("The number of expected launches is wrong", count, num); //$NON-NLS-1$
	}

	/**
	 * Tests if a launch is properly removed from the launch manager when
	 * #preLaunchCheck is cancelled
	 *
	 * @throws Exception
	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=437122
	 * @since 3.9.100
	 */
	@Test
	public void testCancelledPreLaunchCheck() throws Exception {
		ILaunchConfiguration config = getCancellingConfiguration(true, false, false);
		assertNotNull("The cancelling config should have been created", config); //$NON-NLS-1$
		try {
			hasCancellingLaunches(0);
			config.launch("run", new NullProgressMonitor()); //$NON-NLS-1$
			hasCancellingLaunches(0);
		} finally {
			ILaunch[] launches = getLaunchManager().getLaunches();
			for (ILaunch launche : launches) {
				getLaunchManager().removeLaunch(launche);
			}
			config.delete();
		}
	}

	/**
	 * Tests if a launch is properly removed from the launch manager when
	 * #finalLaunchCheck is cancelled
	 *
	 * @throws Exception
	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=437122
	 * @since 3.9.100
	 */
	@Test
	public void testCancelledFinalLaunchCheck() throws Exception {
		ILaunchConfiguration config = getCancellingConfiguration(false, true, false);
		assertNotNull("The cancelling config should have been created", config); //$NON-NLS-1$
		try {
			hasCancellingLaunches(0);
			config.launch("run", new NullProgressMonitor()); //$NON-NLS-1$
			hasCancellingLaunches(0);
		} finally {
			ILaunch[] launches = getLaunchManager().getLaunches();
			for (ILaunch launche : launches) {
				getLaunchManager().removeLaunch(launche);
			}
			config.delete();
		}
	}

	/**
	 * Tests if a launch is properly removed from the launch manager when
	 * #buildFoLaunch is cancelled
	 *
	 * @throws Exception
	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=437122
	 * @since 3.9.100
	 */
	@Test
	public void testCancelledBuildForLaunch() throws Exception {
		ILaunchConfiguration config = getCancellingConfiguration(false, false, true);
		assertNotNull("The cancelling config should have been created", config); //$NON-NLS-1$
		try {
			hasCancellingLaunches(0);
			config.launch("run", new NullProgressMonitor()); //$NON-NLS-1$
			hasCancellingLaunches(1); // XXX #buildForLaunch does not remove the
										// launch
		} finally {
			ILaunch[] launches = getLaunchManager().getLaunches();
			for (ILaunch launche : launches) {
				getLaunchManager().removeLaunch(launche);
			}
			config.delete();
		}
	}

	/**
	 * There was a race condition in getting a unique name for a launch
	 * configuration.
	 * <p>
	 * Note, due to the nature of the bug, it is possible that running this test
	 * will not trigger the original bug. To increase the probability of hitting
	 * the NPE in the unpatched code, increase the size of config. However,
	 * increasing the number increases the runtime of the test substantially.
	 */
	@Test
	public void testNPE_Bug484882() throws Exception {
		// In this thread continuously creates configs so that
		// org.eclipse.debug.internal.core.LaunchManager.clearConfigNameCache()
		// is called repeatedly. We also want to make lots of configurations so
		// the runtime of getAllSortedConfigNames (called by
		// isExistingLaunchConfigurationName
		// below) is long enough to hit the race condition.
		final boolean stop[] = new boolean[] { false };
		final Throwable exception[] = new Throwable[] { null };
		Thread thread = new Thread() {
			@Override
			public void run() {
				ILaunchConfiguration config[] = new ILaunchConfiguration[10000];
				try {
					for (int i = 0; i < config.length && !stop[0]; i++) {
						config[i] = getLaunchConfiguration("Name" + i); //$NON-NLS-1$
					}
					for (ILaunchConfiguration c : config) {
						if (c != null) {
							c.delete();
						}
					}
				} catch (CoreException e) {
					exception[0] = e;
				}
			}
		};
		thread.start();
		try {
			ILaunchManager launchManager = getLaunchManager();
			while (thread.isAlive()) {
				// This call generated an NPE
				launchManager.isExistingLaunchConfigurationName("Name"); //$NON-NLS-1$
			}
		} finally {
			stop[0] = true;
			thread.join(1000);
			assertFalse(thread.isAlive());
			if (exception[0] != null) {
				throw new Exception("Exception in Thread", exception[0]); //$NON-NLS-1$
			}
		}
	}
}
