/*******************************************************************************
 * 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;

/**
 * @since 3.1
 */
public class UIJobTest extends UITestCase {

    protected IWorkbenchWindow fWindow;

    protected IWorkbenchPage fPage;

    /**
     * Constructor for IEditorPartTest
     */
    public UIJobTest(String testName) {
        super(testName);
    }

    @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
     */
    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 {

        }

    }


}
