import { malloc } from "./malloc.js";
import { STSpace } from "./STSpace.js";
import { SSubGroup } from "./SSubGroup.js";
import { vec3 } from "gl-matrix";
import { assert } from "../../../../core/assert.js";
import { GetNormal } from "./GetNormal.js";
import { GROUP_WITH_ANY } from "./constants/GROUP_WITH_ANY.js";
import { INTERNAL_RND_SORT_SEED } from "./constants/INTERNAL_RND_SORT_SEED.js";
import { QuickSort } from "./QuickSort.js";
import { CompareSubGroups } from "./CompareSubGroups.js";
import { array_copy } from "../../../../core/collection/array/array_copy.js";
import { EvalTspace } from "./EvalTspace.js";
import { ORIENT_PRESERVING } from "./constants/ORIENT_PRESERVING.js";
import { AvgTSpace } from "./AvgTSpace.js";
import { v3_scale_dot_sub_normalize } from "./v3_scale_dot_sub_normalize.js";

const vOs = vec3.create();
const vOt = vec3.create();

const vOs2 = vec3.create();
const vOt2 = vec3.create();
/**
 * @type {vec3}
 */
const n = vec3.create();

/**
 * @type {SSubGroup}
 */
const tmp_group = new SSubGroup();

/**
 *
 * @param {STSpace[]} psTspace
 * @param {STriInfo[]} pTriInfos
 * @param {SGroup[]} pGroups
 * @param {number} iNrActiveGroups
 * @param {number[]|Int32Array} piTriListIn
 * @param {number} fThresCos
 * @param {SMikkTSpaceContext} pContext
 */
export function GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext) {

    let iMaxNrFaces = 0, iUniqueTspaces = 0;

    for (let g = 0; g < iNrActiveGroups; g++) {
        const sGroup = pGroups[g];

        if (iMaxNrFaces < sGroup.iNrFaces) {
            iMaxNrFaces = sGroup.iNrFaces;
        }

    }

    if (iMaxNrFaces === 0) {
        // no faces, nothing to do
        return;
    }


    // make initial allocations
    /**
     * @type {STSpace[]}
     */
    const pSubGroupTspace = malloc(STSpace, iMaxNrFaces);

    /**
     *
     * @type {SSubGroup[]}
     */
    const pUniSubGroups = malloc(SSubGroup, iMaxNrFaces);

    /**
     *
     * @type {Int32Array|null}
     */
    const pTmpMembers = new Int32Array(iMaxNrFaces);

    iUniqueTspaces = 0;


    for (let g = 0; g < iNrActiveGroups; g++) {
        const pGroup = pGroups[g];
        let iUniqueSubGroups = 0;

        for (let i = 0; i < pGroup.iNrFaces; i++) {
            // triangles
            const f = pGroup.pFaceIndices[i];	// triangle number

            let bFound = false;

            const tri_info = pTriInfos[f];

            let index = -1;
            if (tri_info.AssignedGroup[0] === pGroup) {
                index = 0;
            } else if (tri_info.AssignedGroup[1] === pGroup) {
                index = 1;
            } else if (tri_info.AssignedGroup[2] === pGroup) {
                index = 2;
            }

            assert(index >= 0 && index < 3);

            const iVertIndex = piTriListIn[f * 3 + index];
            assert.equal(iVertIndex, pGroup.iVertexRepresentitive);

            // is normalized already
            GetNormal(n, pContext, iVertIndex);

            // project
            v3_scale_dot_sub_normalize(vOs, n, tri_info.vOs);
            v3_scale_dot_sub_normalize(vOt, n, tri_info.vOt);

            // original face number
            const iOF_1 = tri_info.iOrgFaceNumber;

            let iMembers = 0;
            for (let j = 0; j < pGroup.iNrFaces; j++) {
                const t = pGroup.pFaceIndices[j];	// triangle number
                const iOF_2 = pTriInfos[t].iOrgFaceNumber;

                // project
                v3_scale_dot_sub_normalize(vOs2, n, pTriInfos[t].vOs);
                v3_scale_dot_sub_normalize(vOt2, n, pTriInfos[t].vOt);

                const bAny = ((tri_info.iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY) !== 0;

                // make sure triangles which belong to the same quad are joined.
                const bSameOrgFace = iOF_1 === iOF_2;

                const fCosS = vec3.dot(vOs, vOs2);
                const fCosT = vec3.dot(vOt, vOt2);

                assert(f !== t || bSameOrgFace);	// sanity check

                if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) {
                    pTmpMembers[iMembers++] = t;
                }

            }

            // sort pTmpMembers
            tmp_group.iNrFaces = iMembers;
            tmp_group.pTriMembers = pTmpMembers;

            if (iMembers > 1) {
                // could replace with a random seed?
                QuickSort(pTmpMembers, 0, iMembers - 1, INTERNAL_RND_SORT_SEED);
            }

            // look for an existing match
            bFound = false;
            let l = 0;
            while (l < iUniqueSubGroups && !bFound) {

                bFound = CompareSubGroups(tmp_group, pUniSubGroups[l]);

                if (!bFound) {
                    ++l;
                }
            }

            // assign tangent space index
            assert(bFound || l === iUniqueSubGroups);
            //piTempTangIndices[f*3+index] = iUniqueTspaces+l;

            // if no match was found we allocate a new subgroup
            if (!bFound) {
                // insert new subgroup
                const pIndices = new Int32Array(iMembers);

                pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers;
                pUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices;

                array_copy(tmp_group.pTriMembers, 0, pIndices, 0, iMembers);

                EvalTspace(
                    pSubGroupTspace[iUniqueSubGroups],
                    tmp_group.pTriMembers,
                    iMembers,
                    piTriListIn,
                    pTriInfos,
                    pContext,
                    pGroup.iVertexRepresentitive
                );

                ++iUniqueSubGroups;
            }

            // output tspace

            const iOffs = tri_info.iTSpacesOffs;
            const iVert = tri_info.vert_num[index];

            /**
             * @type {STSpace}
             */
            const pTS_out = psTspace[iOffs + iVert];

            assert.lessThan(pTS_out.iCounter, 2);
            assert.equal(((tri_info.iFlag & ORIENT_PRESERVING) !== 0), pGroup.bOrientPreservering);

            if (pTS_out.iCounter === 1) {

                AvgTSpace(pTS_out, pTS_out, pSubGroupTspace[l]);
                pTS_out.iCounter = 2;	// update counter
                pTS_out.bOrient = pGroup.bOrientPreservering;

            } else {

                assert.equal(pTS_out.iCounter, 0);

                pTS_out.copy(pSubGroupTspace[l]);
                pTS_out.iCounter = 1;	// update counter
                pTS_out.bOrient = pGroup.bOrientPreservering;

            }

        }

        // clean up and offset iUniqueTspaces

        iUniqueTspaces += iUniqueSubGroups;
    }
}
