import { Box3, OBB } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { MathUtils } from '../math/MathUtils';
import { BaseComponent } from './BaseComponent';
import { LightSphereModeType, NodeUserDataType, InheritType, AccuracyMode } from '../const';

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

let _aabb = { center: MathUtils.createVec3(), halfSize: MathUtils.createVec3(), rotation: MathUtils.createQuat() };
let _obb = { center: MathUtils.createVec3(), halfSize: MathUtils.createVec3(), rotation: MathUtils.createQuat() };
let _emptyAABB = new Box3();
let _emptyOBB = new OBB();
let _origin = [0, 0, 0];

// #region Private Functions

// Filter node callback.
function _filterNodeCallback(node) {
	let userData = node.getUserData();
	if (userData[NodeUserDataType.BoundingBoxInheritType] == InheritType.Stop) {
		return false;
	}

	// if the node's parent node type is break
	let parent = node.getParent();
	let parentUserData = parent ? parent.getUserData() : null;
	if (parentUserData && parentUserData[NodeUserDataType.BoundingBoxInheritType] == InheritType.Break) {
		// if node is bodyNode,return true
		// if node is child of that who's type is break,return false
		if (userData[NodeUserDataType.BoundingBoxInheritType] != 'continue') {
			return false;
		}
	}


	if (userData['isDebugNode']) {
		return false;
	}

	if (userData['active'] === false) {
		return false;
	}

	return true;
}

// #endregion

/**
 * @class BoundingComponent
 * 包围盒组件。
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class BoundingComponent extends BaseComponent {

	static mustCopyWithInstance = true;

	/**
	 * 包围盒(AABB, OBB)组件。
	 */
	constructor() {
		super();

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

		_private.inheritType = InheritType.Normal;

		_private.localBoundingBoxResult = null;

		_private.lightSphereModeType = LightSphereModeType.Dynamic;
		_private.lightCenterInSelfSpace = null;
		_private.lightSphere = null;

		_private.accuracyMode = AccuracyMode.Basic;
	}

	// #region Private Functions

	_getNode(recursive) {
		let object = this.object;

		return recursive ? object.node : object.bodyNode;
	}

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

		return _private.lightSphere = _private.lightSphere || {
			center: [0, 0, 0],
			radius: 0,
			shadowRadius: 0
		};
	}

	// #endregion

	getAABB(recursive = true, updateMatrix = true, local = false) {
		let node = this._getNode(recursive);

		const _private = this[__.private];
		let funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateAABB' : 'getAABB';

		// Get bounding box
		if (node[funName]) {
			let box = node[funName](_aabb, _filterNodeCallback, updateMatrix, local);

			if (MathUtils.exactEqualsVector3(box.halfSize, _origin)) {
				return new Box3(this.object.position, [Number.EPSILON, Number.EPSILON, Number.EPSILON]);
			}
			else {
				return new Box3(box.center, MathUtils.fixScaleFactor(box.halfSize));
			}
		}
		else {
			return _emptyAABB;
		}
	}

	getOBB(recursive = true, updateMatrix = true, local = false) {
		let node = this._getNode(recursive);

		const _private = this[__.private];
		let funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateOBB' : 'getOBB';

		// Get oriented bounding box
		if (node[funName]) {
			let box = node[funName](_obb, _filterNodeCallback, updateMatrix, local);

			if (MathUtils.exactEqualsVector3(box.halfSize, _origin)) {
				return new OBB(this.object.position, [Number.EPSILON, Number.EPSILON, Number.EPSILON], box.rotation);
			}
			else {
				return new OBB(box.center, MathUtils.fixScaleFactor(box.halfSize), box.rotation);
			}
		}
		else {
			return _emptyOBB;
		}
	}

	getSelfBoundingBox() {
		const _private = this[__.private];
		const node = this.object.body.getRenderableNode();

		const target = {
			center: [0, 0, 0],
			halfSize: [Number.EPSILON, Number.EPSILON, Number.EPSILON]
		};

		const funName = _private.accuracyMode === AccuracyMode.Accurate ? 'getAccurateAABB' : 'getAABB';

		// Get bounding box
		if (node[funName]) {
			node[funName](target, _filterNodeCallback, true, 0);
		}

		return target;
	}

	/**
	 * Get the initial local bounding box.
	 * @returns {BoundingBoxResult}
	 * @private
	 */
	getInitialLocalBoundingBox() {
		let _private = this[__.private];

		return _private.localBoundingBoxResult;
	}

	/**
	 * Set the local bounding box.
	 * @param {BoundingBoxResult} value The value.
	 * @private
	 */
	setInitialLocalBoundingBox(value) {
		let _private = this[__.private];

		let bodyNode = this.object.bodyNode;

		if (value) {
			_private.localBoundingBoxResult = {
				center: value.center,
				halfSize: value.halfSize
			};

			if (bodyNode.isRenderableNode) {
				bodyNode.setLocalBoundingBox(_private.localBoundingBoxResult);
			}
		}
		else if (_private.localBoundingBoxResult) {
			_private.localBoundingBoxResult = null;

			if (bodyNode.isRenderableNode) {
				bodyNode.clearLocalBoundingBox();
			}
		}
	}

	/**
	 * @typedef {object} LightSphereInfo
	 * @property {Array<number>} center The center of light sphere.
	 * @property {number} radius The radius of light sphere, 0 indicates use the radius of bounding box.
	 * @property {number} shadowRadius The shadow's radius of light sphere, 0 indicates use the radius or radius of bounding box.
	 */

	/**
	 * Get light sphere info.
	 * @param {boolean} [updateMatrix=true] True indicates update matrix for self and all children.
	 * @returns {LightSphereInfo}
	 * @private
	 */
	getLightSphere(updateMatrix = true) {
		let _private = this[__.private];

		let lightSphere = this._getLightSphere();

		switch (_private.lightSphereModeType) {
			case LightSphereModeType.Dynamic:
				let box = this.getAABB(true, updateMatrix);
				lightSphere.center = box.center;
				lightSphere.radius = box.radius;
				break;

			case LightSphereModeType.Static:
				lightSphere.center = this.object.selfToWorld(_private.lightCenterInSelfSpace);
				break;

			default:
				break;
		}

		return lightSphere;
	}

	// #region Accessor

	/**
	 * Get/Set light sphere mode type.
	 * @type {LightSphereModeType}
	 * @private
	 */
	get lightSphereModeType() {
		let _private = this[__.private];

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

		switch (value) {
			case LightSphereModeType.Static:
				let lightSphere = this._getLightSphere();

				let box = this.getAABB(true);
				_private.lightCenterInSelfSpace = this.object.worldToSelf(box.center);
				lightSphere.radius = box.radius;
				break;

			default:
				break;
		}

		_private.lightSphereModeType = value;
	}

	/**
	 * 获取世界坐标系下的包围盒(AABB)。
	 * @returns {Box3}
	 * @example
	 * let aabb = object.bounding.boundingBox;
	 * console.log('center: ', aabb.center);
	 * console.log('halfSize: ', aabb.halfSize);
	 * @public
	 */
	get boundingBox() {
		return this.getAABB();
	}

	/**
	 * 获取世界坐标系下的定向包围盒(OBB)。
	 * @returns {OBB}
	 * @example
	 * let obb = object.bounding.orientedBox;
	 * console.log('center: ', obb.center);
	 * console.log('halfSize: ', obb.halfSize);
	 * console.log('angles: ', obb.angles);
	 * @public
	 */
	get orientedBox() {
		return this.getOBB();
	}

	/**
	 * 获取/设置初始局部包围盒。
	 * @type {BoundingBoxResult}
	 * @example
	 * // 设置初始局部包围盒
	 * object.bounding.initialLocalBoundingBox = {
	 *     center: [0, 0, 0],
	 *     halfSize: [5, 5, 5]
	 * };
	 *
	 * // 清除初始局部包围盒
	 * object.bounding.initialLocalBoundingBox = null;
	 * @public
	 */
	get initialLocalBoundingBox() {
		return this.getInitialLocalBoundingBox();
	}
	set initialLocalBoundingBox(value) {
		this.setInitialLocalBoundingBox(value);
	}

	/**
	 * 获取/设置包围盒继承类型。
	 * @type {InheritType}
	 * @example
	 * // 对象将进行包围盒计算,但是子对象将被忽略
	 * object.bounding.inheritType = InheritType.Break
	 * // 对象将跳过包围盒计算,但是子对象将被正常计算进包围盒
	 * object.bounding.inheritType = InheritType.Jump
	 * // 对象将中断包围盒计算,且子对象也将被忽略
	 * object.bounding.inheritType = InheritType.Stop
	 * @public
	 */
	get inheritType() {
		let _private = this[__.private];

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

		if (value == InheritType.Normal) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
		}
		else if (value == InheritType.Break) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Break;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = 'continue';
		}
		else if (value == InheritType.Jump) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Normal;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
		}
		else if (value == InheritType.Stop) {
			object.node.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
			object.bodyNode.getUserData()[NodeUserDataType.BoundingBoxInheritType] = InheritType.Stop;
		}

		_private.inheritType = value;
	}

	/**
	 * Get/Set the bounding box inherit type.
	 * @type {InheritType}
	 * @deprecated Since 2.7.0
	 * @private
	 */
	get inheritActionType() {
		Utils.warn(`Please use '.inheritType' attribute, '.inheritActionType' has been deprecated`);

		return this.inheritType;
	}
	set inheritActionType(value) {
		Utils.warn(`Please use '.inheritType' attribute, '.inheritActionType' has been deprecated`);

		this.inheritType = value;
	}

	/**
	 * 获取/设置选中包围盒的尺寸(设置为 null 可清除)。
	 * @type {Array<number>}
	 * @example
	 * // 获取当前选中包围盒的尺寸
	 * let size = object.bounding.pickedSize;
	 *
	 * // 设置选中包围盒的尺寸
	 * object.bounding.pickedSize = [10, 20, 30];
	 *
	 * // 清除选中包围盒
	 * object.bounding.pickedSize = null;
	 * @public
	 */
	get pickedSize() {
		let target = this.object.node.getPickBoundingBox();
		if (!target) {
			return null;
		}

		return MathUtils.scaleVector(target.halfSize, 2);
	}
	set pickedSize(value) {
		if (value) {
			let center = this.getAABB().center;

			this.object.node.setPickBoundingBox({ center, halfSize: MathUtils.scaleVector(value, 0.5) });
		}
		else {
			this.object.node.clearPickBoundingBox();
		}
	}

	/**
	 * 获取/设置精度模式。
	 * @example
	 * // 用于在包围盒计算精度和性能之间进行权衡,精确模式提供更准确的包围盒但性能消耗更大(通常建议使用基础模式, 只在需要特别精确的包围盒数据时才临时切换到精确模式)。可以参考以下示例使用该接口:
	 * // 设置精度模式为精确模式
	 * object.bounding.accuracyMode = THING.AccuracyMode.Accurate;
	 * // 获取包围盒
	 * let boundingBox = object.bounding.boundingBox;
	 * // 设置初始局部包围盒
	 * object.bounding.initialLocalBoundingBox = boundingBox;
	 * // 设置精度模式为基础模式
	 * object.bounding.accuracyMode = THING.AccuracyMode.Basic;
	 * @type {AccuracyMode}
	 * @public
	 */
	get accuracyMode() {
		const _private = this[__.private];
		return _private.accuracyMode;
	}
	set accuracyMode(value) {
		const _private = this[__.private];
		_private.accuracyMode = value;
	}

	// #endregion

}

// #endregion
export { BoundingComponent }