import { Box, Matrix3D, PlaneClassification, Plane3D, Vector3D } from '@awayjs/core';

import { BoundingVolumeBase } from './BoundingVolumeBase';
import { BoundingVolumePool } from './BoundingVolumePool';
import { ContainerNode } from '../partition/ContainerNode';

/**
 * BoundingBox represents a bounding box volume that has its planes aligned to the local coordinate axes of the bounded object.
 * This is useful for most sprites.
 */
export class BoundingBox extends BoundingVolumeBase {
	private _box: Box;
	private _centerX: number = 0;
	private _centerY: number = 0;
	private _centerZ: number = 0;
	private _halfExtentsX: number = 0;
	private _halfExtentsY: number = 0;
	private _halfExtentsZ: number = 0;

	/**
	 * @inheritDoc
	 */
	public nullify(): void {
		this._centerX = this._centerY = this._centerZ = 0;
		this._halfExtentsX = this._halfExtentsY = this._halfExtentsZ = 0;
	}

	/**
	 * @inheritDoc
	 */
	public isInFrustum(planes: Array<Plane3D>, numPlanes: number): boolean {
		if (this._invalid)
			this._update();

		if (this._box == null)
			return;

		for (let i: number = 0; i < numPlanes; ++i) {

			const plane: Plane3D = planes[i];
			const a: number = plane.a;
			const b: number = plane.b;
			const c: number = plane.c;
			const flippedExtentX: number = a < 0 ? -this._halfExtentsX : this._halfExtentsX;
			const flippedExtentY: number = b < 0 ? -this._halfExtentsY : this._halfExtentsY;
			const flippedExtentZ: number = c < 0 ? -this._halfExtentsZ : this._halfExtentsZ;
			const projDist: number = a * (this._centerX + flippedExtentX) + b * (this._centerY + flippedExtentY) + c * (this._centerZ + flippedExtentZ) - plane.d;

			if (projDist < 0)
				return false;
		}

		return true;
	}

	public rayIntersection(position: Vector3D, direction: Vector3D, targetNormal: Vector3D): number {
		if (this._invalid)
			this._update();

		if (this._box == null)
			return -1;

		return this._box.rayIntersection(position, direction, targetNormal);
	}

	public getBox(): Box {
		if (this._invalid)
			this._update();

		return this._box;
	}

	public classifyToPlane(plane: Plane3D): number {
		let a: number = plane.a;
		let b: number = plane.b;
		let c: number = plane.c;
		const centerDistance: number = a * this._centerX + b * this._centerY + c * this._centerZ - plane.d;

		if (a < 0)
			a = -a;

		if (b < 0)
			b = -b;

		if (c < 0)
			c = -c;

		const boundOffset: number = a * this._halfExtentsX + b * this._halfExtentsY + c * this._halfExtentsZ;

		return centerDistance > boundOffset ? PlaneClassification.FRONT : centerDistance < -boundOffset ? PlaneClassification.BACK : PlaneClassification.INTERSECT;
	}

	public _update(): void {
		super._update();

		const targetCoordinateSpace: ContainerNode = <ContainerNode> this._asset;
		const picker = (<BoundingVolumePool> this._pool).picker;

		// a null invTargetMatrix means local coords to the node
		const invTargetMatrix: Matrix3D = (targetCoordinateSpace != picker.node)
			? targetCoordinateSpace.getInverseMatrix3D()
			: null;

		this._box = picker._getBoxBoundsInternal(invTargetMatrix, this._strokeFlag, this._fastFlag, this._box);

		if (this._box == null)
			return;

		this._halfExtentsX = this._box.width / 2;
		this._halfExtentsY = this._box.height / 2;
		this._halfExtentsZ = this._box.depth / 2;
		this._centerX = this._box.x + this._halfExtentsX;
		this._centerY = this._box.y + this._halfExtentsY;
		this._centerZ = this._box.z + this._halfExtentsZ;
	}

	// public _createBoundsPrimitive():Sprite
	// {
	// 	this._prefab = new PrimitiveCubePrefab(null, ElementsType.LINE);

	// 	return <Sprite> this._prefab.getNewObject();
	// }
}