import { EventBase } from './EventBase';

/**
 * Base export class for dispatching events
*
* @export class away.events.EventDispatcher
*
*/
export class EventDispatcher {
	private _listenerObjects: Record<string, ListenerObject> = {};
	private _t: any;

	constructor(target: any = null) {
		this._t = target || this;
	}

	/**
	 * Add an event listener
	 * @method addEventListener
	 * @param {String} type of event to add a listener for
	 * @param {Function} listener function
	 */
	public addEventListener(type: string, listener: (event: EventBase) => void): void {
		let l: ListenerObject = this._listenerObjects[type];

		if (l === undefined)
			l = this._listenerObjects[type] = new ListenerObject();

		l.addEventListener(listener);
	}

	/**
	 * Remove an event listener
	 * @method removeEventListener
	 * @param {String} type of event to remove a listener for
	 * @param {Function} listener function
	 */
	public removeEventListener(type: string, listener: (event: EventBase) => void): void {
		const l: ListenerObject = this._listenerObjects[type];

		if (l) {
			l.removeEventListener(listener);

			if (l.numListeners == 0)
				delete this._listenerObjects[type];
		}
	}

	/**
	 * Dispatch an event
	 * @method dispatchEvent
	 * @param {Event} event to dispatch
	 */
	public dispatchEvent(event: EventBase, target: any = null): void {
		const l: ListenerObject = this._listenerObjects[event.type];

		if (l) {
			event.target = target || this._t;
			event.currentTarget = this._t;
			l.dispatchEvent(event);
		}
	}

	/**
	 * check if an object has an event listener assigned to it
	 * @method hasListener
	 * @param {String} type of event to remove a listener for
	 * @param {Function} listener function
	 */
	public hasEventListener(type: string, listener?: (event: EventBase) => void): boolean {
		if (this._listenerObjects[type] === undefined)
			return false;

		if (listener != null)
			return this._listenerObjects[type].getEventListenerIndex(listener) !== -1;

		return this._listenerObjects[type].numListeners > 0;
	}

	protected removeAllEventListeners() {
		this._listenerObjects = {};
	}
}

export class ListenerObject {
	private _index: number = 0;

	private _listeners: Array<(event: EventBase) => void> = new Array<(event: EventBase) => void>();

	public numListeners: number = 0;

	public addEventListener(listener: (event: EventBase) => void): void {
		//check if listener already added
		if (this._listeners.indexOf(listener) !== -1)
			return;

		this._listeners.push(listener);

		this.numListeners++;
	}

	public removeEventListener(listener: (event: EventBase) => void): void {
		//check if listener exists
		const index: number = this._listeners.indexOf(listener);

		if (index === -1)
			return;

		this._listeners.splice(index, 1);

		//deals with removing a listener mid-way through dispatching listeners
		if (index <= this._index)
			this._index--;

		this.numListeners--;
	}

	public dispatchEvent(event: EventBase): void {
		const len: number = this.numListeners;
		for (this._index = 0; this._index < len && this._index < this.numListeners; this._index++) {
			this._listeners[this._index](event);
			if (!event._iAllowedToImmediatlyPropagate) {
				break;
			}
		}
	}

	/**
	 * get Event Listener Index in array. Returns -1 if no listener is added
	 * @method getEventListenerIndex
	 * @param {Function} listener function
	 */
	public getEventListenerIndex(listener: (event: EventBase) => void): number {
		return this._listeners.indexOf(listener);
	}
}