import { Flags, CancelablePromise } from '@uino/base-thing';
import { Utils } from '../common/Utils'
import { EventType } from '../const';
import { LevelControlManager } from './LevelControlManager';
import { BaseComponentGroup } from '../main';

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

const Flag = {
	Changing: 1 << 0,
}

const cEnterLevelCompleteEventTag = '__LevelManager_enter_level_complete_event';
const cLeaveLevelCompleteEventTag = '__LevelManager_leave_level_complete_event';
const cDestroyObjectEventTag = '__LevelManager_object_destroy_event';

/**
 * @class LevelManager
 * 层级管理器
 * @memberof THING
 * @public
 * @example
 * const manager = app.levelManager;
 */
class LevelManager extends BaseComponentGroup {

	/**
	 * 层级管理器类,用于管理层级切换和过渡
	 */
	constructor(app) {
		super();

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

		_private.enable = true;

		_private.pendingPromises = [];
		_private.changeOptions = null;

		_private.prev = null;
		_private.current = null;
		_private.leavingObject = null;
		_private.enteringObject = null;

		_private.flags = new Flags();

		_private.onFilterPath = null;

		_private.app = app;

		this.addComponent(new LevelControlManager(app), 'levelControlManager', { isResident: true });

		// Leave current level when it had been destroyed
		_private.app.on(EventType.BeforeDestroy, '*', (ev) => {
			if (!_private.current) {
				return;
			}

			const target = ev.object;
			if (_private.current == target || _private.current.isChildOf(target)) {
				this.quit();
			}
		}, cDestroyObjectEventTag);
	}

	// #region Private

	// Notify event.
	_notifyEvent(type, params) {
		let _private = this[__.private];

		let app = _private.app;

		let object = params['current'];

		// Trigger global events
		app.trigger(type, params);

		// Prevent root trigger duplicated event
		if (object != app.root) {
			object.trigger(type, params);
		}
	}

	_notifyComplete(options, path) {
		let _private = this[__.private];

		// Clear change options
		_private.changeOptions = null;

		// Clear pendingPromises
		_private.pendingPromises.length = 0;

		// Finished change
		_private.flags.enable(Flag.Changing, false);

		// Build event to notify outside
		let ev = {
			prev: _private.prev,
			current: _private.current,
			path
		};

		let onComplete = Utils.parseValue(options['onComplete'], options['complete']);
		if (onComplete) {
			onComplete(ev);
		}

		_private.app.trigger(EventType.CompleteEnterLevel, ev);

		_private.current.trigger(EventType.CompleteEnterLevel, ev);
	}

	_enterObjectLevel(object, options, path, origin) {
		let _private = this[__.private];

		// Update level
		_private.prev = _private.current;
		_private.current = object;

		if (object.destroyed) {
			return CancelablePromise.resolve();
		}

		const enterEventParam = this._generateEventParam(path, origin, null, options);

		// Notify before enter level event
		this._notifyEvent(EventType.BeforeEnterLevel, enterEventParam);

		_private.enteringObject = object;

		_private.changingOptions = enterEventParam;

		let enterParam = Object.assign({}, enterEventParam);

		enterParam.levelControls = this.getControlsByObject(object);

		// Enter level
		let enterPromises = [];
		const controlPromise = object.level.enter(enterParam);
		enterPromises.push(controlPromise);

		if (!this.levelControlManager.controlCount) {
			const eventPromise = new Promise((resolve) => {
				_private.app.once(EventType.ObjectLevelComplete, () => {
					resolve();
				});

				this._notifyEvent(EventType.EnterObjectLevel, enterEventParam);
			}, cEnterLevelCompleteEventTag);
			enterPromises.push(eventPromise);
		}

		return new CancelablePromise((resolve, reject, onCancel) => {
			let canceled = false;
			onCancel(() => {
				canceled = true;
			});

			Promise.all(enterPromises).then(() => {
				if (canceled) {
					return;
				}

				// Finished level changed
				object.level.finish(enterParam);

				_private.enteringObject = null;

				// Notify enter level event
				this._notifyEvent(EventType.EnterLevel, enterEventParam);

				// Notify after enter level event
				this._notifyEvent(EventType.AfterEnterLevel, enterEventParam);

				// Check whether reach the target object
				if (object == path[path.length - 1]) {
					this._notifyComplete(options, path);
				}

				_private.changingOptions = null;

				resolve();
			}).catch(reject);
		});
	}

	_leaveObjectLevel(object, options, path, origin) {
		const _private = this[__.private];

		if (!_private.current) {
			return CancelablePromise.resolve();
		}
		if (_private.current.destroyed) {
			return CancelablePromise.resolve();
		}
		if (_private.current == _private.leavingObject) {
			return CancelablePromise.resolve();
		}

		_private.leavingObject = _private.current;

		const leaveEventParam = this._generateEventParam(path, origin, object, options);

		// Notify before leave level event
		this._notifyEvent(EventType.BeforeLeaveLevel, leaveEventParam);

		_private.changingOptions = leaveEventParam;

		// level control promise
		const leavePromises = [];

		const leaveParam = Object.assign({}, leaveEventParam);
		leaveParam.levelControls = this.getControlsByObject(_private.current);
		const controlPromise = _private.current.level.leave(leaveParam);
		leavePromises.push(controlPromise);


		if (!this.levelControlManager.controlCount) {
			// event promise
			const eventPromise = new Promise((resolve) => {
				_private.app.once(EventType.ObjectLevelComplete, () => {
					resolve();
				});

				this._notifyEvent(EventType.LeaveObjectLevel, leaveEventParam);
			}, cLeaveLevelCompleteEventTag);
			leavePromises.push(eventPromise);
		}

		return new CancelablePromise((resolve, reject, onCancel) => {
			let canceled = false;
			onCancel(() => {
				canceled = true;
			});

			Promise.all(leavePromises).then(() => {
				if (canceled) {
					return;
				}

				// Notify leave level event
				this._notifyEvent(EventType.LeaveLevel, leaveEventParam);

				// Notify after leave level event
				this._notifyEvent(EventType.AfterLeaveLevel, leaveEventParam);

				_private.leavingObject = null;
				_private.changingOptions = null;

				resolve();
			}).catch(reject);
		})
	}

	_changeObject(object, options, path, origin) {
		let _private = this[__.private];

		return new CancelablePromise((resolve, reject, onCancel) => {
			let leavePromise = null;
			let enterPromise = null;

			onCancel((cancelParam) => {
				if (leavePromise) {
					leavePromise.cancel();
				}
				if (enterPromise) {
					enterPromise.cancel(cancelParam);
				}
			});

			// Start to change
			_private.flags.enable(Flag.Changing, true);

			// Exit current level
			leavePromise = this._leaveObjectLevel(object, options, path, origin);
			leavePromise.then(() => {
				this._enterObjectLevel(object, options, path, origin).then(() => {
					resolve();
					enterPromise = null;
				});
				leavePromise = null;
			});
		});
	}

	_generateEventParam(path, origin, next, options) {
		let _private = this[__.private];

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

	_startToChange(path, options) {
		let _private = this[__.private];

		// Try to filter path
		let onFilterPath = _private.onFilterPath;
		if (onFilterPath) {
			path = onFilterPath(path) || path;
		}

		let origin = _private.current;

		path.forEachAsync(obj => {
			let promise = this._changeObject(obj, options, path, origin);

			_private.pendingPromises.push(promise);

			return promise;
		});
	}

	_change(object, options) {
		let _private = this[__.private];

		// Get the current level
		let current = _private.current;

		// Skip to change for the same object level
		if (current == object) {
			return;
		}

		if (current && !current.destroyed) {
			// Get path from current/root level to object
			let path = current.getPathTo(object) || app.root.getPathTo(object);

			// Start to change level
			this._startToChange(path, options);
		}
		else {
			// Get the level path from parents
			let path = object.parents.reverse();
			path.push(object);

			// Start to change level
			this._startToChange(path, options);
		}
	}

	_cancelPendingPromises(object, options, path) {
		let _private = this[__.private];

		if (!_private.pendingPromises.length) {
			return;
		}

		if (!_private.current) {
			return;
		}

		let levelPath = path || _private.current.getPathTo(object);

		let levelControls = this.getControlsByObject(_private.current);
		let cancelParam = {
			origin: _private.current,
			prev: _private.prev,
			current: _private.current,
			next: object,
			path: levelPath,
			options,
			levelControls
		}

		_private.pendingPromises.forEach(promise => {
			promise.cancel(cancelParam);
		});

		const changeOptions = _private.changeOptions;
		if (changeOptions) {
			let onStop = Utils.parseValue(changeOptions['onStop'], changeOptions['stop']);
			if (onStop) {
				onStop();
			}
			_private.changeOptions = null;
		}

		// Record options
		_private.changeOptions = options;

		_private.pendingPromises.length = 0;
	}

	_createCancelablePromise(object, options) {
		return new CancelablePromise((resolve, reject, onCancel) => {
			onCancel(() => { });


			let copyOptions = Object.assign({}, options);

			let onComplete = options['onComplete'] || options['complete'];
			let onStop = options['onStop'] || options['stop'];

			copyOptions.onComplete = copyOptions.complete = (ev) => {
				if (onComplete) {
					onComplete(ev);
				}
				resolve('onComplete');
			};

			copyOptions.onStop = copyOptions.stop = (ev) => {
				if (onStop) {
					onStop(ev);
				}
				resolve('onStop');
			}

			this._change(object, copyOptions);
		});
	}

	// #endregion


	dispose() {
		let _private = this[__.private];

		let app = _private.app;

		app.off(EventType.AfterDestroy, '*', cDestroyObjectEventTag);

		this._cancelPendingPromises(null);

		_private.flags.clear();

		_private.prev = null;
		_private.current = null;
		this.removeAllComponents(true);
	}

	/**
	 * 层级改变时调用的函数
	 * @callback LevelChangedCallback
	 * @public
	 * @param {object} ev 回调参数
	 * @param {THING.BaseObject} ev.current 当前层级
	 * @param {THING.BaseObject} ev.prev 上一层级
	 * @param {Array<THING.BaseObject>} ev.path层级切换的路径(都经过了哪些层级)
	 */

	/**
	 * 层级切换参数
	 * @typedef {object} LevelChangeOptions
	 * @public
	 * @property {LevelChangedCallback} onStop 停止切换层级的回调
	 * @property {LevelChangedCallback} onComplete 最终切换完成的回调
	 */

	/**
	 * 切换层级
	 * @param {THING.BaseObject} object 要切换到的层级对象
	 * @param {LevelChangeOptions} options 层级切换的参数
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 *   }
	 * });
	 */
	change(object, options = {}) {
		return this.changeAsync(object, options);
	}

	/**
	 * 异步模式切换当前层级。
	 * @param {THING.BaseObject} object 目标对象。
	 * @param {LevelChangeOptions} options 选项。
	 * @returns {Promise<any>}
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.changeAsync(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 *   }
	 * });
	 */
	changeAsync(object, options = {}) {
		let _private = this[__.private];

		let app = _private.app;

		// Skip for disable state
		if (!_private.enable) {
			return Promise.resolve();
		}

		// Skip to change for the same object level
		let current = _private.current;
		if (current == object) {
			return Promise.resolve();
		}

		// Ignore level
		if (object.tags.has('IgnoreLevel')) {
			return Promise.resolve();
		}

		let pathStart = current;
		if (!current || current.destroyed) {
			pathStart = app.root;
		}

		let path = null;
		if (pathStart.isRootObject && pathStart === object) {
			path = [object]
		}
		else {
			path = pathStart.getPathTo(object);
		}

		for (let i = 0; i < path.length; i++) {
			const element = path[i];
			if (element.tags.has('IgnoreLevel')) {
				return Promise.resolve();
			}
		}

		this._cancelPendingPromises(object, options, path);

		// Start to change level
		const promise = this._createCancelablePromise(object, options);

		_private.pendingPromises.push(promise);

		return promise;
	}

	/**
	 * 回退到上一层级
	 * @param {LevelChangeOptions} options 层级切换参数
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 * 		level.back({
	 *   		onComplete: function(){
	 * 				ret = level.current == target.parent;
	 * 				// @expect(ret == true)
	 *   		}
	 * 		});
	 *   }
	 * });
	 */
	back(options = {}) {
		let _private = this[__.private];

		let current = _private.current;
		if (!current) {
			return Promise.resolve();
		}

		let parent = current.parent;
		if (!parent) {
			return Promise.resolve();
		}

		return this.changeAsync(parent, options);
	}

	/**
	 * 异步模式切换到父级层级。
	 * @param {LevelChangeOptions} options 选项。
	 * @returns {Promise<any>}
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 * 		level.backAsync({
	 *   		onComplete: function(){
	 * 				ret = level.current == target.parent;
	 * 				// @expect(ret == true)
	 *   		}
	 * 		});
	 *   }
	 * });
	 */
	backAsync(options = {}) {
		let _private = this[__.private];

		// Skip for disable state
		if (!_private.enable) {
			return;
		}

		let current = _private.current;
		if (!current) {
			return Promise.resolve();
		}

		let parent = current.parent;
		if (!parent) {
			return Promise.resolve();
		}

		this._cancelPendingPromises(parent, options);

		const promise = this._createCancelablePromise(parent, options);

		_private.pendingPromises.push(promise);

		return promise;
	}

	/**
	 * 退出层级
	 * @returns {Promise<any>}
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * level.quit({
	 *   onComplete: function(){
	 * 		let ret = level.current == null;
	 * 		// @expect(ret == true);
	 *   }
	 * });
	 */
	quit() {
		let _private = this[__.private];


		_private.flags.clear();

		// Exit current level
		if (_private.current) {
			this._cancelPendingPromises(_private.current, null);

			let params = {
				origin: null,
				prev: _private.prev,
				current: _private.current,
				next: null,
			};

			this._notifyEvent(EventType.BeforeLeaveLevel, params);

			let levelControls = this.getControlsByObject(_private.current);

			return _private.current.level.leave({
				path: [_private.current],
				origin: null,
				prev: null,
				current: _private.current,
				next: null,
				levelControls
			}).then(() => {
				if (_private.pendingPromises.length) {
					return;
				}

				this._notifyEvent(EventType.AfterLeaveLevel, params);

				_private.prev = null;
				_private.current = null;
			});
		}
		else {
			return Promise.resolve();
		}
	}

	/**
	 * 开启/禁用 层级管理器
	 * Enable/Disable level manager.
	 * @type {boolean}
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * level.enable = false;
	 * let target = app.query('.Entity')[0];
	 * level.change(target);
	 * let ret = level.isChanging;
	 * // @expect(ret == false);
	 */
	get enable() {
		let _private = this[__.private];

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

		_private.enable = value;
	}

	/**
	 * 开始获取对象层级路径时要调用的函数。
	 * @callback FilterLevelPathCallback
	 * @param {Array<THING.BaseObject>} path 层级路径。
	 * @returns {Array<THING.BaseObject>} 新的层级路径,返回 null 或 undefined 表示使用当前层级路径。
	 */

	/**
	 * 获取/设置过滤路径的回调函数。
	 * @type {FilterLevelPathCallback}
	 * @private
	 */
	get onFilterPath() {
		let _private = this[__.private];

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

		_private.onFilterPath = value;
	}

	/**
	 * 获取上一层级
	 * @type {THING.BaseObject}
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let prev = level.prev;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.prev == prev;
	 * 		// @expect(ret == true);
	 * 	 }
	 * });
	 */
	get prev() {
		let _private = this[__.private];

		return _private.prev;
	}

	/**
	 * 获取当前层级
	 * @type {THING.BaseObject}
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target, {
	 *   onComplete: function(){
	 * 		let ret = level.current == target;
	 * 		// @expect(ret == true);
	 * 	 }
	 * });
	 */
	get current() {
		let _private = this[__.private];

		return _private.current;
	}

	/**
	 * 是否正在切换层级
	 * @type {boolean}
	 * @public
	 * @example
	 * let app = THING.App.current;
	 * let level = app.level;
	 * let target = app.query('.Entity')[0];
	 * level.change(target);
	 * let ret = level.isChanging;
	 * // @expect(ret == true);
	 */
	get isChanging() {
		let _private = this[__.private];

		return _private.flags.has(Flag.Changing);
	}

	get changingOptions() {
		let _private = this[__.private];
		return _private.changingOptions;
	}

}

export { LevelManager }