/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */ /** */

import { Vector3, Axis, Camera, Plane, Ray, Matrix, DeepImmutable } from '../loader/babylonjs-import';
import { Vertex3 } from '../math';

/**
 * Extension of [Plane](https://doc.babylonjs.com/typedoc/classes/babylon.plane) with methods to keep the plane facing
 * a [Camera](https://doc.babylonjs.com/typedoc/classes/babylon.camera) and putting it in front of a Camera.
 */
export class PivotPlane extends Plane {
    private static readonly _tmpCameraDirection = Vector3.Zero();
    private static readonly _tmpRay = Ray.Zero();
    private static readonly _tmpIdentityMatrix = Matrix.Identity();

    private readonly _planeOrigin = Vector3.Zero();

    /**
     * Default distance to a pivot plane.
     */
    public static readonly DefaultDistanceToPivotPlane = 2;

    /**
     * Constructor to create empty pivot plane.
     */
    public constructor() {
        super(0.0, 0.0, 0.0, 0.0);
    }

    /**
     * Create empty pivot plane.
     */
    public static Empty(): PivotPlane {
        return new PivotPlane();
    }

    /**
     * Origin of the plane. Will be (0,0,0) if neither {@link  update} or {@link putInFrontOfCamera}
     * has been called.
     */
    public get Origin(): DeepImmutable<Vector3> {
        return this._planeOrigin;
    }

    /**
     * Updates a existing pivot plane so it faces the camera.
     * @param camera Camera to reorient the pivot plane for.
     * @param planeOrigin Optional origin (point on) for plane. Will set {@link Origin}.
     * @returns Signed distance to pivot plane. Positive if the camera is in front. Otherwise false.
     */
    public update(camera: Camera, planeOrigin?: Vertex3): number {
        // If pos is not specified then we simply recalculate the pivotplane
        // based on the old position.
        if (planeOrigin !== undefined) {
            this._planeOrigin.copyFromFloats(planeOrigin.x, planeOrigin.y, planeOrigin.z);
        }
        camera.getDirectionToRef(Axis.Z, PivotPlane._tmpCameraDirection);
        const cameraDirection = PivotPlane._tmpCameraDirection;
        cameraDirection.negateToRef(this.normal);
        this.d = -(
            this.normal.x * this._planeOrigin.x +
            this.normal.y * this._planeOrigin.y +
            this.normal.z * this._planeOrigin.z
        );
        return this.signedDistanceTo(camera.globalPosition);
    }

    /**
     * Puts the plane so it faces the camera at a distance of `distance` from the camera
     * @param camera {@link Camera} to position plane in front of.
     * @param distance Distance in front of `camera` to place the plane at
     */
    public putInFrontOfCamera(camera: Camera, distance = PivotPlane.DefaultDistanceToPivotPlane): void {
        camera.getDirectionToRef(Axis.Z, PivotPlane._tmpCameraDirection);
        const cameraDirection = PivotPlane._tmpCameraDirection;
        const pivotPointAheadOfCamera = cameraDirection.scaleInPlace(distance).addInPlace(camera.position);
        this._planeOrigin.copyFrom(pivotPointAheadOfCamera);
        this.d = -(
            this.normal.x * this._planeOrigin.x +
            this.normal.y * this._planeOrigin.y +
            this.normal.z * this._planeOrigin.z
        );
    }

    /**
     * Checks if plane is in front of the {@link camera} (`true`) or not (`false`).
     * @param camera {@link Camera}
     * @returns `true` if the plane is in front of the {@link camera}. Otherwise `false`.
     */
    public isInFrontOfCamera(camera: Camera): boolean {
        return this.signedDistanceTo(camera.globalPosition) > 0;
    }
}
