import { Plane } from "@uino/base-thing";
import { Object3D } from './Object3D';
import { MathUtils } from '../math/MathUtils';
import { FrustumHelperComponent } from '../components/FrustumHelperComponent';

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

const registerComponentParam = { isResident: true };

/**
 * Frustum - 视锥体
 * 
 * 表示场景中的视锥体对象,可用于计算可见范围。
 * 
 * @class Frustum
 * @memberof THING
 * @extends THING.Object3D
 * @public
 */
class Frustum extends Object3D {

	/**
	 * 场景中的视锥体对象。
	 * @param {object} param 初始参数
	 * @param {number} [param.fov=50] 视场角(度)
	 * @param {number} [param.aspect=1] 宽高比
	 * @param {number} [param.near=0.1] 近平面距离
	 * @param {number} [param.far=1] 远平面距离
	 */
	constructor(param = {}) {
		super(param);

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

		_private.fov = param.fov || 50;
		_private.aspect = param.aspect || 1;
		_private.near = param.near || 0.1;
		_private.far = param.far || 1;

		_private.projectionMatrix = MathUtils.createMat4();

		this.planes = [
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane(),
			new Plane()
		];

		this._updateProjectionMatrix();
	}

	// #region Overrides

	onSetupComponent(param) {
		super.onSetupComponent(param);

		this.addComponent(FrustumHelperComponent, 'helper', registerComponentParam);
	}

	// #endregion

	// #region Private

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

		MathUtils.mat4.perspective(_private.projectionMatrix, _private.fov / 180 * Math.PI, _private.aspect, _private.near, _private.far);

		this._setFromProjectionMatrix(_private.projectionMatrix);
	}

	_setFromProjectionMatrix(m) {
		const planes = this.planes;
		const me = m;
		const me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
		const me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
		const me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
		const me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];

		planes[0].setComponents(me3 - me0, me7 - me4, me11 - me8, me15 - me12).normalize();
		planes[1].setComponents(me3 + me0, me7 + me4, me11 + me8, me15 + me12).normalize();
		planes[2].setComponents(me3 + me1, me7 + me5, me11 + me9, me15 + me13).normalize();
		planes[3].setComponents(me3 - me1, me7 - me5, me11 - me9, me15 - me13).normalize();
		planes[4].setComponents(me3 - me2, me7 - me6, me11 - me10, me15 - me14).normalize();
		planes[5].setComponents(me3 + me2, me7 + me6, me11 + me10, me15 + me14).normalize();

		return this;
	}

	// #endregion

	/**
	 * set projection matrix.
	 * @param {number} fov The fov.
	 * @param {number} aspect The aspect.
	 * @param {number} near The near.
	 * @param {number} far The far.
	 */
	setProjectionMatrix(fov, aspect, near, far) {
		let _private = this[__.private];

		_private.fov = fov;
		_private.aspect = aspect;
		_private.near = near;
		_private.far = far;

		this._updateProjectionMatrix();
	}

	/**
	 * intersects object
	 * @param {object} object 对象。
	 * @returns {boolean} 是否相交。
	 */
	intersectsObject(object) {
		// Get object's bounding sphere in world space
		const sphere = object.getAABB().getBoundingSphere();

		// Convert sphere center to frustum's local space
		const localCenter = this.worldToSelf(sphere.center);
		const localSphere = {
			center: localCenter,
			radius: sphere.radius
		};

		return this.intersectsSphere(localSphere);
	}

	/**
	 * intersects sphere
	 * @param {object} sphere 球体。
	 * @param {Array<number>} sphere.center 球体中心。
	 * @param {number} sphere.radius 球体半径。
	 * @returns {boolean} 是否相交。
	 */
	intersectsSphere(sphere) {
		const planes = this.planes;
		const center = sphere.center;
		const negRadius = -sphere.radius;

		// Convert sphere center to frustum's local space if it's in world space
		const localCenter = this.worldToSelf(center);

		for (let i = 0; i < 6; i++) {
			const distance = planes[i].distanceToPoint(localCenter);

			if (distance < negRadius) {
				return false;
			}
		}

		return true;
	}

	/**
	 * intersects point
	 * @param {Array<number>} point 点的坐标
	 * @returns {boolean} 是否相交。
	 */
	containsPoint(point) {
		const planes = this.planes;

		// Convert point to frustum's local space
		const localPoint = this.worldToSelf(point);

		for (let i = 0; i < 6; i++) {
			if (planes[i].distanceToPoint(localPoint) < 0) {
				return false;
			}
		}

		return true;
	}

	// #region Common

	/**
	 * Get projection matrix.
	 * @returns {Array<number>} 投影矩阵。
	 * @private
	 */
	get projectionMatrix() {
		let _private = this[__.private];

		return _private.projectionMatrix;
	}

	/**
	 * Get/Set fov.
	 * @type {number}
	 * @example
	 * 	object.name = 45;
	 */
	get fov() {
		let _private = this[__.private];

		return _private.fov;
	}

	set fov(value) {
		let _private = this[__.private];

		_private.fov = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set aspect.
	 * @type {number}
	 * @example
	 * 	object.aspect = 1;
	 */
	get aspect() {
		let _private = this[__.private];

		return _private.aspect;
	}

	set aspect(value) {
		let _private = this[__.private];

		_private.aspect = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set near.
	 * @type {number}
	 * @example
	 * 	object.near = 0.1;
	 */
	get near() {
		let _private = this[__.private];

		return _private.near;
	}

	set near(value) {
		let _private = this[__.private];

		_private.near = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Get/Set far.
	 * @type {number}
	 * @example
	 * 	object.far = 10;
	 */
	get far() {
		let _private = this[__.private];

		return _private.far;
	}

	set far(value) {
		let _private = this[__.private];

		_private.far = value;
		this._updateProjectionMatrix();
	}

	/**
	 * Check whether it's frustum object.
	 * @type {boolean}
	 */
	get isFrustum() {
		return true;
	}

	// #endregion

}

export { Frustum }