/* eslint-disable no-restricted-syntax */
/**
 * this module contains helper functions for managing events
 * @author Aamir khan
 * @module utils/events/eventManager
 */


/**
 * Create a new `Event Emitter`.
 * @constructor
 * @api public
 */
function EventManager() {}

/**
 * Mixin the emitter properties.
 *
 * @param {Object} obj
 * @return {Object}
 * @api public
 */

EventManager.extend = function extend(obj, name) {
    // eslint-disable-next-line guard-for-in
    for (const key in EventManager.prototype) {
        // eslint-disable-next-line no-param-reassign
        obj[key] = EventManager.prototype[key];
    }
    return obj;
}

/**
 * Listen on the given `event` with `fn`.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {EventManager}
 * @api public
 */

EventManager.prototype.addEvent = function addEvent(event, fn) {
    if(!event){
        throw new Error("event name is required");
    }
    this.log("regestring event", event);
    this.callbacks = this.callbacks || {};
    (this.callbacks['$' + event] = this.callbacks['$' + event] || [])
        .push(fn);
    return () => this.removeEvent(event, fn);
};

/**
 * Adds an `event` listener that will be invoked a single
 * time then automatically removed.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {EventManager}
 * @api public
 */

EventManager.prototype.addEventOnce = function addEventOnce(event, fn) {
    this.log(`adding ${event} for once`);
    function on() {
        this.removeEvent(event, on);
        fn.apply(this, arguments);
    }
    on.fn = fn;
    this.addEvent(event, on);
    return this;
};

/**
 * Remove the given callback for `event` or all
 * registered callbacks.
 *
 * @param {String} event
 * @param {Function} fn
 * @return {EventManager}
 * @api public
 */

EventManager.prototype.removeEvent = function removeEvent(event, fn) {
    this.log("removing event", event);
    this.callbacks = this.callbacks || {};

    // all
    if (arguments.length === 0) {
        this.callbacks = {};
        return this;
    }

    // specific event
    const callbacks = this.callbacks['$' + event];
    if (!callbacks) return this;

    // remove all handlers
    if (arguments.length === 1) {
        delete this.callbacks['$' + event];
        return this;
    }

    // remove specific handler
    let cb;
    for (let i = 0; i < callbacks.length; i += 1) {
        cb = callbacks[i];
        if (cb === fn || cb.fn === fn) {
            callbacks.splice(i, 1);
            break;
        }
    }

    // Remove event specific arrays for event types that no
    // one is subscribed for to avoid memory leak.
    if (callbacks.length === 0) {
        delete this.callbacks['$' + event];
    }

    return this;
};

/**
 * Emit `event` with the given args.
 *
 * @param {String} event
 * @param {Mixed} ...
 * @return {EventManager}
 */

EventManager.prototype.emit = function emit(event) {
    this.callbacks = this.callbacks || {};

    const args = new Array(arguments.length - 1);
    let callbacks = this.callbacks['$' + event];
    this.log("emit", event, args, callbacks);


    for (let i = 1; i < arguments.length; i += 1) {
        // eslint-disable-next-line prefer-rest-params
        args[i - 1] = arguments[i];
    }

    if (callbacks) {
        callbacks = callbacks.slice(0);
        // eslint-disable-next-line no-plusplus
        for (let i = 0, len = callbacks.length; i < len; ++i) {
            callbacks[i].apply(this, args);
        }
    }

    return this;
};

/**
 * Return array of callbacks for `event`.
 *
 * @param {String} event
 * @return {Array}
 * @api public
 */

EventManager.prototype.listeners = function listeners(event) {
    this.callbacks = this.callbacks || {};
    return this.callbacks['$' + event] || [];
};

/**
 * Check if this emitter has `event` handlers.
 *
 * @param {String} event
 * @return {Boolean}
 * @api public
 */

EventManager.prototype.hasListeners = function hasListeners(event) {
    return !!this.listeners(event).length;
};


export default EventManager;