
/**
 * Handles named events.
 */
export default class NamedEventHandler {
  constructor () {
    this._eventListener = new Map()
    this._stateListener = new Map()
  }

  /**
   * @private
   * Returns all listeners that listen to a specified name.
   *
   * @param {String} name The query event name.
   */
  _getListener (name) {
    let listeners = this._eventListener.get(name)
    if (listeners === undefined) {
      listeners = {
        once: new Set(),
        on: new Set()
      }
      this._eventListener.set(name, listeners)
    }
    return listeners
  }

  /**
   * Adds a named event listener. The listener is removed after it has been
   * called once.
   *
   * @param {String} name The event name to listen to.
   * @param {Function} f The function that is executed when the event is fired.
   */
  once (name, f) {
    let listeners = this._getListener(name)
    listeners.once.add(f)
  }

  /**
   * Adds a named event listener.
   *
   * @param {String} name The event name to listen to.
   * @param {Function} f The function that is executed when the event is fired.
   */
  on (name, f) {
    let listeners = this._getListener(name)
    listeners.on.add(f)
  }

  /**
   * @private
   * Init the saved state for an event name.
   */
  _initStateListener (name) {
    let state = this._stateListener.get(name)
    if (state === undefined) {
      state = {}
      state.promise = new Promise(function (resolve) {
        state.resolve = resolve
      })
      this._stateListener.set(name, state)
    }
    return state
  }

  /**
   * Returns a Promise that is resolved when the event name is called.
   * The Promise is immediately resolved when the event name was called in the
   * past.
   */
  when (name) {
    return this._initStateListener(name).promise
  }

  /**
   * Remove an event listener that was registered with either
   * {@link EventHandler#on} or {@link EventHandler#once}.
   */
  off (name, f) {
    if (name == null || f == null) {
      throw new Error('You must specify event name and function!')
    }
    const listener = this._eventListener.get(name)
    if (listener !== undefined) {
      listener.on.delete(f)
      listener.once.delete(f)
    }
  }

  /**
   * Emit a named event. All registered event listeners that listen to the
   * specified name will receive the event.
   *
   * @param {String} name The event name.
   * @param {Array} args The arguments that are applied to the event listener.
   */
  emit (name, ...args) {
    this._initStateListener(name).resolve()
    const listener = this._eventListener.get(name)
    if (listener !== undefined) {
      listener.on.forEach(f => f.apply(null, args))
      listener.once.forEach(f => f.apply(null, args))
      listener.once = new Set()
    } else if (name === 'error') {
      console.error(args[0])
    }
  }
  destroy () {
    this._eventListener = null
  }
}
