package de.renew.net.event;

import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;

import de.renew.engine.simulator.SimulatorEventQueue;
import de.renew.engine.thread.SimulationThreadPool;


/**
 * A {@code ListenerSet} manages a collection
 * of event listeners and is able to distribute events
 * to all of its listeners.
 * <p>
 * Synchronization is done on this object.
 * <p>
 * When an event is delivered synchronously, it is ensured that
 * the listener set is locked, so that attempted changes to the
 * set of listeners do not affect the current notifications.
 * <p>
 * This is a utility class that is not type safe. It should be
 * subclassed to get adequate functionality.
 *
 * @author <a href="mailto:kummer@informatik.uni-hamburg.de">Olaf Kummer</a>
 */
public class ListenerSet {
    /**
     * The Logger for the ListenerSet class.
     */
    public static final Logger LOGGER = Logger.getLogger(ListenerSet.class);

    /**
     * The listeners to notify if events occur.
     **/
    private final transient Set<NetEventListener> _listeners = new HashSet<>();

    /**
     * Constructs a new ListenerSet.
     */
    public ListenerSet() {}

    synchronized void include(NetEventListener listener) {
        _listeners.add(listener);
    }

    synchronized void exclude(NetEventListener listener) {
        _listeners.remove(listener);
    }

    /**
     * Call the dispatcher once for each registered listener.
     **/
    synchronized void dispatch(final ListenerSetDispatcher dispatcher) {
        assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";

        NetEventListener[] listenerArray = _listeners.toArray(new NetEventListener[] { });
        for (NetEventListener netEventListener : listenerArray) {
            if (netEventListener.wantSynchronousNotification()) {
                try {
                    dispatcher.dispatchTo(netEventListener);
                } catch (RuntimeException | Error e) {
                    LOGGER.error("Error while dispatching net event: " + e, e);
                }
                continue;
            }

            final NetEventListener listener = netEventListener;
            SimulatorEventQueue.enqueue(() -> {
                assert SimulationThreadPool.isSimulationThread() : "is not in a simulation thread";
                try {
                    dispatcher.dispatchTo(listener);
                } catch (RuntimeException | Error e) {
                    LOGGER.error("Error while dispatching net event: " + e, e);
                }
            });
        }
    }
}