import { MathUtils } from '../math/MathUtils';
import { BaseTickableObject3D } from './BaseTickableObject3D';
import { ObjectAttributes } from '@uino/base-thing';

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

let _vec3_1 = MathUtils.createVec3();
let _vec3_2 = MathUtils.createVec3();
let _mat3 = MathUtils.createMat3();
let _mat4 = MathUtils.createMat4();

// #region Private Functions

// Convert clipping plane to world space.
function _convertPlaneToWorldSpace(plane, matrixWorld) {
	let dir_1 = MathUtils.vec3.copy(_vec3_1, plane.direction);
	let dir_2 = MathUtils.vec3.copy(_vec3_2, plane.direction);

	const normalMatrix = MathUtils.mat3.normalFromMat4(_mat3, matrixWorld);
	const referencePoint = MathUtils.vec3.transformMat4(dir_1, MathUtils.vec3.scale(dir_1, dir_1, -plane.height), matrixWorld);
	const normal = MathUtils.vec3.normalize(dir_2, MathUtils.vec3.transformMat3(dir_2, dir_2, normalMatrix));
	const constant = -MathUtils.vec3.dot(referencePoint, normal);

	return {
		direction: normal.slice(0),
		height: constant
	}
}

// #endregion

/**
 * ClippingPlanes - 裁剪平面
 * 
 * 提供创建裁剪面的能力,支持六个方向(上、下、左、右、前、后)的平面裁剪。
 * 裁剪面需要在对象的 style 下使用。
 * 
 * @class ClippingPlanes
 * @memberof THING
 * @extends THING.BaseTickableObject3D
 * @public
 * 
 * @example
 * const planes = new THING.ClippingPlanes({
 *   planes: [
 *     { direction: [0, -1, 0], height: 6 },
 *     { direction: [0, 1, 0], height: 2 },
 *   ],
 * });
 * obj.style.clippingPlanes = planes;
 */
class ClippingPlanes extends BaseTickableObject3D {

	/**
	 *
	 * 提供创建裁剪面的能力 裁剪面需要在对象的 style 下使用。
	 * @param {object} param 初始化参数列表
	 * @param {ClippingPlaneResult[]} param.planes 上,下,左,右,前,后的平面法线方向以及距离 每个裁剪面的格式为{ direction: [x, y, z], height: h }
	 * @example
		  let clippingPlanes = [
			{ direction: [0, -1, 0], height: 6 }, // top
			{ direction: [0, 1, 0], height: 2 }, // bottom
			{ direction: [-1, 0, 0], height: 10 }, // right
			{ direction: [1, 0, 0], height: 10 }, // left
			{ direction: [0, 0, -1], height: 10 }, // front
			{ direction: [0, 0, 1], height: 10 } // back
			];
		let clippingPlanesObject = new THING.ClippingPlanes({
			parent: parentObj,
			planes: clippingPlanes,
		});
		parentObj.style.clippingPlanes = clippingPlanesObject;
	 * @constructor
	 * @public
	*/
	constructor(param) {
		super(param);

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

		_private.lastMat4 = null;
		_private.resources = [];
		_private.worldPlanes = null;

		// Create ObjectAttributes to monitor planes array changes
		_private.planesData = new ObjectAttributes({
			data: { planes: null },
			onChange: (ev) => {
				this._updatePlanes(true);
			},
			onNeedProxy: (prop, value) => {
				return true;
			},
			onCloneObject: (data) => {
				return {
					planes: data.planes ? data.planes.map(plane => ({
						direction: plane.direction.slice(0),
						height: plane.height
					})) : null
				};
			}
		});

		let planes = param['planes'];
		if (planes) {
			this.planes = planes;
		}
	}

	// #region Private

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

		// Get the object's matrix world
		this.node.getMatrixWorld(_mat4);

		// Check whether matrix world has been changed
		if (!force) {
			if (_private.lastMat4) {
				if (MathUtils.mat4.equals(_mat4, _private.lastMat4)) {
					return;
				}
			}
			else {
				_private.lastMat4 = MathUtils.createMat4();
			}
		}

		// Keep matrix world to latest
		if (_private.lastMat4) {
			MathUtils.mat4.copy(_private.lastMat4, _mat4);
		}

		// Convert local planes to world planes
		_private.worldPlanes = _private.planesData.getValue('planes').map(plane => {
			return _convertPlaneToWorldSpace(plane, _mat4);
		});

		// Update styles's resource clipping planes
		_private.resources.forEach(resource => {
			resource.setClippingPlanes(_private.worldPlanes);
		});
	}

	// #endregion

	// #region BaseObject Interface

	onUpdate(deltaTime) {
		super.onUpdate(deltaTime);

		let _private = this[__.private];
		let planes = _private.planesData.getValue('planes');

		if (!planes) {
			return;
		}

		if (_private.resources.length) {
			this._updatePlanes();
		}
	}

	// #endregion

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

		if (_private.resources.indexOf(resource) !== -1) {
			return;
		}

		_private.resources.push(resource);

		resource.setClippingPlanes(_private.worldPlanes);
	}

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

		let index = _private.resources.indexOf(resource);
		if (index === -1) {
			return;
		}

		_private.resources.splice(index, 1);

		resource.setClippingPlanes(null);
	}

	// #endregion

	// #region Accessor

	/**
	 * @typedef {object} ClippingPlaneResult
	 * @property {Array<number>} direction 世界坐标系下的方向
	 * @property {number} height 高度,单位:米
	 * @public
	 */

	/**
	 * 获取/设置裁剪平面。
	 * @type {Array<ClippingPlaneResult>}
	 * @public
	 */
	get planes() {
		let _private = this[__.private];
		return _private.planesData.getValue('planes');
	}
	set planes(value) {
		let _private = this[__.private];
		_private.planesData.setValue('planes', value);
	}

	// #endregion

	/**
	 * 检查是否为ClippingPlanes类型或继承自它。
	 * @type {boolean}
	 * @public
	 */
	get isClippingPlanes() {
		return true;
	}

}

export { ClippingPlanes }