
/**
 * Base class for objects that dispatches events.
 * @class EventEmitter
 * @constructor
 * @example
 *     var emitter = new EventEmitter();
 *     emitter.on('myEvent', function(evt){
 *         console.log(evt.message);
 *     });
 *     emitter.emit({
 *         type: 'myEvent',
 *         message: 'Hello world!'
 *     });
 */

export class EventEmitter {
    tmpArray: any[];
    _listeners
    constructor() {
        this.tmpArray = [];
    }

    /**
     * Add an event listener
     * @method on
     * @param  {String} type
     * @param  {Function} listener
     * @return {EventEmitter} The self object, for chainability.
     * @example
     *     emitter.on('myEvent', function(evt){
     *         console.log('myEvt was triggered!');
     *     });
     */
    on(type: string, listener, context?) {
        listener.context = context || this;
        if (this._listeners === undefined) {
            this._listeners = {};
        }
        var listeners = this._listeners;
        if (listeners[type] === undefined) {
            listeners[type] = [];
        }
        if (listeners[type].indexOf(listener) === - 1) {
            listeners[type].push(listener);
        }
        return this;
    }

    /**
     * Remove an event listener
     * @method off
     * @param  {String} type
     * @param  {Function} listener
     * @return {EventEmitter} The self object, for chainability.
     * @example
     *     emitter.on('myEvent', handler); // Add handler
     *     emitter.off('myEvent', handler); // Remove handler
     */
    off(type: string, listener: Function): EventEmitter {
        var listeners = this._listeners;
        if (!listeners || !listeners[type]) {
            return this;
        }
        var index = listeners[type].indexOf(listener);
        if (index !== - 1) {
            listeners[type].splice(index, 1);
        }
        return this;
    }

    /**
     * Check if an event listener is added
     * @method has
     * @param  {String} type
     * @param  {Function} listener
     * @return {Boolean}
     */
    has(type: string, listener?: Function): boolean {
        if (this._listeners === undefined) {
            return false;
        }
        var listeners = this._listeners;
        if (listener) {
            if (listeners[type] !== undefined && listeners[type].indexOf(listener) !== - 1) {
                return true;
            }
        } else {
            if (listeners[type] !== undefined) {
                return true;
            }
        }

        return false;
    }

    /**
     * Emit an event.
     * @method emit
     * @param  {Object} event
     * @param  {String} event.type
     * @return {EventEmitter} The self object, for chainability.
     * @example
     *     emitter.emit({
     *         type: 'myEvent',
     *         customData: 123
     *     });
     */
    emit(event) {
        if (this._listeners === undefined) {
            return this;
        }
        if (typeof (event) == "string") event = { type: event }
        var listeners = this._listeners;
        var listenerArray = listeners[event.type];
        if (listenerArray !== undefined) {
            event.target = this;

            // Need to copy the listener array, in case some listener was added/removed inside a listener
            var tmpArray = this.tmpArray;
            for (var i = 0, l = listenerArray.length; i < l; i++) {
                tmpArray[i] = listenerArray[i];
            }
            for (var i = 0, m = tmpArray.length; i < m; i++) {
                var listener = tmpArray[i];
                listener.call(listener.context, event);
            }
            tmpArray.length = 0;
        }
        return this;
    }
};
