Source: HasEventsMixin.js

const Mixin = require('./Mixin')

/**
 * HasEventsMixin provides DOM-like asynchronous event processing for classes.
 * @abstract
 * @hideconstructor
 * @example
<caption>Define Events on your class</caption>
class GameEngine {
	constructor() {
		// Has Events
		HasEventsMixin(this, options)

		...

		// Events
		this.defineEvents(['running','mouseup','mousedown','mousemove','resize'])
	}
}
 * @example
<caption>Bind to events</caption>
var x1 = new GameEngine()

x1.on('running',(ge, isRunning) => {
	console.log("GameEngine >", ge, isRunning ? 'running' : 'stopped')
})
 * @example
<caption>Trigger Events</caption>
this.trigger('running', this, true)
 */
class HasEventsMixin {
  /**
	 * Initialise the events subsystem.
	 */
  static init (obj, options = {}) {
    obj._events = options.events || {}
  }

  /**
	 * Add the given event.
	 * @param {String[]} event The event name to add
	 */
  defineEvent (event) {
    this._events[event] = []
  }

  /**
	 * Remove the given event.
	 * @param {String[]} event The event name to remove
	 */
  undefineEvent (event) {
    delete this._events[event]
  }

  /**
	 * Add the given events.
	 * @param {String[]} events The event names to add
	 */
  defineEvents (events) {
    for (var i in events) this.defineEvent(events[i])
  }

  /**
	 * Remove the given events.
	 * @param {String[]} events The event names to remove
	 */
  undefineEvents (events) {
    for (var i in events) this.undefineEvent(events[i])
  }

  /**
	 * Add the given handler to the event.
	 * @param {String} event The name of the event
	 * @param {Function} fn The event handler
	 * @throws {Exception} 'unon: no such event <eventname>' if event not defined
	 */
  on (event, fn) {
    if (!this._events[event]) throw 'on: no such event ' + event
    if (this._events[event].indexOf(fn) == -1) this._events[event].push(fn)
    return this
  }

  /**
	 * Remove the given handler from the event.
	 * @param {String} event The name of the event
	 * @param {Function} fn The event handler
	 * @throws {Exception} 'unon: no such event <eventname>' if event not defined
	 */
  unon (event, fn) {
    if (!this._events[event]) throw 'unon: no such event ' + event
    var x = this._events[event]
    var i = x.indexOf(fn)
    if (i > -1) { x.splice(i, 1) }
    this._events[event] = x
    return this
  }

  /**
	 * addEventListener is an alias of {@link HasEventsMixin#on}
	 * @param {String} event The name of the event
	 * @param {Function} fn The event handler
	 * @throws {Exception} 'unon: no such event <eventname>' if event not defined
	 */
  addEventListener (event, fn) { return this.on(event, fn) }
  /**
	 * removeEventListener is an alias of {@link HasEventsMixin#on}
	 * @param {String} event The name of the event
	 * @param {Function} fn The event handler
	 * @throws {Exception} 'unon: no such event <eventname>' if event not defined
	 */
  removeEventListener (event, fn) { return this.unon(event, fn) }

  /**
	 * Trigger the given event with the specified arguments. Handlers are queued for execution using [setImmediate()]{@link https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate}.
	 * @param {String} event The name of the event
	 * @param {...*} args The event arguments
	 * @throws {Exception} 'trigger: no such event <eventname>' if event not defined
	 */
  trigger (event, ...args) {
    if (!this._events[event]) throw 'trigger: no such event ' + event
    for (var i in this._events[event]) {
      var f = () => { this._events[event][i].apply(null, args) }
      setImmediate(f)
    }
  }

  /**
	 * Return an array of the handlers for the given event.
	 * @returns {Function[]} The handler Functions for the event
	 * @throws {Exception} 'getEventListeners: no such event <eventname>' if event not defined
	 */
  getEventListeners (event) {
    if (!this._events[event]) throw 'getEventListeners: no such event ' + event
    return this._events[event]
  }
}

module.exports = Mixin.export(HasEventsMixin)