import { EventDispatcher, MathUtils } from '@uino/base-thing';

import { ObjectColliderBoxHelper } from '../helpers/ObjectColliderBoxHelper';

import { ObjectColliderSphereHelper } from '../helpers/ObjectColliderSphereHelper';

import { BaseComponent } from './BaseComponent';

import { EventType, ColliderType } from '../const';



const __ = {

	private: Symbol('private'),

}



let _vec3_0 = MathUtils.createVec3();

let _vec3_1 = MathUtils.createVec3();

let _vec3_2 = MathUtils.createVec3();

let _vec3_3 = MathUtils.createVec3();

let _vec3_4 = MathUtils.createVec3();

let _vec3_5 = MathUtils.createVec3();

let _vec3_6 = MathUtils.createVec3();

let _vec3_7 = MathUtils.createVec3();



let _vec3Lib = MathUtils.vec3;



/**
 * ColliderComponent - 碰撞器组件
 * 
 * 物体的碰撞器,可以监听碰撞事件以处理碰撞逻辑。
 * 
 * @class ColliderComponent
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 * @example
 * // 假设object是一个渲染对象
    const localBoundingBox = object.body.getLocalBoundingBox()
	object.collider.enable = true
	object.collider.mode = THING.ColliderType.Box
	object.collider.offset = localBoundingBox.center
	object.collider.halfSize = localBoundingBox.halfSize
	object.collider.visible = true
	object.collider.addEventListener(
		THING.EventType.ColliderEnter,
		function (ev) {
			console.log('collider_enter');
		}
	)
	object.collider.addEventListener(
		THING.EventType.ColliderLeave,
		function (ev) {
			console.log('collider_leave');
		}
	)
 */
class ColliderComponent extends BaseComponent {



	static mustCopyWithInstance = true;



	/**

	 * 物体的碰撞器,可以监听碰撞事件以处理碰撞逻辑代码。

	 */

	constructor() {

		super();



		this[__.private] = {};

		let _private = this[__.private];



		_private.enable = true;

		_private.visible = false;



		_private.helper = null;



		_private.eventDispatcher = new EventDispatcher();



		// For fast usage, we define here ...



		// The common info

		this._radius = 0.5;

		this._halfSize = null;

		this._offset = [0, 0, 0];

		this._mode = ColliderType.Box;



		// Get the min and max points

		let min = [-this._radius, -this._radius, -this._radius];

		let max = [this._radius, this._radius, this._radius];



		// The area info

		this._matrixWorld = null;

		this._areaInfo = {

			base: {

				min,

				max,

			},

			local: [

				// TOP

				[min[0], min[1], min[2]], // 0

				[max[0], min[1], min[2]], // 1

				[max[0], min[1], max[2]], // 2

				[min[0], min[1], max[2]], // 3

				// BOTTOM

				[min[0], max[1], min[2]], // 4

				[max[0], max[1], min[2]], // 5

				[max[0], max[1], max[2]], // 6

				[min[0], max[1], max[2]], // 7

			],

			transform: {

				min: min.slice(0),

				max: max.slice(0)

			},

		};

	}



	// #region Private Functions



	_createHelper() {

		let _private = this[__.private];



		switch (this._mode) {

			case ColliderType.Box:

				_private.helper = new ObjectColliderBoxHelper({ object: this.object });

				this.app.scene.rootObjects['debug'].node.add(_private.helper.lines);

				break;



			case ColliderType.Sphere:

				_private.helper = new ObjectColliderSphereHelper({ object: this.object });

				this.app.scene.rootObjects['debug'].node.add(_private.helper.lines);

				break;

		}

	}



	_disposeHelper() {

		let _private = this[__.private];



		if (_private.helper) {

			_private.helper.dispose();

			_private.helper = null;

		}

	}



	_refreshHelper() {

		this._disposeHelper();

		this._createHelper();

	}



	_refreshAreaInfo() {

		let areaInfo = this._areaInfo;



		let min = [], max = [];

		MathUtils.vec3.add(min, areaInfo.base.min, this._offset);

		MathUtils.vec3.add(max, areaInfo.base.max, this._offset);



		areaInfo.local = [

			// TOP

			[min[0], min[1], min[2]], // 0

			[max[0], min[1], min[2]], // 1

			[max[0], min[1], max[2]], // 2

			[min[0], min[1], max[2]], // 3

			// BOTTOM

			[min[0], max[1], min[2]], // 4

			[max[0], max[1], min[2]], // 5

			[max[0], max[1], max[2]], // 6

			[min[0], max[1], max[2]], // 7

		];

	}



	// #endregion



	// #region Overrides



	onBeforeRemove() {

		this.enable = false;

		this.visible = false;

	}



	// #endregion



	// #region Callbacks



	onColliderEnter(collider) {

		let _private = this[__.private];



		_private.eventDispatcher.dispatchEvent({ type: EventType.ColliderEnter.toLowerCase(), object: collider.object });

	}



	onColliderLeave(collider) {

		let _private = this[__.private];



		_private.eventDispatcher.dispatchEvent({ type: EventType.ColliderLeave.toLowerCase(), object: collider.object });

	}



	// #endregion



	// #region Events



	/**

	 * 添加事件监听

	 * @param {string} type 事件类型

	 * @param {Function} listener 事件回调函数

	 * @public

	 */

	addEventListener(type, listener) {

		let _private = this[__.private];



		_private.eventDispatcher.addEventListener(type.toLowerCase(), listener);

	}



	/**

	 * 移除事件监听

	 * @param {string} type 事件类型

	 * @param {Function} listener 事件回调函数

	 * @public

	 */

	removeEventListener(type, listener) {

		let _private = this[__.private];



		_private.eventDispatcher.removeEventListener(type.toLowerCase(), listener);

	}



	// #endregion



	test(collider) {

		switch (this._mode) {

			case ColliderType.Box:

				if (collider.mode == ColliderType.Box) {

					let transform1 = this._areaInfo.transform;

					let transform2 = collider._areaInfo.transform;



					return MathUtils.intersectsBox(transform1.min, transform1.max, transform2.min, transform2.max);

				} else {

					return this._intersectBoxAndSphere(this, collider);

				}

			case ColliderType.Sphere:

				if (collider.mode == ColliderType.Sphere) {

					let matrixWorld1 = this._matrixWorld;

					let matrixWorld2 = collider._matrixWorld;



					let dx = matrixWorld1[12] - matrixWorld2[12];

					let dy = matrixWorld1[13] - matrixWorld2[13];

					let dz = matrixWorld1[14] - matrixWorld2[14];

					let distance = Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.5;



					if (distance < this._radius || distance < collider._radius) {

						return true;

					}

				} else {

					return this._intersectBoxAndSphere(collider, this);

				}



				break;



			default:

				break;

		}



		return false;

	}



	_intersectBoxAndSphere(boxCollider, sphereCollider) {

		let min = boxCollider._areaInfo.transform.min, max = boxCollider._areaInfo.transform.max;



		let matrixWorld = sphereCollider._matrixWorld;

		let center = [matrixWorld[12], matrixWorld[13], matrixWorld[14]];

		let radius = sphereCollider.radius;



		return MathUtils.IsCollisionBoxAndSphere(min, max, center, radius);

	}



	setMatrixWorld(value) {

		this._matrixWorld = value;



		let areaInfo = this._areaInfo;

		let min = areaInfo.transform.min;

		let max = areaInfo.transform.max;



		min[0] = min[1] = min[2] = +Infinity;

		max[0] = max[1] = max[2] = -Infinity;



		let local = this._areaInfo.local;



		_vec3Lib.transformMat4(_vec3_0, local[0], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_0);

		_vec3Lib.max(max, max, _vec3_0);



		_vec3Lib.transformMat4(_vec3_1, local[1], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_1);

		_vec3Lib.max(max, max, _vec3_1);



		_vec3Lib.transformMat4(_vec3_2, local[2], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_2);

		_vec3Lib.max(max, max, _vec3_2);



		_vec3Lib.transformMat4(_vec3_3, local[3], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_3);

		_vec3Lib.max(max, max, _vec3_3);



		_vec3Lib.transformMat4(_vec3_4, local[4], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_4);

		_vec3Lib.max(max, max, _vec3_4);



		_vec3Lib.transformMat4(_vec3_5, local[5], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_5);

		_vec3Lib.max(max, max, _vec3_5);



		_vec3Lib.transformMat4(_vec3_6, local[6], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_6);

		_vec3Lib.max(max, max, _vec3_6);



		_vec3Lib.transformMat4(_vec3_7, local[7], this._matrixWorld);

		_vec3Lib.min(min, min, _vec3_7);

		_vec3Lib.max(max, max, _vec3_7);

	}



	// #region Accessors



	/**

	 * 开启/关闭

	 * @type {boolean}

	 * @public

	 */

	get enable() {

		let _private = this[__.private];



		return _private.enable;

	}

	set enable(value) {

		let _private = this[__.private];



		_private.enable = value;



		if (value) {

			this.object.app.colliderManager.add(this);

		}

		else {

			this.object.app.colliderManager.remove(this);

		}

	}



	/**

	 * 获取/设置碰撞器的模式。

	 * @type {ColliderType}

	 * @public

	 */

	get mode() {

		return this._mode;

	}

	set mode(value) {

		let _private = this[__.private];



		if (this._mode == value) {

			return;

		}



		this._mode = value;



		// If helper is showing then refresh it

		if (_private.visible) {

			this._refreshHelper();

		}

	}



	/**

	 * 获取/设置半径。

	 * @type {number}

	 * @public

	 */

	get radius() {

		return this._radius;

	}

	set radius(value) {

		this._radius = value;

		this.halfSize = [value, value, value];

	}



	/**

	 * 获取/设置偏移量

	 * @type {Array<number>}

	 * @public

	 */

	get offset() {

		return this._offset;

	}

	set offset(value) {

		if (value) {

			this._offset = value.slice(0);

		}

		else {

			this._offset = [0, 0, 0];

		}



		this._refreshAreaInfo();

	}



	/**

	 * 获取/设置半尺寸。

	 * @type {Array<number>}

	 * @public

	 */

	get halfSize() {

		return this._halfSize;

	}

	set halfSize(value) {

		this._halfSize = value.slice(0);



		let base = this._areaInfo.base;

		base.min[0] = -value[0];

		base.min[1] = -value[1];

		base.min[2] = -value[2];

		base.max[0] = value[0];

		base.max[1] = value[1];

		base.max[2] = value[2];



		this._refreshAreaInfo();

	}



	/**

	 * 获取/设置显隐

	 * @type {boolean}

	 * @public

	 */

	get visible() {

		let _private = this[__.private];



		return _private.visible;

	}

	set visible(value) {

		let _private = this[__.private];



		if (_private.visible == value) {

			return;

		}



		_private.visible = value;



		if (value) {

			this._createHelper();

		}

		else {

			this._disposeHelper();

		}

	}



	/**

	 * Get the area info.

	 * @type {object}

	 * @private

	 */

	get areaInfo() {

		return this._areaInfo;

	}



	// #endregion



}



export { ColliderComponent }