import { CancelablePromise } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';
import { ActionQueueType } from '../const';

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

// #region Private Functions

// Wait for async complete.
function _waitAsyncCallback(resolve, asyncCallback) {
	if (_DEBUG) {
		const _checkAsyncCallbackInterval = 10 * 1000;

		let handler = setInterval(() => {
			Utils.error(`Did you forget to invoke resolve() in\n`, asyncCallback);

			clearInterval(handler);
		}, _checkAsyncCallbackInterval);

		let callback = () => {
			clearInterval(handler);

			resolve();
		}

		return callback;
	}
	else {
		return resolve;
	}
}

// #endregion

/**
 * 层级事件信息。
 * @typedef {object} LevelEventInfo
 * @property {Array<THING.BaseObject>} path 层级路径。
 * @property {THING.BaseObject} origin 原始层级对象。
 * @property {THING.BaseObject} prev 前一个层级对象。
 * @property {THING.BaseObject} current 当前层级对象。
 * @property {THING.BaseObject} next 下一个层级对象(仅用于离开操作)。
 * @property {object} options 层级选项。
 */

/**
 * 异步模式下离开自身层级时的回调。
 * @callback OnLeaveLevelAsyncCallback
 * @param {LevelEventInfo} ev 事件信息。
 * @param {Function} resolve Promise resolve 回调函数。
 * @param {Function} reject Promise reject 回调函数。
 */

/**
 * 离开自身层级时的回调。
 * @callback OnLeaveLevelCallback
 * @param {LevelEventInfo} ev 事件信息。
 */

/**
 * 异步模式下进入自身层级时的回调。
 * @callback OnEnterLevelAsyncCallback
 * @param {LevelEventInfo} ev 事件信息。
 * @param {Function} resolve Promise resolve 回调函数。
 * @param {Function} reject Promise reject 回调函数。
 */

/**
 * 进入自身层级时的回调。
 * @callback OnEnterLevelCallback
 * @param {LevelEventInfo} ev 事件信息。
 */

/**
 * 进入自身层级完成时的回调。
 * @callback OnFinishedEnterLevelCallback
 * @param {LevelEventInfo} ev 事件信息。
 */

/**
 * @class LevelComponent
 * 对象层级控制器
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class LevelComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * 提供物体层级切换能力的功能组件
	 */
	constructor() {
		// #region Overrides

		/**
		 * 离开层级时的回调函数。
		 * @member {OnLeaveLevelCallback} onLeave
		 * @memberof THING.LevelComponent
		 * @instance
		 * @private
		 */

		/**
		 * 异步模式下离开层级时的回调函数。
		 * @member {OnLeaveLevelAsyncCallback} onLeaveAsync
		 * @memberof THING.LevelComponent
		 * @instance
		 * @private
		 */

		/**
		 * 进入层级时的回调函数。
		 * @member {OnEnterLevelCallback} onEnter
		 * @memberof THING.LevelComponent
		 * @instance
		 * @private
		 */

		/**
		 * 异步模式下进入层级时的回调函数。
		 * @member {OnEnterLevelAsyncCallback} onEnterAsync
		 * @memberof THING.LevelComponent
		 * @instance
		 * @private
		 */

		/**
		 * 完成进入层级时的回调函数。
		 * @member {OnFinishedEnterLevelCallback} onFinish
		 * @memberof THING.LevelComponent
		 * @instance
		 * @private
		 */

		// #endregion

		super();

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

		_private.isLeave = false;

		// Settings config in the level
		_private.config = {
			ignoreVisible: false,
			ignoreEvent: false,
			ignoreStyle: false
		}

		_private.generateLevelControlParams = (eventParams) => {
			let target = null;
			if (eventParams.path) {
				target = eventParams.path[eventParams.path.length - 1];
			}

			return {
				origin: eventParams.origin,
				prev: eventParams.prev,
				current: eventParams.current,
				next: eventParams.next,
				target: target,
				path: eventParams.path,
				options: eventParams.options
			};
		}

		_private.viewpoint = null;
		_private.viewpointName = '';
		_private.viewpointMap = new Map();
	}

	// #region Private


	#importViewpoint(params, _private) {
		if (params.viewpointName) {
			_private.viewpointName = params.viewpointName;
			_private.viewpointMap = new Map(Object.entries(params.viewpointMap));
		}
		else if (params.viewpoint) {
			_private.viewpoint = Utils.cloneObject(params.viewpoint);
		}
	}

	#importConfig(params, _private) {
		if (params.config) {
			_private.config = {
				ignoreVisible: params.config.ignoreVisible || false,
				ignoreEvent: params.config.ignoreEvent || false,
				ignoreStyle: params.config.ignoreStyle || false
			};
		}
	}

	#exportViewpoint(result, _private) {
		if (_private.viewpointName) {
			result.viewpointName = Utils.cloneObject(_private.viewpointName);
			result.viewpointMap = Object.fromEntries(_private.viewpointMap);
		}
		else if (_private.viewpoint) {
			result.viewpoint = Utils.cloneObject(_private.viewpoint);
		}
	}

	#exportConfig(result, _private) {
		let config = null;
		if (_private.config.ignoreEvent === true) {
			config = config ? config : {};
			config.ignoreEvent = true;
		}

		if (_private.config.ignoreStyle === true) {
			config = config ? config : {};
			config.ignoreStyle = true;
		}

		if (_private.config.ignoreVisible === true) {
			config = config ? config : {};
			config.ignoreVisible = true;
		}

		// Export config
		if (config) {
			result.config = config;
		}
	}

	/**
	 * 离开自身层级。
	 * @param {object} param 参数。
	 * @private
	 */
	leave(param) {
		return new CancelablePromise((resolve, reject, onCancel) => {
			onCancel(() => {
			});

			if (this.onLeaveAsync) {
				this.onLeaveAsync.call(this, param, _waitAsyncCallback(resolve, this.onLeaveAsync), reject);
			}
			else if (this.onLeave) {
				this.onLeave.call(this, param);

				resolve();
			}
			else {
				resolve();
			}
		});
	}

	/**
	 * 进入自身层级。
	 * @param {object} param 参数。
	 * @private
	 */
	enter(param) {
		let _private = this[__.private];

		return new CancelablePromise((resolve, reject, onCancel) => {
			onCancel((cancelParam) => {
				let levelControls = cancelParam.levelControls;
				let controlParams = _private.generateLevelControlParams(cancelParam);
				levelControls.forEach(control => {
					if (control.isEnabled) {
						control.onLeave(controlParams);
					}
				});
				_private.isLeave = true;
			});

			if (this.onEnterAsync) {
				this.onEnterAsync.call(this, param, _waitAsyncCallback(resolve, this.onEnterAsync), reject);
			}
			else if (this.onEnter) {
				this.onEnter.call(this, param);

				resolve();
			}
			else {
				resolve();
			}
		});
	}

	/**
	 * 完成进入自身层级。
	 * @param {object} param 参数。
	 * @private
	 */
	finish(param) {
		if (this.onFinish) {
			this.onFinish(param);
		}
	}

	/**
	 * 根据名称获取层级动作。
	 * @param {string} name 动作名称。
	 * @returns {ActionProcessor}
	 * @private
	 */
	getActionByName(name) {
		let actionQueue = this.actionQueue;
		if (!actionQueue) {
			return null;
		}

		return actionQueue.getByName(name);
	}

	/**
	 * 根据类型获取层级动作。
	 * @param {*} type 动作类型。
	 * @returns {ActionProcessor}
	 * @private
	 */
	getActionByType(type) {
		let actionQueue = this.actionQueue;
		if (!actionQueue) {
			return null;
		}

		return actionQueue.getByType(type);
	}

	/**
	 * 启用/禁用动作。
	 * @param {string} name 动作名称或类型。
	 * @param {boolean} value 动作启用状态。
	 * @private
	 */
	enableAction(name, value) {
		let actionQueue = this.actionQueue;
		if (!actionQueue) {
			return;
		}

		actionQueue.enable(name, value);
	}

	/**
	 * 检查是否启用了动作。
	 * @param {string} name 动作名称或类型。
	 * @returns {boolean}
	 * @private
	 */
	isActionEnabled(name) {
		let actionQueue = this.actionQueue;
		if (!actionQueue) {
			return false;
		}

		return actionQueue.isEnabled(name);
	}

	// #endregion

	// #region Property

	get isCurrentLevel() {
		let app = this.object.app;

		return this.object == app.levelManager.current;
	}

	/**
	 * 获取层级动作队列。
	 * @type {THING.ActionQueue}
	 * @private
	 */
	get actionQueue() {
		return this.object.actionGroup.get(ActionQueueType.EnterLevel);
	}

	/**
	 * 获取层级动作。
	 * @type {Array<ActionProcessor>}
	 * @private
	 */
	get actions() {
		let actionQueue = this.actionQueue;
		if (!actionQueue) {
			return [];
		}

		return actionQueue.actions;
	}

	/**
	 * 层级配置参数信息
	 * @typedef {object} LevelConfig
	 * @property {boolean} ignoreVisible - 是否忽略层级设置中对象的可见性设置。
	 * @property {boolean} ignoreEvent - 是否忽略层级设置中对象的鼠标事件。
	 * @property {boolean} ignoreStyle - 是否忽略层级设置中对象的样式。
	 * @public
	 */

	/**
	 * 获取/设置层级配置。需要在层级改变之前,进行层级配置。
	 * @type {LevelConfig}
	 *
	 * @example
	 * // 获取当前层级配置
	 * let levelComponent  = THING.App.current.root.level;
	 * levelComponent.config = { ignoreVisible: true, ignoreStyle: false };
	 * // @expect { levelComponent.config.ignoreVisible === true }
	 * // @expect { levelComponent.config.ignoreStyle === false }
	 * @public
	 */
	get config() {
		let _private = this[__.private];

		return _private.config;
	}
	set config(value) {
		let _private = this[__.private];

		_private.config = value;
	}

	/**
	 * 视点信息
	 * @typedef {object} Viewpoint
	 * @property {Array<number>} position 摄像机位置。
	 * @property {Array<number>} target 目标位置。
	 * @public
	 */

	/**
	 * 获取/设置摄像机的局部位置和目标位置。此信息是从场景文件里读取出来的
	 * @type {Viewpoint}
	 *
	 * @example
	 * let levelComponent = THING.App.current.root.level;
	 * // Get the local viewpoint
	 * let localViewpoint = levelComponent.localViewpoint;
	 * let ret1 = typeof localViewpoint === 'object';
	 * let ret2 = localViewpoint === null;
	 * // @expect { ret1 == true || ret2 == true }
	 *
	 * // Set the local viewpoint
	 * levelComponent.localViewpoint = {
	 *   position: [0, 0, 0],
	 *   target: [1, 1, 1]
	 * };
	 * // @expect {levelComponent.localViewpoint.position[0] === 0 && levelComponent.localViewpoint.target[2] === 1}
	 * @public
	 */
	get localViewpoint() {
		const _private = this[__.private];
		if (_private.viewpointName) {
			return _private.viewpointMap.get(_private.viewpointName);
		}

		return _private.viewpoint;
	}
	set localViewpoint(value) {
		const _private = this[__.private];
		if (_private.viewpointName) {
			value ? _private.viewpointMap.set(_private.viewpointName, Utils.cloneObject(value)) : _private.viewpointMap.set(_private.viewpointName, null);
		}
		else {
			_private.viewpoint = value ? Utils.cloneObject(value) : null;
		}
	}

	/**
	 * 获取/设置摄像机的位置和目标位置。此信息是从场景文件里读取出来的
	 * @type {Viewpoint}
	 *
	 * @example
	 * let levelComponent = THING.App.current.root.level;
	 * // Get the world viewpoint
	 * let viewpoint = levelComponent.viewpoint;
	 * let ret1 = typeof viewpoint === 'object';
	 * let ret2 = viewpoint === null;
	 * // @expect { ret1 == true || ret2 == true }
	 *
	 * // Set the world viewpoint
	 * levelComponent.viewpoint = {
	 *   position: [0, 0, 0],
	 *   target: [1, 1, 1]
	 * };
	 * // @expect {levelComponent.viewpoint.position[0] === 0 && levelComponent.viewpoint.target[2] === 1}
	 * @public
	 */
	get viewpoint() {
		const _private = this[__.private];
		const viewpoint = _private.viewpoint || (_private.viewpointName && _private.viewpointMap.get(_private.viewpointName));

		if (!viewpoint) {
			return null;
		}

		return {
			target: this.object.selfToWorld(viewpoint.target),
			position: this.object.selfToWorld(viewpoint.position)
		};
	}
	set viewpoint(value) {
		const _private = this[__.private];

		if (!value) {
			_private.viewpointName ? _private.viewpointMap.set(_private.viewpointName, null) : _private.viewpoint = null;
			return;
		}

		const viewpoint = {
			target: this.object.worldToSelf(value.target),
			position: this.object.worldToSelf(value.position)
		};

		_private.viewpointName ? _private.viewpointMap.set(_private.viewpointName, viewpoint) : _private.viewpoint = viewpoint;
	}

	get viewpointName() {
		const _private = this[__.private];
		return _private.viewpointName;
	}
	set viewpointName(value) {
		const _private = this[__.private];
		_private.viewpointName = value;
	}

	get viewpointMap() {
		const _private = this[__.private];
		return _private.viewpointMap;
	}
	set viewpointMap(value) {
		const _private = this[__.private];
		if (value instanceof Map) {
			_private.viewpointMap = new Map(value);
		}
		else {
			_private.viewpointMap = new Map(Object.entries(value));
		}
	}

	// #endregion

	/**
	 * @type {Function}
	 * @private
	 */
	async onLeaveAsync(param, resolve, reject) {
		let _private = this[__.private];

		if (!_private.isLeave) {
			let controlParams = _private.generateLevelControlParams(param);

			let levelControls = param.levelControls;
			for (let i = 0; i < levelControls.length; i++) {
				const control = levelControls[i];
				await control.onLeave(controlParams);
			}
			_private.isLeave = true;
		}
		resolve();
	}

	/**
	 * @type {Function}
	 * @private
	 */
	async onEnterAsync(param, resolve, reject) {
		let _private = this[__.private];
		_private.isLeave = false;

		let controlParams = _private.generateLevelControlParams(param);

		let levelControls = param.levelControls;

		for (let i = 0; i < levelControls.length; i++) {
			const control = levelControls[i];
			if (control) {
				if (control.isEnabled) {
					await control.onEnter(controlParams);
				}
			}
		}
		resolve();
	}

	/**
	 * 从外部资源导入数据。
	 * @param {object} external 配置信息 参加下面示例
	 *
	 * @example
	 * let levelComponent = THING.App.current.root.level;
	 * // Import data from an external source
	 * // Example 1: Importing data
	 * levelComponent.onImport({ viewpoint: { position: [1, 2, 3], target: [4, 5, 6] }, config: { ignoreStyle:true, ignoreVisible:false } });
	 * // @expect { levelComponent.localViewpoint.position[0] === 1 }
	 * // @expect { levelComponent.localViewpoint.target[2] === 6 }
	 * // @expect { levelComponent.config.ignoreStyle === true }
	 * // @expect { levelComponent.config.ignoreVisible === false }
	 * @public
	 */
	onImport(external) {
		if (!external) {
			return;
		}

		const _private = this[__.private];

		// Import viewpoint
		this.#importViewpoint(external, _private);

		// Import config
		this.#importConfig(external, _private);
	}

	/**
	 * 导出配置信息
	 * @returns {object|null} 配置信息
	 *
	 * @example
	 * let levelComponent = THING.App.current.root.level;
	 * levelComponent.onImport({ viewpoint: { position: [1, 2, 3], target: [4, 5, 6] }, config: { ignoreStyle:true, ignoreVisible:false } });
	 * // Export data to an external source
	 * let exportedData = levelComponent.onExport();
	 * // @expect { exportedData.viewpoint.position[1] === 2 }
	 * // @expect { exportedData.viewpoint.target[0] === 4 }
	 * // @expect { exportedData.config.ignoreStyle === true }
	 * let ret = exportedData.config.ignoreVisible == undefined;
	 * // @expect { ret === true }
	 * @public
	 */
	onExport() {
		let _private = this[__.private];
		let result = {};

		// Export viewpoint
		this.#exportViewpoint(result, _private);

		// False is the default value and is not export
		this.#exportConfig(result, _private);

		return Object.keys(result).length ? result : null;
	}

}

export { LevelComponent }