/*******************************************************************************
 * Copyright (c) 2003, 2016 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.osgi.framework.eventmgr;

import java.util.Map;
import java.util.Set;
import org.eclipse.osgi.framework.eventmgr.EventManager.EventThread;

/**
 * The ListenerQueue is used to snapshot the list of listeners at the time the event
 * is fired. The snapshot list is then used to dispatch
 * events to those listeners. A ListenerQueue object is associated with a
 * specific EventManager object. ListenerQueue objects constructed with the same
 * EventManager object will get in-order delivery of events when
 * using asynchronous delivery. No delivery order is guaranteed for synchronous
 * delivery to avoid any potential deadly embraces.
 *
 * <p>ListenerQueue objects are created as necessary to build a list of listeners
 * that should receive a specific event or events. Once the list is created, the event
 * can then be synchronously or asynchronously delivered to the list of
 * listeners. After the event has been dispatched for delivery, the
 * ListenerQueue object should be discarded as it is likely the list of listeners is stale.
 * A new ListenerQueue object should be created when it is time to deliver
 * another event. The Sets used to build the list of listeners must not change after being
 * added to the list.
 * @since 3.1
 */
public class ListenerQueue<K, V, E> {
	/**
	 * EventManager with which this queue is associated.
	 */
	protected final EventManager manager;
	/**
	 * A list of listener lists.
	 */
	private final Map<Set<Map.Entry<K, V>>, EventDispatcher<K, V, E>> queue;

	/**
	 * Once the listener queue has been used to dispatch an event,
	 * you cannot add modify the queue.
	 * Access to this field must be protected by a synchronized region.
	 */
	private boolean readOnly;

	/**
	 * ListenerQueue constructor. This method creates an empty snapshot list.
	 *
	 * @param manager The EventManager this queue is associated with.
	 * @throws IllegalArgumentException If manager is null.
	 */
	public ListenerQueue(EventManager manager) {
		if (manager == null) {
			throw new IllegalArgumentException();
		}

		this.manager = manager;
		queue = new CopyOnWriteIdentityMap<>();
		readOnly = false;
	}

	/**
	 * Add a listener list to the snapshot list. This method can be called multiple times, prior to
	 * calling one of the dispatchEvent methods, to build the set of listeners for the
	 * delivery of a specific event. The current list of listeners in the specified EventListeners
	 * object is added to the snapshot list.
	 *
	 * @param listeners An EventListeners object to add to the queue. The current listeners
	 * in the EventListeners object will be called when an event is dispatched.
	 * @param dispatcher An EventDispatcher object to use when dispatching an event
	 * to the listeners on the specified EventListeners.
	 * @throws IllegalStateException If called after one of the dispatch methods has been called.
	 * @deprecated As of 3.5. Replaced by {@link #queueListeners(Set, EventDispatcher)}.
	 */
	public void queueListeners(EventListeners<K, V> listeners, EventDispatcher<K, V, E> dispatcher) {
		queueListeners(listeners.entrySet(), dispatcher);
	}

	/**
	 * Add a set of listeners to the snapshot list. This method can be called multiple times, prior to
	 * calling one of the dispatchEvent methods, to build the list of listeners for the
	 * delivery of a specific event. The specified listeners
	 * are added to the snapshot list.
	 *
	 * @param listeners A Set of Map.Entries to add to the queue. This is typically the entrySet
	 * from a CopyOnWriteIdentityMap object. This set must not change after being added to this
	 * snapshot list.
	 * @param dispatcher An EventDispatcher object to use when dispatching an event
	 * to the specified listeners.
	 * @throws IllegalStateException If called after one of the dispatch methods has been called.
	 * @since 3.5
	 */
	public synchronized void queueListeners(Set<Map.Entry<K, V>> listeners, EventDispatcher<K, V, E> dispatcher) {
		if (readOnly) {
			throw new IllegalStateException();
		}

		if (!listeners.isEmpty()) {
			queue.put(listeners, dispatcher); // enqueue the list and its dispatcher
		}
	}

	/**
	 * Asynchronously dispatch an event to the snapshot list. An event dispatch thread
	 * maintained by the associated EventManager is used to deliver the events.
	 * This method may return immediately to the caller.
	 *
	 * @param eventAction This value is passed to the EventDispatcher.
	 * @param eventObject This object is passed to the EventDispatcher.
	 */
	public void dispatchEventAsynchronous(int eventAction, E eventObject) {
		synchronized (this) {
			readOnly = true;
		}
		EventThread<K, V, E> eventThread = manager.getEventThread();
		synchronized (eventThread) { /* synchronize on the EventThread to ensure no interleaving of posting to the event thread */
			for (Map.Entry<Set<Map.Entry<K, V>>, EventDispatcher<K, V, E>> entry : queue.entrySet()) { /* iterate over the list of listener lists */
				eventThread.postEvent(entry.getKey(), entry.getValue(), eventAction, eventObject);
			}
		}
	}

	/**
	 * Synchronously dispatch an event to the snapshot list. The event may
	 * be dispatched on the current thread or an event dispatch thread
	 * maintained by the associated EventManager.
	 * This method will not return to the caller until the EventDispatcher
	 * has been called (and has returned) for each listener on the queue.
	 *
	 * @param eventAction This value is passed to the EventDispatcher.
	 * @param eventObject This object is passed to the EventDispatcher.
	 */
	public void dispatchEventSynchronous(int eventAction, E eventObject) {
		synchronized (this) {
			readOnly = true;
		}
		// We can't guarantee any delivery order for synchronous events.
		// Attempts to do so result in deadly embraces.
		for (Map.Entry<Set<Map.Entry<K, V>>, EventDispatcher<K, V, E>> entry : queue.entrySet()) { /* iterate over the list of listener lists */
			EventManager.dispatchEvent(entry.getKey(), entry.getValue(), eventAction, eventObject);
		}
	}
}
