import { Object3D } from './Object3D';
import { ModelAnimationComponent } from '../components/ModelAnimationComponent';

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

/**
 * 动画组件默认名称 不要修改!
 */
const _internalAnimationComponentName = '_internalAnimation';

const registerComponentParam = { autoRegister: false, isResident: true };

/**
 * @class BaseEntity
 * 在场景中加载3d模型资源的物体,支持动画播放
 * @memberof THING
 * @extends THING.Object3D
 * @public
 */
class BaseEntity extends Object3D {

	/**
	 * 基础实体类,负责在场景中加载3D模型资源并支持动画播放
	 * </br>场景中加载三维模型资源的基础实体对象。
	 * @param {object} param 初始化参数 这里支持Object3D初始化的所有参数,此外可接受url作为资源路径
	 * @param {string} param.url 资源路径
	 */
	constructor(param) {
		super(param);

		this._animation = this._animation || null;
	}

	// #region Private

	_playDelayAnimation() {
		// Make sure object is alive
		if (this.destroyed) {
			return;
		}

		// Get the renderable node
		let node = this.bodyNode;

		// If entity has animation then complete the delayed commands of it
		if (node.hasAnimation()) {
			// Refresh the animation component
			if (this._animation) {
				this._animation.refreshAnimations();
			}
			this.animation.runDelayedCommands();
		}
		// If entity do not have any animations, then use the dummy animation component
		else {
			if (this._animation) {
				this.removeComponent('animation');
			}

			this._animation = ModelAnimationComponent.cDummyAnimationInterface;
		}
	}

	// #endregion

	// #region Object3D overrides

	onAfterSetup(param) {
		if (param.extras && param.extras.animInfo) {
			const animInfo = param.extras.animInfo;

			this.playAnimation(animInfo);
		}
	}

	onLoadComplete(options) {
		super.onLoadComplete(options);

		this._playDelayAnimation();
	}

	unloadResource(recursive = true) {
		this.clearInternalAnimation();

		return super.unloadResource(recursive);
	}

	clearInternalAnimation() {
		if (this._animation) {
			this._animation.stopAllAnimations();
			this._animation = null;

			this.removeComponent(_internalAnimationComponentName);
		}
	}

	// #endregion

	// #region Internal components attributes and functions

	// #region Component - animation

	get animation() {
		if (!this._animation) {
			this._animation = new ModelAnimationComponent();
			this.addComponent(this._animation, _internalAnimationComponentName, registerComponentParam);
		}

		return this._animation;
	}

	/**
	 * 获取资源自带的所有动画信息
	 * @type {Array<AnimationResult>}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let animations = UinoSpaceman.animations;
	 *   let ret = animations[0].name == 'Walk';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	get animations() { return this.animation.animations; }

	/**
	 * 获取资源自带的所有动画名称
	 * @type {Array<string>}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let animations = UinoSpaceman.animationNames;
	 *   let ret = animations[0] == 'Walk';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	get animationNames() { return this.animation.animationNames; }

	/**
	 * @typedef {object} PlayAnimationArgs 动画播放的参数
	 * @property {string} name 动画名称
	 * @property {number} times? 循环次数
	 * @property {LoopType} loopType? 循环类型
	 * @property {number} [speed=1] 播放速度
	 * @property {boolean} [reverse=false] 是否反向播放
	 * @property {Function} [onComplete=null] 播放完成回调函数
	 * @public
	 */

	/**
	 * 播放动画,会停止所有其他正在播放的动画
	 * @param {PlayAnimationArgs} param 参数
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret = UinoSpaceman.isAnimationPlaying('Walk');
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	playAnimation() { this.animation.playAnimation.apply(this.animation, arguments); }

	/**
	 * 异步模式下播放动画,会停止所有其他正在播放的动画。
	 * @param {PlayAnimationArgs} param 动画播放参数
	 * @returns {Promise<any>} 返回一个Promise,用于异步处理动画播放完成后的回调
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimationAsync({ name: 'Walk', loopType: "Repeat" });
	 *   let ret = UinoSpaceman.isAnimationPlaying('Walk');
	 *   // @expect(ret == true);
	 * });
	 * @private
	 */
	playAnimationAsync() { return this.animation.playAnimationAsync.apply(this.animation, arguments); }

	/**
	 * 播放动画,播放时不会影响该物体正在播放的其他动画
	 * @param {PlayAnimationArgs} param 动画播放参数
	 * @returns {void} 无返回值
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.blendAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret = UinoSpaceman.isAnimationPlaying('Walk');
	 *   // @expect(ret == true);
	 * });
	 * @public
	 */
	blendAnimation() { return this.animation.blendAnimation.apply(this.animation, arguments); }

	/**
	 * 异步模式下混合动画,不会停止所有其他正在播放的动画。
	 * @param {PlayAnimationArgs} param 动画播放参数
	 * @returns {Promise<any>} 返回一个Promise,用于异步处理动画播放完成后的回调
	 * @private
	 */
	blendAnimationAsync() { return this.animation.blendAnimationAsync.apply(this.animation, arguments); }

	/**
	 * 根据动画名称检查该对象身上是否有这个动画
	 * @param {string} name 动画名称
	 * @returns {boolean} 返回一个布尔值,表示是否有指定名称的动画
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let ret = UinoSpaceman.hasAnimation('Walk');
	 *   // @expect(ret == true);
	 * });
	 * @public
	 */
	hasAnimation() { return this.animation.hasAnimation.apply(this.animation, arguments); }

	/**
	 * 该名称的动画是否正在播放
	 * @returns {boolean}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret = UinoSpaceman.isAnimationPlaying('Walk');
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	isAnimationPlaying() { return this.animation.isAnimationPlaying.apply(this.animation, arguments); }

	/**
	 * 暂停动画(暂停名称匹配的所有动画)
	 * @param {string|string[]} name 动画名称或动画名称的数组
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.pauseAnimation('Walk');
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Paused';
	 * 	 // @expect(ret1 == true && ret2 == true);
	 * });
	 * @public
	 */
	pauseAnimation() { this.animation.pauseAnimation.apply(this.animation, arguments); }

	/**
	 * 暂停所有动画
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.pauseAllAnimations();
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Paused';
	 * 	 // @expect(ret1 == true && ret2 == true);
	 * });
	 * @public
	 */
	pauseAllAnimations() { this.animation.pauseAllAnimations.apply(this.animation, arguments); }

	/**
	 * 恢复动画(恢复名称匹配的所有动画)
	 * @param {string|Array<string>} name 动画名称或动画名称的数组
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.pauseAnimation('Walk');
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Paused';
	 * 	 UinoSpaceman.resumeAnimation('Walk');
	 *   let ret3 = UinoSpaceman.isAnimationPlaying('Walk');
	 * 	 // @expect(ret1 == true && ret2 == true && ret3 == true);
	 * });
	 * @public
	 */
	resumeAnimation() { this.animation.resumeAnimation.apply(this.animation, arguments); }

	/**
	 * 恢复所有动画
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.pauseAnimation('Walk');
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Paused';
	 * 	 UinoSpaceman.resumeAllAnimations();
	 *   let ret3 = UinoSpaceman.isAnimationPlaying('Walk');
	 * 	 // @expect(ret1 == true && ret2 == true && ret3 == true);
	 * });
	 * @public
	 */
	resumeAllAnimations() { this.animation.resumeAllAnimations.apply(this.animation, arguments); }

	/**
	 * 停止动画
	 * @param {string} name 动画名称
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.stopAnimation('Walk');
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Stopped';
	 * 	 // @expect(ret1 == true && ret2 == true);
	 * });
	 * @public
	 */
	stopAnimation() { this.animation.stopAnimation.apply(this.animation, arguments); }

	/**
	 * 停止所有动画
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
	 *   UinoSpaceman.stopAllAnimations();
	 *   let ret2 = UinoSpaceman.getAnimationState() == 'Stopped';
	 * 	 // @expect(ret1 == true && ret2 == true);
	 * });
	 * @public
	 */
	stopAllAnimations() { this.animation.stopAllAnimations.apply(this.animation, arguments); }

	/**
	 * @typedef {object} AnimationResult 动画信息
	 * @property {string} name 动画名称
	 * @property {number} duration 时长(秒)
	 * @property {number} speed 播放速度
	 * @property {PlayState} state 播放状态
	 * @public
	 */

	/**
	 * 根据名称获取动画信息
	 * @param {string} name 动画名称
	 * @returns {AnimationResult} 动画信息
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let animation = UinoSpaceman.getAnimation('Walk');
	 *   let ret = animation.name == 'Walk';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getAnimation() { return this.animation.getAnimation.apply(this.animation, arguments); }

	/**
	 * 根据名称获取动画播放状态
	 * @param {string} name 动画名称
	 * @returns {PlayState}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let state = UinoSpaceman.getAnimationState('Walk');
	 *   let ret = state == 'Playing';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getAnimationState() { return this.animation.getAnimationState.apply(this.animation, arguments); }

	/**
	 * 获取所有正在播放的动画
	 * @returns {Array<AnimationResult>}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
	 *   let animations = UinoSpaceman.getPlayingAnimations();
	 *   let ret = animations[0].state == 'Playing';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getPlayingAnimations() { return this.animation.getPlayingAnimations.apply(this.animation, arguments); }

	/**
	 * 获取动画播放的方向(正向/反向)
	 * </br>获取动画的方向类型。
	 * @param {string} name 动画名称
	 * @returns {AnimationDirectionType}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let direction = UinoSpaceman.getAnimationDirectionType('Walk');
	 *   let ret = direction == 'Normal';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getAnimationDirectionType() { return this.animation.getAnimationDirectionType.apply(this.animation, arguments); }

	/**
	 * 设置动画的方向类型。
	 * @param {string} name 动画名称
	 * @param {AnimationDirectionType} value 方向类型(THING.AnimationDirectionType.Normal/THING.AnimationDirectionType.Reverse)
	 * @returns {boolean} 返回一个布尔值,表示是否设置成功
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.setAnimationDirectionType('Walk', 'Reverse');
	 *   let direction = UinoSpaceman.getAnimationDirectionType('Walk');
	 *   let ret = direction == 'Reverse';
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	setAnimationDirectionType() { return this.animation.setAnimationDirectionType.apply(this.animation, arguments); }

	/**
	 * 获取动画速度
	 * @param {string} name 动画名称
	 * @returns {number} 播放速度
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat", speed: 10 });
	 *   let speed = UinoSpaceman.getAnimationSpeed('Walk');
	 *   let ret = speed == 10;
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getAnimationSpeed() { return this.animation.getAnimationSpeed.apply(this.animation, arguments); }

	/**
	 * 设置动画速度
	 * @param {string} name 动画名称
	 * @param {number} value 速度
	 * @returns {boolean}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat", speed: 10 });
	 * 	 UinoSpaceman.setAnimationSpeed('Walk', 20)
	 *   let speed = UinoSpaceman.getAnimationSpeed('Walk');
	 *   let ret = speed == 20;
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	setAnimationSpeed() { return this.animation.setAnimationSpeed.apply(this.animation, arguments); }

	/**
	 * 获取动画权重
	 * @param {string} name 动画名称
	 * @returns {number} 播放权重
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat", speed: 10 });
	 *   let weight = UinoSpaceman.getAnimationWeight('Walk');
	 *   let ret = weight == 0;
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	getAnimationWeight() { return this.animation.getAnimationWeight.apply(this.animation, arguments); }

	/**
	 * 设置动画速度
	 * @param {string} name 动画名称
	 * @param {number} value 权重
	 * @returns {boolean}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat", speed: 10 });
	 * 	 UinoSpaceman.setAnimationWeight('Walk', 0.5)
	 *   let weight = UinoSpaceman.getAnimationWeight('Walk');
	 *   let ret = weight == 0.5;
	 * 	 // @expect(ret == true);
	 * });
	 * @public
	 */
	setAnimationWeight() { return this.animation.setAnimationWeight.apply(this.animation, arguments); }

	// #endregion

	/**
	 * Promote node as child object.
	 * @param {string} name The node name.
	 * @param {THING.Object3D} parent The parent object, if it's null then indicates use current object as parent.
	 * @param {Function} [cls] The class type of the promoted object.
	 * @returns {THING.Object3D} The promoted object.
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * UinoSpaceman.waitForComplete().then(() => {
	 *   let obj = UinoSpaceman.promoteNode('pasted__head');
	 * 	 let ret1 = obj instanceof THING.BaseEntity;
	 *   let ret2 = obj.name == 'pasted__head';
	 * 	 // @expect(ret1 == true && ret2 == true);
	 * });
	 */
	promoteNode(name, parent, cls = BaseEntity) {
		return super.promoteNode(name, parent, cls);
	}

	/**
	 * Check whether it's BaseEntity type or inherit from it.
	 * @type {boolean}
	 * @example
	 * var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
	 * let ret = UinoSpaceman.isBaseEntity;
	 * // @expect(ret == true);
	 */
	get isBaseEntity() {
		return true;
	}

}

export { BaseEntity }