# util/EventEmitter.js

    /**
     * Event Emitter.
     *
     * @author Alain Pitiot
     * @version 2022.2.3
     * @copyright (c) 2017-2020 Ilixa Ltd. (http://ilixa.com) (c) 2020-2022 Open Science Tools Ltd. (https://opensciencetools.org)
     * @license Distributed under the terms of the MIT License
     */

    import * as util from "./Util.js";

    /**
     * <p>EventEmitter implements the classic observer/observable pattern.</p>
     *
     * <p>Note: this is heavily inspired by http://www.datchley.name/es6-eventemitter/</p>
     *
     * @example
     * let observable = new EventEmitter();
     * let uuid1 = observable.on('change', data => { console.log(data); });
     * observable.emit("change", { a: 1 });
     * observable.off("change", uuid1);
     * observable.emit("change", { a: 1 });
     */
    export class EventEmitter
    {
    	/**
    	 * @memberof module:util
    	 */
    	constructor()
    	{
    		this._listeners = new Map();
    		this._onceUuids = new Map();
    	}

    	/**
    	 * Listener called when this instance emits an event for which it is registered.
    	 *
    	 * @callback module:util.EventEmitter~Listener
    	 * @param {object} data - the data passed to the listener
    	 */

    	/**
    	 * Register a new listener for events with the given name emitted by this instance.
    	 *
    	 * @param {String} name - the name of the event
    	 * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
    	 * @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)
    	 */
    	on(name, listener)
    	{
    		// check that the listener is a function:
    		if (typeof listener !== "function")
    		{
    			throw new TypeError("listener must be a function");
    		}

    		// generate a new uuid:
    		let uuid = util.makeUuid();

    		// add the listener to the event map:
    		if (!this._listeners.has(name))
    		{
    			this._listeners.set(name, []);
    		}
    		this._listeners.get(name).push({ uuid, listener });

    		return uuid;
    	}

    	/**
    	 * Register a new listener for the given event name, and remove it as soon as the event has been emitted.
    	 *
    	 * @param {String} name - the name of the event
    	 * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
    	 * @return string - the unique identifier associated with that (event, listener) pair (useful to remove the listener)
    	 */
    	once(name, listener)
    	{
    		let uuid = this.on(name, listener);

    		if (!this._onceUuids.has(name))
    		{
    			this._onceUuids.set(name, []);
    		}
    		this._onceUuids.get(name).push(uuid);

    		return uuid;
    	}

    	/**
    	 * Remove the listener with the given uuid associated to the given event name.
    	 *
    	 * @param {String} name - the name of the event
    	 * @param {module:util.EventEmitter~Listener} listener - a listener called upon emission of the event
    	 */
    	off(name, uuid)
    	{
    		let relevantUuidListeners = this._listeners.get(name);

    		if (relevantUuidListeners && relevantUuidListeners.length)
    		{
    			this._listeners.set(name, relevantUuidListeners.filter((uuidlistener) => (uuidlistener.uuid != uuid)));
    			return true;
    		}
    		return false;
    	}

    	/**
    	 * Emit an event with a given name and associated data.
    	 *
    	 * @param {String} name - the name of the event
    	 * @param {object} data - the data of the event
    	 * @return {boolean} true if at least one listener has been registered for that event, and false otherwise
    	 */
    	emit(name, data)
    	{
    		let relevantUuidListeners = this._listeners.get(name);
    		if (relevantUuidListeners && relevantUuidListeners.length)
    		{
    			let onceUuids = this._onceUuids.get(name);
    			let self = this;
    			relevantUuidListeners.forEach(({ uuid, listener }) =>
    			{
    				listener(data);

    				if (typeof onceUuids !== "undefined" && onceUuids.includes(uuid))
    				{
    					self.off(name, uuid);
    				}
    			});
    			return true;
    		}

    		return false;
    	}
    }