‘use strict’;

/**

* Class representing an event.
*
* @private
*/

class Event {

/**
 * Create a new `Event`.
 *
 * @param {String} type The name of the event
 * @param {Object} target A reference to the target to which the event was
 *     dispatched
 */
constructor(type, target) {
  this.target = target;
  this.type = type;
}

}

/**

* Class representing a message event.
*
* @extends Event
* @private
*/

class MessageEvent extends Event {

/**
 * Create a new `MessageEvent`.
 *
 * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
 * @param {WebSocket} target A reference to the target to which the event was
 *     dispatched
 */
constructor(data, target) {
  super('message', target);

  this.data = data;
}

}

/**

* Class representing a close event.
*
* @extends Event
* @private
*/

class CloseEvent extends Event {

/**
 * Create a new `CloseEvent`.
 *
 * @param {Number} code The status code explaining why the connection is being
 *     closed
 * @param {String} reason A human-readable string explaining why the
 *     connection is closing
 * @param {WebSocket} target A reference to the target to which the event was
 *     dispatched
 */
constructor(code, reason, target) {
  super('close', target);

  this.wasClean = target._closeFrameReceived && target._closeFrameSent;
  this.reason = reason;
  this.code = code;
}

}

/**

* Class representing an open event.
*
* @extends Event
* @private
*/

class OpenEvent extends Event {

/**
 * Create a new `OpenEvent`.
 *
 * @param {WebSocket} target A reference to the target to which the event was
 *     dispatched
 */
constructor(target) {
  super('open', target);
}

}

/**

* Class representing an error event.
*
* @extends Event
* @private
*/

class ErrorEvent extends Event {

/**
 * Create a new `ErrorEvent`.
 *
 * @param {Object} error The error that generated this event
 * @param {WebSocket} target A reference to the target to which the event was
 *     dispatched
 */
constructor(error, target) {
  super('error', target);

  this.message = error.message;
  this.error = error;
}

}

/**

* This provides methods for emulating the `EventTarget` interface. It's not
* meant to be used directly.
*
* @mixin
*/

const EventTarget = {

/**
 * Register an event listener.
 *
 * @param {String} type A string representing the event type to listen for
 * @param {Function} listener The listener to add
 * @param {Object} [options] An options object specifies characteristics about
 *     the event listener
 * @param {Boolean} [options.once=false] A `Boolean`` indicating that the
 *     listener should be invoked at most once after being added. If `true`,
 *     the listener would be automatically removed when invoked.
 * @public
 */
addEventListener(type, listener, options) {
  if (typeof listener !== 'function') return;

  function onMessage(data) {
    listener.call(this, new MessageEvent(data, this));
  }

  function onClose(code, message) {
    listener.call(this, new CloseEvent(code, message, this));
  }

  function onError(error) {
    listener.call(this, new ErrorEvent(error, this));
  }

  function onOpen() {
    listener.call(this, new OpenEvent(this));
  }

  const method = options && options.once ? 'once' : 'on';

  if (type === 'message') {
    onMessage._listener = listener;
    this[method](type, onMessage);
  } else if (type === 'close') {
    onClose._listener = listener;
    this[method](type, onClose);
  } else if (type === 'error') {
    onError._listener = listener;
    this[method](type, onError);
  } else if (type === 'open') {
    onOpen._listener = listener;
    this[method](type, onOpen);
  } else {
    this[method](type, listener);
  }
},

/**
 * Remove an event listener.
 *
 * @param {String} type A string representing the event type to remove
 * @param {Function} listener The listener to remove
 * @public
 */
removeEventListener(type, listener) {
  const listeners = this.listeners(type);

  for (let i = 0; i < listeners.length; i++) {
    if (listeners[i] === listener || listeners[i]._listener === listener) {
      this.removeListener(type, listeners[i]);
    }
  }
}

};

module.exports = EventTarget;