/*******************************************************************************
 * Copyright (c) 2018 InterSystems 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:
 *     InterSystems Corporation - tests for Bug 444188
 *******************************************************************************/
package org.eclipse.equinox.preferences.tests;

import static org.junit.Assert.assertNotNull;

import java.util.concurrent.CountDownLatch;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ICoreRunnable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.junit.After;
import org.junit.Test;
import org.osgi.service.prefs.BackingStoreException;

/**
 * Test suite for class org.eclipse.core.internal.preferences.EclipsePreferences
 * WARNING: many tests are still located in
 * org.eclipse.core.tests.internal.preferences.EclipsePreferencesTest from
 * eclipse.platform.runtime
 */
public class EclipsePreferencesTest {

	/**
	 * Concurrent access to listener collection should not lead to exceptions
	 * 
	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=444188
	 */
	@Test
	public void testConcurrentPreferenceChangeListener() throws InterruptedException, CoreException {
		final IEclipsePreferences node = createTestNode();
		final int runSize = 100000;

		executeInTwoThreads(new ICoreRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				IPreferenceChangeListener listener = new IPreferenceChangeListener() {
					@Override
					public void preferenceChange(PreferenceChangeEvent event) {
					}
				};
				for (int i = 0; i < runSize && !monitor.isCanceled(); i++) {
					node.addPreferenceChangeListener(listener); // Should not throw
					node.put("x", "y"); // Should not throw
					node.remove("x"); // Should not throw
					node.removePreferenceChangeListener(listener); // Should not throw
				}
			}
		});
	}

	/**
	 * Concurrent access to listener collection should not lead to exceptions
	 * 
	 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=444188
	 */
	@Test
	public void testConcurrentNodeChangeListener() throws InterruptedException, CoreException {
		final IEclipsePreferences node = createTestNode();
		final int runSize = 100000;
		executeInTwoThreads(new ICoreRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				INodeChangeListener listener = new INodeChangeListener() {

					@Override
					public void removed(NodeChangeEvent event) {
					}

					@Override
					public void added(NodeChangeEvent event) {
					}
				};
				for (int i = 0; i < runSize && !monitor.isCanceled(); i++) {
					node.addNodeChangeListener(listener); // Should not throw
					try {
						node.node(Thread.currentThread().getName()).removeNode(); // Should not throw
					} catch (BackingStoreException e) {
						throw new CoreException(
								new Status(IStatus.ERROR, "org.eclipse.core.tests.runtime", 0, "", null));
					}
					node.removeNodeChangeListener(listener); // Should not throw
				}
			}
		});
	}

	private static void executeInTwoThreads(final ICoreRunnable runnable) throws InterruptedException, CoreException {
		final CountDownLatch latch = new CountDownLatch(1);
		Job job = Job.create("", new ICoreRunnable() {
			@Override
			public void run(IProgressMonitor monitor) throws CoreException {
				latch.countDown();
				runnable.run(monitor);
			}
		});
		job.schedule();
		try {
			latch.await();
			runnable.run(new NullProgressMonitor());
		} finally {
			job.cancel();
			job.join();
			IStatus result = job.getResult();
			assertNotNull("Job is expected to complete", result);
			if (!result.isOK()) {
				throw new CoreException(result);
			}
		}
	}

	@After
	public void after() throws BackingStoreException {
		getScopeRoot().removeNode();
	}

	private static String getUniqueString() {
		return System.currentTimeMillis() + "-" + Math.random();
	}

	private static IEclipsePreferences createTestNode() {
		return (IEclipsePreferences) getScopeRoot().node(getUniqueString());
	}

	private static IEclipsePreferences getScopeRoot() {
		return (IEclipsePreferences) Platform.getPreferencesService().getRootNode().node("EclipsePreferencesTest");
	}
}
