import { BimCoreApi } from '../BimCoreApi';
import { Matrix, Plane, Ray, Vector3 } from '../loader/babylonjs-import';
import { copyVertex3ToRef, intersectsPlaneAtToRef, Vertex2, Vertex3 } from '../math';
import { BimPointBase } from './BimPointBase';
import { CoordinateTracker, TrackCoordinate3D } from './CoordinateTracker';

export interface HtmlPointParent<T extends TrackCoordinate3D> {
    /** @hidden */
    readonly _pointTracker: CoordinateTracker<T>;
    worldMatrix: () => Matrix;
    scale: (point: Html2CanvasPoint, retainAspectRatio?: boolean) => void;
    rotate: (point: Html2CanvasPoint) => void;
    translate: () => void;
    centerPoint: () => Html2CanvasPoint;
}

/**
 * Represents result of a {@link DynamicPolygonPoint} move operation.
 */
export enum PointMoveResult {
    /** Result is unknown */
    Unknown = 0,
    /** Points were moved successfully */
    Success = 1,
    /** Points were not moved because the polygon would have become complex */
    ComplexPolygon = 2,
    /** Could not move point because the ray (from mouse coordinate) did not intersect the polygon plane. */
    RayIntersectionFailed = 3
}

export class Html2CanvasPoint extends BimPointBase {
    private static readonly _tmp = {
        identityMatrix: Matrix.Identity(),
        rayA: Ray.Zero(),
        vector3A: Vector3.Zero(),
        vector3B: Vector3.Zero(),
        counter: 0
    };

    /** When `true` then {@link DynamicPolygon.onPointTrackableScreen} will trigger for this instance. Otherwise it will not */
    public isEnabled = true;

    public _parent: HtmlPointParent<Html2CanvasPoint>;

    protected recalculateOrGetWorldCoords(force = false): Vertex3 {
        if (this._recalculateWorldCoords || force) {
            Vector3.TransformCoordinatesFromFloatsToRef(
                this._localX,
                this._localY,
                this._localZ,
                this._parent.worldMatrix(),
                Html2CanvasPoint._tmp.vector3B
            );
            this._worldX = Html2CanvasPoint._tmp.vector3B.x;
            this._worldY = Html2CanvasPoint._tmp.vector3B.y;
            this._worldZ = Html2CanvasPoint._tmp.vector3B.z;
            this._recalculateWorldCoords = false;
        }
        return { x: this._worldX, y: this._worldY, z: this._worldZ };
    }

    protected recalculateOrGetLocalCoords(force = false): Vertex3 {
        if (this._recalculateLocalCoords || force) {
            Vector3.TransformCoordinatesFromFloatsToRef(
                this._worldX,
                this._worldY,
                this._worldZ,
                this._parent.worldMatrix().clone().invert(),
                Html2CanvasPoint._tmp.vector3B
            );
            this._localX = Html2CanvasPoint._tmp.vector3B.x;
            this._localY = Html2CanvasPoint._tmp.vector3B.y;
            this._localZ = Html2CanvasPoint._tmp.vector3B.z;
            this._recalculateLocalCoords = false;
        }
        return { x: this._localX, y: this._localY, z: this._localZ };
    }

    /**
     *
     * @param _api {@link BimCoreApi} API.
     * @param plane [Plane](https://doc.babylonjs.com/typedoc/classes/babylon.plane) that this point lies on.
     */
    public constructor(
        protected readonly _api: BimCoreApi,
        public readonly plane: Plane
    ) {
        super();
    }

    /**
     * Attempt to move a point to a new location on [Plane](https://doc.babylonjs.com/typedoc/classes/babylon.plane).
     * To find the location, a ray is shot from camera through `canvasCoordinate`
     * and then an intersection test is performed. If the ray intersects Plane, the point is moved to the coordinates
     * of the intersection. Otherwise nothing happens.
     * The point is assigned the same x,y,z values
     * as the coordinate where the ray intersects the plane.
     * @returns a {@link DynamicPolygonPointMoveResult}.
     */
    public move(canvasCoordinate: Vertex2): PointMoveResult {
        // If we get an intersection and if point is virtual then convert the virtual point to a real point.
        // and trigger the coordinate tracker. We also add two new virtual points to the coordinate tracker
        // otherwise we simply move it.
        const pickingRay = Html2CanvasPoint._tmp.rayA;
        this._api.viewer.scene.createPickingRayToRef(
            canvasCoordinate.x,
            canvasCoordinate.y,
            Html2CanvasPoint._tmp.identityMatrix,
            pickingRay,
            this._api.viewer.scene.activeCamera
        );

        // TODO If we move a point so it ends up perfectly on the line of the neighbour points then we could
        // convert the point to a virtual point. It no longer contributes to the polygon anyway.
        // If that happens then return DynamicPolygonPointMoveResult.ComplexPolygon

        const intersectionPoint = Html2CanvasPoint._tmp.vector3A;
        const distance = intersectsPlaneAtToRef(pickingRay, this.plane, intersectionPoint);
        if (distance > -1) {
            copyVertex3ToRef(intersectionPoint, this);

            return PointMoveResult.Success;
        }
        return PointMoveResult.RayIntersectionFailed;
    }

    public scale(retainAspectRatio = true): void {
        this._parent.scale(this, retainAspectRatio);
    }

    public rotate(): void {
        this._parent.rotate(this);
    }

    public tranlate(): void {
        this._parent.translate();
    }
}
