import { vec3 } from "gl-matrix";
import { v3_allocate } from "../../../../core/geom/vec3/v3_allocate.js";
import { DEG_TO_RAD } from "../../../../core/math/DEG_TO_RAD.js";
import { Build4RuleGroups } from "./Build4RuleGroups.js";
import { MARK_DEGENERATE } from "./constants/MARK_DEGENERATE.js";
import { DegenEpilogue } from "./DegenEpilogue.js";
import { DegenPrologue } from "./DegenPrologue.js";
import { GenerateInitialVerticesIndexList } from "./GenerateInitialVerticesIndexList.js";
import { GenerateSharedVerticesIndexList } from "./GenerateSharedVerticesIndexList.js";
import { GenerateTSpaces } from "./GenerateTSpaces.js";
import { GetPosition } from "./GetPosition.js";
import { InitTriInfo } from "./InitTriInfo.js";
import { m_getNumFaces } from "./m_getNumFaces.js";
import { m_setTSpace } from "./m_setTSpace.js";
import { malloc } from "./malloc.js";
import { STSpace } from "./STSpace.js";


/*
 To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
 normal map sampler must use the exact inverse of the pixel shader transformation.
 The most efficient transformation we can possibly do in the pixel shader is
 achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
 pixel shader (fast transform out)
 vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
 where vNt is the tangent space normal. The normal map sampler must likewise use the
 interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
 sampler does (exact inverse of pixel shader):
 float3 row0 = cross(vB, vN);
 float3 row1 = cross(vN, vT);
 float3 row2 = cross(vT, vB);
 float fSign = dot(vT, row0)<0 ? -1 : 1;
 vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
 where vNout is the sampled normal in some chosen 3D space.

 Should you choose to reconstruct the bitangent in the pixel shader instead
 of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
 Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
 quads as your renderer then problems will occur since the interpolated tangent spaces will differ
 eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
 sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
 However, this must be used both by the sampler and your tools/rendering pipeline.

 @see https://github.com/mmikk/MikkTSpace/blob/master/mikktspace.c (the original)
 @see https://github.com/blender/blender/blob/594f47ecd2d5367ca936cf6fc6ec8168c2b360d0/intern/mikktspace/mikktspace.c (looks like best-optimized version)
 @see https://github.com/undefinist/mikktspacehx/blob/master/src/mikktspace/Mikktspace.hx (Haxe port)
 @see https://github.com/gltf-rs/mikktspace/blob/6275cc4f15cff8be29819fb34ae8be3b9129dae1/src/generated.rs (Rust port)
*/

class SGroup {
    constructor() {
        this.iNrFaces = 0;
        /**
         * *int
         * @type {null}
         */
        this.pFaceIndices = null;
        this.iVertexRepresentitive = 0;
        this.bOrientPreservering = false;
    }
}

class STriInfo {
    constructor() {
        /**
         * int[3]
         * @type {number[]}
         */
        this.FaceNeighbors = [];

        /**
         * SGroup[3]
         * @type {SGroup[]}
         */
        this.AssignedGroup = [];

        // normalized first order face derivatives

        /**
         * @type {vec3|Float32Array}
         */
        this.vOs = v3_allocate();
        /**
         * @type {vec3|Float32Array}
         */
        this.vOt = v3_allocate();

        // original magnitudes
        this.fMagS = 0;
        this.fMagT = 0;

        /**
         *
         * @type {number}
         */
        this.iOrgFaceNumber = 0;

        /**
         *
         * @type {number}
         */
        this.iFlag = 0;

        /**
         * int[4]
         * @type {number[]}
         */
        this.vert_num = new Uint8Array(4);

        /**
         *
         * @type {number}
         */
        this.iTSpacesOffs = 0;
    }
}

export class SMikkTSpaceContext {
    constructor() {
        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_index = [];
        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_vertex_normal = [];
        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_vertex_position = [];
        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_vertex_uv = [];
        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_vertex_tangent = [];

        /**
         *
         * @type {number[]}
         */
        this.geometry_buffer_vertex_bitangent = [];
    }
}

// returns the position/normal/texcoord of the referenced face of vertex number iVert.
// iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.


/**
 *
 * @param {SMikkTSpaceContext} pContext
 * @param {number} [fAngularThreshold]
 * @return {boolean}
 */
export function genTangSpace(pContext, fAngularThreshold = 180) {
    // count nr_triangles
    let iNrTSPaces = 0, iNrMaxGroups = 0;
    let iNrActiveGroups = 0;

    const iNrFaces = m_getNumFaces(pContext);
    const fThresCos = Math.cos(fAngularThreshold * DEG_TO_RAD);


    // count triangles on supported faces
    // AlexGoldring: since we only support triangles, the number of triangles is going to equal number of faces
    let iNrTrianglesIn = iNrFaces;

    if (iNrTrianglesIn <= 0) {
        return false;
    }

    // allocate memory for an index list
    const piTriListIn = new Int32Array(3 * iNrTrianglesIn);
    const pTriInfos = malloc(STriInfo, iNrTrianglesIn);

    // make an initial triangle --> face index list
    iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);

    // make a welded index list of identical positions and attributes (pos, norm, texc)
    //printf("gen welded index list begin\n");
    GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn);
    //printf("gen welded index list end\n");

    // Mark all degenerate triangles
    const iTotTris = iNrTrianglesIn;
    let iDegenTriangles = 0;

    const p0 = vec3.create();
    const p1 = vec3.create();
    const p2 = vec3.create();

    for (let t = 0; t < iTotTris; t++) {

        const t3 = t * 3;

        const i0 = piTriListIn[t3 ];
        const i1 = piTriListIn[t3 + 1];
        const i2 = piTriListIn[t3 + 2];

        GetPosition(p0, 0, pContext, i0);
        GetPosition(p1, 0, pContext, i1);
        GetPosition(p2, 0, pContext, i2);

        if (
            vec3.exactEquals(p0, p1) ||
            vec3.exactEquals(p0, p2) ||
            vec3.exactEquals(p1, p2)
        ) {
            // degenerate
            pTriInfos[t].iFlag |= MARK_DEGENERATE;
            ++iDegenTriangles;
        }
    }

    iNrTrianglesIn = iTotTris - iDegenTriangles;

    // mark all triangle pairs that belong to a quad with only one
    // good triangle. These need special treatment in DegenEpilogue().
    // Additionally, move all good triangles to the start of
    // pTriInfos[] and piTriListIn[] without changing order and
    // put the degenerate triangles last.
    DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);


    // evaluate triangle level attributes and neighbor list
    //printf("gen neighbors list begin\n");
    InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
    //printf("gen neighbors list end\n");


    // based on the 4 rules, identify groups based on connectivity
    iNrMaxGroups = iNrTrianglesIn * 3;
    const pGroups = malloc(SGroup, iNrMaxGroups);
    const piGroupTrianglesBuffer = new Int32Array(iNrTrianglesIn * 3);

    //printf("gen 4rule groups begin\n");
    iNrActiveGroups = Build4RuleGroups(
        pTriInfos,
        pGroups,
        piGroupTrianglesBuffer,
        piTriListIn,
        iNrTrianglesIn
    );
    //printf("gen 4rule groups end\n");

    //

    /**
     *
     * @type {STSpace[]}
     */
    const psTspace = malloc(STSpace, iNrTSPaces);

    // memset not requried in JS
    // memset(psTspace, 0, sizeof(STSpace)*iNrTSPaces);

    for (let t = 0; t < iNrTSPaces; t++) {
        const stSpace = psTspace[t];

        stSpace.vOs[0] = 1.0;
        stSpace.vOs[1] = 0.0;
        stSpace.vOs[2] = 0.0;

        stSpace.fMagS = 1.0;

        stSpace.vOt[0] = 0.0;
        stSpace.vOt[1] = 1.0;
        stSpace.vOt[2] = 0.0;

        stSpace.fMagT = 1.0;
    }

    // make tspaces, each group is split up into subgroups if necessary
    // based on fAngularThreshold. Finally, a tangent space is made for
    // every resulting subgroup
    //printf("gen tspaces begin\n");
    GenerateTSpaces(
        psTspace,
        pTriInfos,
        pGroups,
        iNrActiveGroups,
        piTriListIn,
        fThresCos,
        pContext
    );
    //printf("gen tspaces end\n");


    // degenerate quads with one good triangle will be fixed by copying a space from
    // the good triangle to the coinciding vertex.
    // all other degenerate triangles will just copy a space from any good triangle
    // with the same welded index in piTriListIn[].
    DegenEpilogue(
        psTspace,
        pTriInfos,
        piTriListIn,
        pContext,
        iNrTrianglesIn,
        iTotTris
    );


    for (let f = 0; f < iNrFaces; f++) {

        // set data
        for (let i = 0; i < 3; i++) {
            const index = f * 3 + i
            const pTSpace = psTspace[index];

            const tangent = pTSpace.vOs;
            const bitangent = pTSpace.vOt;

            m_setTSpace(
                pContext,
                tangent,
                bitangent,
                pTSpace.fMagS,
                pTSpace.fMagT,
                pTSpace.bOrient,
                f,
                i
            );
        }
    }


    return true;
}


