Source: managers/EventManager.js

import { Utils } from '../common/Utils'
import { EventTrigger } from '../common/EventTrigger';

const __ = {
	private: Symbol('private'),
}

// #region Private Functions

// Check whether it's keyboard event.
function _isKeyboardEvent(ev) {
	switch (ev.type) {
		case 'keyup':
		case 'keydown':
		case 'keypress':
			return true;

		default:
			break;
	}

	return false;
}

// Check whether it's mouse event.
function _isMouseEvent(ev) {
	switch (ev.type) {
		case 'mousedown':
		case 'mouseup':
		case 'mousemove':
		case 'pointerdown':
		case 'pointerup':
		case 'pointermove':
		case 'click':
		case 'dblclick':
			return true;

		default:
			break;
	}

	return false;
}

// Check whether it's global event.
function _isGlobalEvent(ev) {
	if (_isKeyboardEvent(ev)) {
		return true;
	}

	return false;
}

// Process pick object callback.
function _processPickObjectCall(object, ev) {
	let callback = object.onProcessPickObject || object.constructor.onProcessPickObject;

	// Let the target static function to process pick object event before event trigger
	if (callback) {
		callback(ev, object);
	}
}

// Get proxy object.
function _getProxyObject(object, ev) {
	let proxyObject = object;
	if (proxyObject.getProxyObject()) {
		do {
			_processPickObjectCall(proxyObject, ev);

			proxyObject = proxyObject.getProxyObject();
		}
		while (proxyObject.getProxyObject());
	}

	return proxyObject;
}

// Build mouse hover event.
function _buildMouseHoverEvent(type, object, ev) {
	let result = {
		type,
		object,
		x: ev.x,
		y: ev.y,
		deltaX: ev.deltaX,
		deltaY: ev.deltaY,
		altKey: ev.altKey,
		ctrlKey: ev.ctrlKey,
		shiftKey: ev.shiftKey,
		pickedId: ev.pickedId,
		subNodeName: ev.subNodeName,
	};

	if (ev.target) {
		result.target = ev.target;
	}

	return result;
}

// #endregion

/**
 * @class EventManager
 * The event manager.
 * @memberof THING
 */
class EventManager extends EventTrigger {

	/**
	 * The event manager to register, unregister or trigger event(s).
	 */
	constructor() {
		super();

		this[__.private] = {};
		let _private = this[__.private];

		// The current hover object(for 'mouseenter/mouseleave' event usage)
		_private.hoverInfo = {
			object: null,
			pickedId: null
		}

		_private.app = Utils.getCurrentApp();

		// Hook application delegate events
		_private.app.delegate.addEventListener('*', (ev) => {
			// Process event info
			let pickedObject = this._processEventInfo(ev);

			// Get the event type
			let type = ev.type;

			// Process mouse(enter/leave) event
			if (type == 'mousemove') {
				this._processMouseLeave(pickedObject, ev);
				this._processMouseEnter(pickedObject, ev);
			}
			// Process mouse leave event
			else if (type == 'mouseleave') {
				this._processMouseLeave(pickedObject, ev);
			}
			// Process DOM element enter event
			else if (type == 'domenter') {
				this._processMouseLeave(pickedObject, ev);
			}

			// Dispatch event to object
			if (pickedObject) {
				this.dispatchEvent(type, pickedObject, ev, {});
			}
			else {
				// Check whether it's global event what can dispatch to every objects
				if (_isGlobalEvent(ev)) {
					this.traverseListenerByType(type, (listener) => {
						ev.object = listener.object;

						this.invokeListener(listener, ev);
					});
				}
				// Trigger event by root
				else {
					// We do not hit any objects
					ev.object = null;
					ev.subNodeName = '';

					this.dispatchEvent(type, _private.app.root, ev, {});
				}
			}
		});
	}

	// #region Private

	// Process pick.
	_processPick(ev) {
		let _private = this[__.private];

		let result = _private.app.renderCamera.pick(ev.x, ev.y);
		if (!result) {
			return null;
		}

		let pickedObject = result.object;
		if (pickedObject) {
			pickedObject = _getProxyObject(pickedObject, ev);

			_processPickObjectCall(pickedObject, ev);
		}

		// Copy pick info
		ev.subNodeName = result.subNodeName;
		ev.external = result.external;

		// Copy picked Id when it's existing (it's from merge geometries)
		if (result.pickedId !== undefined) {
			ev.pickedId = result.pickedId;
		}

		// To prevent BIG deal for these actions, we make property here to delay some operations
		Object.defineProperties(ev, {
			'pickedPosition': {
				get() {
					return result.position;
				}
			},
			'normal': {
				get() {
					return result.normal;
				}
			}
		});

		return pickedObject;
	}

	// Check whether can process pick action.
	_needProcessPick(ev) {
		let _private = this[__.private];

		let app = _private.app;

		let picker = app.picker;

		// If mouse up event then we skip the camera control disable picker event
		if (ev.type == 'mouseup') {
			let stateGroup = picker.stateGroup;

			return stateGroup.isEnableByFilter((name) => {
				if (name == 'OrbitControls-start') {
					return false;
				}

				return true;
			});
		}
		else {
			return picker.enable;
		}
	}

	// Process event info.
	_processEventInfo(ev) {
		if (ev.type == 'domenter') {
			ev.target = null;
		}
		else {
			if (_isMouseEvent(ev)) {
				ev.buttonType = Utils.parseMouseButtonType(ev.button);

				// Check whether enable pick
				if (this._needProcessPick(ev)) {
					return this._processPick(ev);
				}
			}
		}

		return null;
	}

	_processMouseLeave(object, ev) {
		let _private = this[__.private];

		// Only works when picker is enable
		let app = _private.app;
		if (!app.picker.enable) {
			return;
		}

		// The hovering object can not be the leaving object
		let hoverInfo = _private.hoverInfo;
		if (!hoverInfo.object || hoverInfo.object == object) {
			return;
		}

		// Notify mouse leave event
		let mouseEvent = _buildMouseHoverEvent('mouseleave', hoverInfo.object, ev);
		if (Utils.isValid(hoverInfo.pickedId)) {
			mouseEvent.pickedId = hoverInfo.pickedId;
		}

		this.dispatchEvent(mouseEvent.type, mouseEvent.object, mouseEvent, {});

		// Clear current hover object
		hoverInfo.object = null;
		hoverInfo.pickedId = null;
	}

	_processMouseEnter(object, ev) {
		let _private = this[__.private];

		// Only works when picker is enable
		let app = _private.app;
		if (!app.picker.enable) {
			return;
		}

		// Make sure we get object by picker
		if (!object) {
			return;
		}

		// Skip for the current hovering object
		let hoverInfo = _private.hoverInfo;
		if (hoverInfo.object == object) {
			return;
		}

		// Notify mouse enter event
		let mouseEvent = _buildMouseHoverEvent('mouseenter', object, ev);
		this.dispatchEvent(mouseEvent.type, mouseEvent.object, mouseEvent, {});

		// Update current hover object
		hoverInfo.object = object;
		if (Utils.isValid(ev.pickedId)) {
			hoverInfo.pickedId = ev.pickedId;
		}
		else {
			hoverInfo.pickedId = null;
		}
	}

	// #endregion

	/**
	 * Get current hover object.
	 * @type {THING.BaseObject}
	 * @private
	 */
	get curHoverObject() {
		let _private = this[__.private];

		let hoverInfo = _private.hoverInfo;
		if (!hoverInfo) {
			return null;
		}

		return hoverInfo.object;
	}

}

export { EventManager }