Source: events/EventQueue.js

// (c) Andrew Wei

'use strict';

import EventDispatcher from './EventDispatcher';
import EventType from '../enums/EventType';
import assert from '../helpers/assert';

/**
 * @class
 *
 * A queue that enqueues multiple events of event dispatchers and notifies when
 * all enqueued events have been dispatched.
 *
 * @alias module:requiem~events.EventQueue
 */
class EventQueue extends EventDispatcher {
  /**
   * Gets the queued up event pool.
   *
   * @type {Object}
   */
  get eventPool() {
    if (this.__private__ === undefined) this.__private__ = {};
    if (this.__private__.eventPool === undefined) this.__private__.eventPool = {};
    return this.__private__.eventPool;
  }

  /**
   * Specifies whether this EventQueue instance is already waiting for queued
   * events.
   *
   * @type {boolean}
   */
  get isWaiting() {
    if (this.__private__ === undefined) this.__private__ = {};
    return this.__private__.isWaiting || false;
  }

  /**
   * Adds an event with the specified event dispatcher and event type to the
   * queue.
   *
   * @param  {EventDispatcher} eventDispatcher - EventDispatcher instance to
   *                                             register the event with.
   * @param  {string} eventType - Name of the event to register.
   */
  enqueue(eventDispatcher, eventType) {
    assert((typeof eventDispatcher.dispatchEvent === 'function') && (typeof eventDispatcher.addEventListener === 'function'), `Not a valid event dispatcher instance`);
    assert(!this.isWaiting, 'Cannot enqueue when EventQueue instance is already waiting for events');
    if (this.eventPool[eventType] === undefined) this.eventPool[eventType] = [];

    let pool = this.eventPool[eventType];
    pool.forEach((event) => assert(event.dispatcher !== eventDispatcher, `The event '${eventType}' of ${eventDispatcher} is already in the queue`));
    pool.push({ dispatcher: eventDispatcher });
  }

  /**
   * Removes an event with the specified event dispatcher and event type from
   * the queue.
   *
   * @param  {EventDispatcher} eventDispatcher - EventDispatcher instance which
   *                                             the event was registered with.
   * @param  {string} eventType - Name of the event which the event was
   *                              registered with.
   */
  dequeue(eventDispatcher, eventType) {
    assert(!this.isWaiting, 'Cannot dequeue when EventQueue instance is already waiting for events');
    let pool = this.eventPool[eventType];
    if (!pool) return;
    let n = pool.length;
    let t = -1;
    for (let i = 0; i < n; i++) {
      if (pool[i].dispatcher === dispatcher) {
        t = i;
        break;
      }
    }
    if (~t) pool.splice(t, 1);
    if (pool.length === 0) delete this.eventPool[eventType];
  }

  /**
   * Starts waiting for queued up events.
   */
  start() {
    for (let eventType in this.eventPool) {
      let pool = this.eventPool[eventType];
      pool.forEach((eventPair) => {
        assert(eventPair.handler === undefined, `Handler not supposed to be defined at this point`);
        eventPair.handler = () => {
          eventPair.dispatcher.removeEventListener(eventType, eventPair.handler);
          delete eventPair.handler;

          let allDone = true;

          for (let t in this.eventPool) {
            let p = this.eventPool[t];
            let n = p.length;
            for (let i = 0; i < n; i++) {
              if (p[i].handler) {
                allDone = false;
                break;
              }
            }
            if (!allDone) break;
          }

          if (allDone) {
            this.kill();
            this.dispatchEvent(new CustomEvent(EventType.OBJECT.COMPLETE));
          }
        };

        eventPair.dispatcher.addEventListener(eventType, eventPair.handler);
        this.__private__.isWaiting = true;
      });
    }

    if (!this.__private__.isWaiting) {
      this.kill();
      this.dispatchEvent(new CustomEvent(EventType.OBJECT.COMPLETE));
    }
  }

  /**
   * Kills this EventQueue instance, setting everything for garbage collection.
   */
  kill() {
    for (let eventType in this.eventPool) {
      let pool = this.eventPool[eventType];
      pool.forEach((eventPair) => {
        if (eventPair.handler) eventPair.dispatcher.removeEventListener(eventType, eventPair.handler);
      });
    }

    delete this.__private__.eventPool;
    this.__private__.isWaiting = false;
  }
}

export default EventQueue;