'use strict';

const { isIterable } = require('./Util');

/**
 * Generic array hash table to store listener definitions `events` is a `Map`
 * whose keys are event names values are the `Set` of listeners to be attached
 * for that event
 */
class EventManager {
  constructor() {
    this.events = new Map();
  }

  /**
   * Fetch all listeners for a given event
   * @param {string} name
   * @return {Set}
   */
  get(name) {
    return this.events.get(name);
  }

  /**
   * @param {string}   eventName
   * @param {Function} listener
   */
  add(eventName, listener) {
    if (!this.events.has(eventName)) {
      this.events.set(eventName, new Set());
    }
    this.events.get(eventName).add(listener);
  }

  /**
   * Attach all currently added events to the given emitter
   * @param {EventEmitter} emitter
   * @param {Object} config
   */
  attach(emitter, config) {
    for (const [ event, listeners ] of this.events) {
      for (const listener of listeners) {
        if (config) {
          emitter.on(event, listener.bind(emitter, config));
        } else {
          emitter.on(event, listener.bind(emitter));
        }
      }
    }
  }

  /**
   * Remove all listeners for a given emitter or only those for the given events
   * If no events are given it will remove all listeners from all events defined
   * in this manager.
   *
   * Warning: This will remove _all_ listeners for a given event list, this includes
   * listeners not in this manager but attached to the same event
   *
   * @param {EventEmitter}  emitter
   * @param {?string|iterable} events Optional name or list of event names to remove listeners from
   */
  detach(emitter, events) {
    if (typeof events === 'string') {
      events = [events];
    } else if (!events) {
      events = this.events.keys();
    } else if (!isIterable(events)) {
      throw new TypeError('events list passed to detach() is not iterable');
    }

    for (const event of events) {
      emitter.removeAllListeners(event);
    }
  }
}

module.exports = EventManager;