/*******************************************************************************
 * Copyright (c) 2004, 2015 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.core.tests.harness;

import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IExtensionDelta;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IRegistryChangeListener;
import org.eclipse.core.runtime.Platform;

/**
 * Allows test cases to wait for event notification.
 */
public class TestRegistryChangeListener implements IRegistryChangeListener {

	/**
	 * Indicates that no matching even has been received
	 */
	public static final int NO_EVENT = -1;

	private List<IRegistryChangeEvent> events = new LinkedList<>();
	private List<Integer> simpleEvents = new LinkedList<>();
	private String xpNamespace;
	private String xpId;
	private String extNamespace;
	private String extId;

	/**
	 * Creates a new listener. The parameters allow filtering events based on extension point/extension's
	 * namespaces/ids.
	 */
	public TestRegistryChangeListener(String xpNamespace, String xpId, String extNamespace, String extId) {
		if (xpId != null && xpNamespace == null) {
			throw new IllegalArgumentException();
		}
		if (extId != null && extNamespace == null) {
			throw new IllegalArgumentException();
		}
		if (xpId == null && extId != null) {
			throw new IllegalArgumentException();
		}
		this.xpNamespace = xpNamespace;
		this.xpId = xpId;
		this.extNamespace = extNamespace;
		this.extId = extId;
	}

	/**
	 * @see IRegistryChangeListener#registryChanged
	 */
	@Override
	public synchronized void registryChanged(IRegistryChangeEvent newEvent) {
		IExtensionDelta delta = null;
		if (xpId != null) {
			if (extId != null) {
				delta = newEvent.getExtensionDelta(xpNamespace, xpId, extNamespace + '.' + extId);
			} else {
				IExtensionDelta[] deltas = newEvent.getExtensionDeltas(xpNamespace, xpId);
				if (deltas.length != 0) {
					delta = deltas[0];
				}
			}
		}
		if (delta == null) {
			return; // this is not the event we are interested in
		}
		events.add(newEvent);
		simpleEvents.add(Integer.valueOf(delta.getKind()));
		notifyAll();
	}

	/**
	 * Returns the first event that is received, blocking for at most <code>timeout</code> milliseconds.
	 * Returns <code>null</code> if a event was not received for the time allowed.
	 * <p>
	 * Note: registry elements referred to by the event returned from this method might be
	 * invalid. Method is preserved for backward compatibility, but users are strongly encouraged
	 * to switch to {@link #eventTypeReceived(long)}.
	 * </p>
	 *
	 * @param timeout the maximum time to wait in milliseconds. If zero, this method will
	 * block until an event is received
	 * @return the first event received, or <code>null</code> if none was received
	 *
	 * @deprecated use {@link #eventTypeReceived(long)} instead
	 */
	@Deprecated
	public synchronized IRegistryChangeEvent getEvent(long timeout) {
		if (!events.isEmpty()) {
			return events.remove(0);
		}
		try {
			wait(timeout);
		} catch (InterruptedException e) {
			// who cares?
		}
		return events.isEmpty() ? null : (IRegistryChangeEvent) events.remove(0);
	}

	/**
	 * Wait for a registry event that fits IDs specified in the constructor, blocking for
	 * at most <code>timeout</code> milliseconds.
	 * <p>
	 * Note: do NOT mix calls to {@link #getEvent(long)} with calls to this method in the same
	 * instance of this class.
	 * </p>
	 *
	 * @param timeout the maximum time to wait in milliseconds. If zero, this method will
	 * block until an event is received
	 * @return event type
	 *
	 * @since 3.4
	 */
	public synchronized int eventTypeReceived(long timeout) {
		long start = System.currentTimeMillis();
		while (simpleEvents.isEmpty()) {
			try {
				long sleepTime = timeout - (System.currentTimeMillis()-start);
				if (sleepTime <= 0) {
					break;
				}
				wait(sleepTime);
			} catch (InterruptedException e) {
				// who cares?
			}
		}
		return simpleEvents.isEmpty() ? NO_EVENT : simpleEvents.remove(0).intValue();
	}

	/**
	 * Wait for a registry event that fits IDs specified in the constructor, blocking for
	 * at most <code>timeout</code> milliseconds.
	 * <p>
	 * Note: do NOT mix calls to {@link #getEvent(long)} with calls to this method in the same
	 * instance of this class.
	 * </p>
	 *
	 * @param timeout the maximum time to wait in milliseconds. If zero, this method will
	 * block until an event is received
	 * @return <code>true</code> if event was received; <code>false</code> otherwise
	 *
	 * @since 3.4
	 */
	public synchronized boolean eventReceived(long timeout) {
		int notified = eventTypeReceived(timeout);
		return notified != TestRegistryChangeListener.NO_EVENT;
	}

	public void register() {
		Platform.getExtensionRegistry().addRegistryChangeListener(this, xpNamespace);
	}

	public void unregister() {
		Platform.getExtensionRegistry().removeRegistryChangeListener(this);
	}

	public synchronized void reset() {
		events.clear();
		simpleEvents.clear();
	}

}
