/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */ /** */
import { Vertex3 } from '.';
import { VertexData } from '../loader/babylonjs-import';
import { Writeable } from '../Types';

/**
 * Represents a UV coordinate
 */
export interface UV {
    /** U value */
    u: number;
    /** V value */
    v: number;
}

/**
 * Represents a specific point in a {@link VertexData} instance. This can be very useful when we need
 * to work with specific point data from a {@link VertexData} instance.
 */
export class VertexDataTrianglePoint {
    /**
     * Offset for X value in {@link vertexData.positions} and {@link vertexData.normals} arrays.
     */
    public readonly xOffset: number = 0;

    /**
     * Offset for Y value in {@link vertexData.positions} and {@link vertexData.normals} arrays.
     */
    public readonly yOffset: number = 0;

    /**
     * Offset for Z value in {@link vertexData.positions} and {@link vertexData.normals} arrays.
     */
    public readonly zOffset: number = 0;

    /**
     * Offset for U value in {@link vertexData.uvs}'s arrays
     */
    public readonly uOffset: number = 0;

    /**
     * Offset for V value in {@link vertexData.uvs}'s arrays
     */
    public readonly vOffset: number = 0;

    /**
     *
     * @param vertexData {@link VertexData} instance.
     */
    public constructor(public readonly vertexData: VertexData) {}

    /**
     * Rebuild `x|y|z|u|vOffsets` from specified offset in {@link vertexData.indices} buffer. This is useful
     * in high peformance loops where we might want to reuse {@link VertexDataTrianglePoint} for performance
     * reasons (in very compute heavy scenarios the time to "new" a new object actually matters, not to mention GC contraints).
     * @param indicesOffset Indice in {@link vertexData.indices} (represents a point in a triangle).
     * @return Reference to `this`.
     */
    public rebuild(indicesOffset: number): VertexDataTrianglePoint {
        const self = this as Writeable<VertexDataTrianglePoint>;
        self.xOffset = this.vertexData.indices![indicesOffset] * 3;
        self.yOffset = self.xOffset + 1;
        self.zOffset = self.xOffset + 2;
        self.uOffset = this.vertexData.indices![indicesOffset] * 2;
        self.vOffset = self.uOffset + 1;
        return this;
    }

    /**
     * Copy X,Y,Z coordianate from {@link vertexData}, this instance represents, into {@link dst}
     * @param p Property to get X,Y,Z data from in {@link vertexData}.
     * @param dst Where X,Y,Z coordinate shall be written.
     * @returns Reference to `dst`.
     */
    public xyzToRef<T extends Vertex3>(p: 'positions' | 'normals' | 'tangents', dst: T): T {
        dst.x = this.vertexData[p]![this.xOffset];
        dst.y = this.vertexData[p]![this.yOffset];
        dst.z = this.vertexData[p]![this.zOffset];
        return dst;
    }

    /**
     * Copy U,V coordianate from {@link vertexData}, this instance represents, into {@link dst}
     * @param p Property to get U,V data from in {@link vertexData}.
     * @param dst Where U,V coordinate shall be written.
     * @returns Reference to `dst`.
     */
    public uvToRef<T extends UV>(p: 'uvs' | 'uvs2' | 'uvs3' | 'uvs4' | 'uvs5' | 'uvs6', dst: T): T {
        dst.u = this.vertexData[p]![this.uOffset];
        dst.v = this.vertexData[p]![this.vOffset];
        return dst;
    }
}

/**
 * Callback for use with methods in {@link VertexDataUtil}.
 */
export interface VertexDataUtilsForEachTriangleHandler {
    /**
     * Function signature for use with methods in {@link VertexDataUtils}.
     * @param p1 First corner of the triangle.
     * @param p2 Seconds corner of the triangle
     * @param p3 Third corner of the triangle
     * @returns `false` indicates that caller code should stop any other invocations of the function.
     */
    (p1: VertexDataTrianglePoint, p2: VertexDataTrianglePoint, p3: VertexDataTrianglePoint): boolean | undefined;
}

/**
 * Utility methods for {@link VertexData}.
 */
export class VertexDataUtils {
    /**
     * Visit each triangle (and their corners) defined by `vertexData.indices` and `vertexData.positions`. Also makes it
     * possible to get normals and uvs from the triangle corners.
     * @param vertexData {@link VertexData}.
     * @param action Callback where parameters represents the three corners of the triangle. Do NOT store references to these parameters (or anything in them)
     * as they point to objects which are reused for every invocation of the callback.
     * @param indiceStartOffset Offset in `vertexData.indice` where iteration shall start. Defaults to 0. Will be forced to >= 0 and a multiple of 3.
     */
    public static forEachTriangle(
        vertexData: VertexData,
        action: VertexDataUtilsForEachTriangleHandler,
        indiceStartOffset = 0
    ): void {
        const iLen = vertexData.indices?.length ?? 0;
        const vLen = vertexData.indices?.length ?? 0;
        if (iLen === 0 || vLen === 0) {
            return;
        }

        // Ensure start offset is >= 0 and a multiple of 3
        indiceStartOffset = Math.max(0, indiceStartOffset);
        indiceStartOffset = Math.floor(indiceStartOffset / 3) * 3;

        // TODO If we really need to cut down on allocations then we can put
        // these in a static temp variable instead.
        const p1 = new VertexDataTrianglePoint(vertexData);
        const p2 = new VertexDataTrianglePoint(vertexData);
        const p3 = new VertexDataTrianglePoint(vertexData);

        for (let i = indiceStartOffset; i < iLen; i += 3) {
            p1.rebuild(i);
            p2.rebuild(i + 1);
            p3.rebuild(i + 2);

            if (action(p1, p2, p3) === false) {
                return; // abort if callback returns false.
            }
        }
    }
}
