import { BaseComponent, LoopType, Math, MathUtils, Utils } from '@uino/thing';

const cRotateAroundActionName = '__rotateAround__';

/**
 * @class RotateAroundComponent
 * @summary 环绕旋转组件,为对象提供围绕指定目标(物体或世界坐标点)旋转的能力。
 * 基于球坐标系插值算法,支持绕Y轴(俯仰角)和绕X轴(方位角)两个维度的平滑旋转,
 * 可配置旋转时间、循环次数、插值类型以及各种生命周期回调。
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 * @example
 * // 让 obj1 绕着 obj2 旋转一周
 * obj1.addComponent(THING.EXTEND.RotateAroundComponent, 'rotateCtrl');
 * obj1.rotateCtrl.start({
 *   object: obj2,
 *   yRotateAngle: 360,
 *   time: 5000,
 *   loopType: THING.LoopType.Repeat,
 *   onUpdate: function(ev) {
 *     console.log('旋转进度:', ev.progress);
 *   }
 * });
 * 
 * // 停止旋转
 * obj1.rotateCtrl.stop();
 */
class RotateAroundComponent extends BaseComponent {

	/**
	 * 环绕旋转组件类构造函数,用于实现对象环绕目标旋转的功能。
	 * @param {object} param 参数列表
	 */
	constructor(param = {}) {
		super(param);
	}

	/**
	 * On add.
	 * @param {THING.BaseObject} object The object to add the component to.
	 */
	onAdd(object) {
		super.onAdd(object);
	}

	/**
	 * On remove.
	 */
	onRemove() {
		super.onRemove();
	}

	/**
	 * @typedef {object} RotateAroundLerpArgs
	 * @property {THING.BaseObject} object 环绕的目标对象。
	 * @property {Array<number>} target 世界坐标系下某个点的坐标(可从 object 中选择一个)。
	 * @property {number} yRotateAngle 绕Y轴旋转的角度(仰视平面,即垂直平面内的角度,范围0~180度)。
	 * @property {number} [xRotateAngle] 绕X轴旋转的角度(方位平面,即水平平面内的角度,范围0~360度)(此参数不能在地球上使用)。
	 * @property {number} [times=-1] 循环次数,-1表示无限循环。
	 * @property {number} [time=1000] 旋转所需时间(毫秒)。
	 * @property {number} [delayTime=0] 延迟时间(毫秒)。
	 * @property {THING.LerpType} [lerpType] 插值类型。
	 * @property {THING.LoopType} [loopType] 循环类型。
	 * @property {Function} onRepeat 重复动作回调函数。
	 * @property {Function} onStop 停止动作回调函数。
	 * @property {Function} onUpdate 更新动作回调函数。
	 * @property {Function} onComplete 完成动作回调函数。
	 * @public
	 */

	/**
	 * 开始让物体绕着某一个点或者物体旋转的操作。
	 * @param {RotateAroundLerpArgs} param 旋转的参数对象。
	 * @example
	 * // 让 obj1 绕着场景中的 obj2 旋转
	 * obj1.addComponent(THING.RotateAroundComponent, 'rotateController');
	 * obj1.rotateController.start({
	 *   object: obj2,           // 被绕的目标对象
	 *   yRotateAngle: 90,       // 绕Y轴旋转90度
	 *   time: 2000,             // 旋转时长2秒
	 *   onComplete: function() {
	 *     console.log('绕物体旋转完成');
	 *   }
	 * });
	 *
	 * // 或者让 obj1 绕某个世界坐标点旋转
	 * obj1.rotateController.start({
	 *   target: [10, 0, 20],    // 绕这个点旋转
	 *   yRotateAngle: 180,      // 旋转180度
	 *   time: 3000,
	 *   onComplete: function() {
	 *     console.log('绕点旋转结束');
	 *   }
	 * });
	 * @public
	 */
	start(param) {
		// Take out parameter configuration.
		var targetObject = param['object'];
		var target = param['target'];
		var yRotateAngle = Utils.parseValue(param['yRotateAngle'] || param['angle'], 0);
		var xRotateAngle = Utils.parseValue(param['xRotateAngle'], 0);

		var time = param['time'];
		var times = param['times'];
		var delayTime = param['delayTime'];

		var loopType = Utils.parseLoopType(param['loopType'] || 'once');
		var lerpType = Utils.parseLerpType(param['lerpType']);

		var onRepeat = Utils.parseValue(param['onRepeat'], param['repeat']);
		var onUpdate = Utils.parseValue(param['onUpdate'], param['update']);
		var onStop = Utils.parseValue(param['onStop'], param['stop']);
		var onComplete = Utils.parseValue(param['onComplete'], param['complete']);

		// Acceptable target as object input
		if (target) {
			if (target.isBaseObject) {
				targetObject = target;
				target = null;
			}
		}

		// Check whether time is provided
		if (time) {
			if (!yRotateAngle && !xRotateAngle) {
				Utils.error('Please provide yRotateAngle or xRotateAngle to rotate around object');
				return;
			}
		}
		else {
			if (!yRotateAngle && !xRotateAngle) {
				time = 0xffffff;
			}
			else {
				time = 1000;
			}
		}

		// Stop rotating.
		this.stop();

		// The reverse mode
		let reverseMode = false;

		// Build lerp params
		var _repeat = () => {
			if (loopType == LoopType.PingPong) {
				reverseMode = !reverseMode;
			}

			if (onRepeat) {
				onRepeat({ object: this.object, from: 0, to: 1 });
			}
		};

		// Calculate relevant data of target points.
		var position = this.object.position;
		var targetPosition = target ? target : targetObject.position;
		var direction = MathUtils.subVector(position, targetPosition);
		var spherical = MathUtils.makeSphericalFromCartesianCoords(direction);

		// Prepare rotation parameters.
		var offset = [0, 0, 0];
		var baseInfo = {
			theta: spherical.theta,
			phi: spherical.phi
		}

		var _update = (ev) => {
			if (reverseMode) {
				ev.progress = 1 - ev.progress;
			}

			if (yRotateAngle) {
				spherical.theta = baseInfo.theta + ev.progress * Math.degToRad(yRotateAngle);
			}
			if (xRotateAngle) {
				spherical.phi = baseInfo.phi + ev.progress * Math.degToRad(xRotateAngle);
			}

			var targetPosition = target ? target : targetObject.position;
			spherical.radius = this.object.distanceTo(targetPosition);

			MathUtils.makeSphericalSafe(spherical);

			offset = MathUtils.getFromSphericalCoords(spherical);
			targetPosition = MathUtils.addVector(targetPosition, offset);

			this.object.position = targetPosition;

			if (onUpdate) {
				onUpdate({ object: this.object, progress: ev.progress });
			}
		}

		// Start to rotate.
		this.object.lerp.to({
			from: { progress: 0 },
			to: { progress: 1 },

			time,
			times,
			delayTime,

			loopType: loopType,
			lerpType: lerpType,

			onRepeat: _repeat,

			onUpdate: _update,
			onStop,
			onComplete

		}, cRotateAroundActionName);
	}

	/**
	 * 停止旋转。
	 * @public
	 */
	stop() {
		this.object.lerp.stopRotating()
		this.object.lerp.stopMoving()

		this.object.lerp.stop(cRotateAroundActionName)
	}

}

export { RotateAroundComponent }