/*******************************************************************************
 * Copyright (c) 2004, 2017 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.ui.tests.api;


import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.tests.harness.util.UITestCase;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * @since 3.1
 */
@RunWith(JUnit4.class)
public class UIJobTest extends UITestCase {

	protected IWorkbenchWindow fWindow;

	protected IWorkbenchPage fPage;

	/**
	 * Constructor for IEditorPartTest
	 */
	public UIJobTest() {
		super(UIJobTest.class.getSimpleName());
	}

	@Override
	protected void doSetUp() throws Exception {
		super.doSetUp();
		fWindow = openTestWindow();
		fPage = fWindow.getActivePage();
	}

	private volatile boolean uiJobFinished = false;
	private volatile boolean backgroundThreadStarted = false;
	private volatile boolean backgroundThreadInterrupted = false;
	private volatile boolean backgroundThreadFinishedBeforeUIJob = false;
	private volatile boolean backgroundThreadFinished = false;
	private volatile boolean uiJobFinishedBeforeBackgroundThread = false;

	/**
	 * Test to ensure that calling join() on a UIJob will block the calling
	 * thread until the job has finished.
	 *
	 * @throws Exception
	 * @since 3.1
	 */
	@Test
	public void testJoin() throws Exception {

		uiJobFinished = false;
		backgroundThreadStarted = false;
		backgroundThreadFinished = false;
		backgroundThreadInterrupted = false;
		uiJobFinishedBeforeBackgroundThread = false;

		final UIJob testJob = new UIJob("blah blah blah") {
			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				backgroundThreadFinishedBeforeUIJob = backgroundThreadFinished;
				uiJobFinished = true;

				return Status.OK_STATUS;
			}
		};

		testJob.setPriority(Job.INTERACTIVE);

		// Background thread that will try to schedule and join a UIJob. If all goes well
		// it should lock up since we're intentionally blocking the UI thread, preventing
		// it from running.
		Thread testThread = new Thread() {
		@Override
		public void run() {
			testJob.schedule();

			backgroundThreadStarted = true;

			try {
				testJob.join();
				uiJobFinishedBeforeBackgroundThread = uiJobFinished;
				backgroundThreadFinished = true;
			} catch (InterruptedException e) {
				backgroundThreadInterrupted = true;
			}
		}
		};

		// This job does nothing. We use a bogus low-priority job instead of a simple
		// sleep(xxxx) in order to ensure that we don't wake up before the test job was
		// scheduled.
		Job delayJob = new Job("blah") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {

				return Status.OK_STATUS;
			}

		};
		delayJob.setPriority(Job.LONG);

		try {
			// Schedule the test thread
			testThread.start();

			// Measure current time
			long currentTime = System.currentTimeMillis();

			// We need to create a situation in which the run() method of the UIJob will have
			// been called and exited, but the runInUIThread() method will not have been called.
			// We then put everything else to sleep, making a high probability that the background
			// thread would wake up if there was nothing blocking it. Note: it is possible that the
			// test would still pass if there was a problem and the background thread failed to
			// wake up for reasons other than its join()... but it creates a high probability of
			// things failing if something goes wrong.
			//
			// The point of this join() is to block the UI thread, making it impossible to
			// run *syncExecs. Joining a low priority job means that the UIJob should get
			// scheduled first (meaning its run(...) method will be called). The fact that we're
			// in the UI thread right now means that the UIJob's runInUIThread method shouldn't
			// be called.

			// Block the UI thread until the UIJob's run() method is called.
			delayJob.schedule(200);
			delayJob.join();

			long finalTime = System.currentTimeMillis();

			// Ensure that we slept for at least 200ms
			Assert.assertTrue("We tried to sleep the UI thread, but it woke up too early. ",
					finalTime - currentTime >= 200);

			Assert.assertTrue("Background thread did not start, so there was no possibility "
					+ "of testing whether its behavior was correct. This is not a test failure. "
					+ "It means we were unable to run the test. ",
					backgroundThreadStarted);

			Assert.assertFalse("A UI job somehow ran to completion while the UI thread was blocked", uiJobFinished);
			Assert.assertFalse("Background job managed to run to completion, even though it joined a UI thread that still hasn't finished",
					backgroundThreadFinished);
			Assert.assertFalse("Background thread was interrupted", backgroundThreadInterrupted);

			// Now run the event loop. Give the asyncExec a chance to run.
			//Use the display provided by the shell if possible
			Display display = fWindow.getShell().getDisplay();

			// Wait until both threads have run to completion. If we wait for more than 3s, something
			// probably deadlocked.
			while (!(uiJobFinished && backgroundThreadFinished) ) {
				// If we've waited more than 3s for the test job to run, then something is wrong.
				if (finalTime - System.currentTimeMillis() > 3000) {
					break;
				}
				if (!display.readAndDispatch()) {
					display.sleep();
				}
			}

			// Now that the event queue is empty, check that our final state is okay.
			Assert.assertTrue("Background thread did not finish (possible deadlock)", backgroundThreadFinished);
			Assert.assertTrue("Test job did not finish (possible deadlock)", uiJobFinished);
			Assert.assertFalse("Background thread was interrupted ", backgroundThreadInterrupted);
			Assert.assertFalse("Background thread finished before the UIJob, even though the background thread was supposed to be waiting for the UIJob",
					backgroundThreadFinishedBeforeUIJob);

			// This is the whole point of the test: ensure that the background job actually waited for the UI job
			// to run to completion.
			Assert.assertFalse("Background thread finished before the UIJob, even though the background thread was supposed to be waiting for the UIJob",
					backgroundThreadFinishedBeforeUIJob);

			// Paranoia check: this is really the same test as above, but it confirms that both
			// threads agreed on the answer.
			Assert.assertTrue("Background thread finished before the UIJob, even though the background thread was supposed to be waiting for the UIJob",
					uiJobFinishedBeforeBackgroundThread);
		} finally {

		}

	}


}
