export class MeshingService {
    constructor(occ, shapeGettersService, transformsService, edgesService, facesService, wiresService, base) {
        this.occ = occ;
        this.shapeGettersService = shapeGettersService;
        this.transformsService = transformsService;
        this.edgesService = edgesService;
        this.facesService = facesService;
        this.wiresService = wiresService;
        this.base = base;
    }
    shapeFacesToPolygonPoints(inputs) {
        const def = this.shapeToMesh(inputs);
        const res = [];
        def.faceList.forEach(face => {
            const vertices = face.vertex_coord;
            const indices = face.tri_indexes;
            for (let i = 0; i < indices.length; i += 3) {
                const p1 = indices[i];
                const p2 = indices[i + 1];
                const p3 = indices[i + 2];
                let pts = [
                    [vertices[p1 * 3], vertices[p1 * 3 + 1], vertices[p1 * 3 + 2]],
                    [vertices[p2 * 3], vertices[p2 * 3 + 1], vertices[p2 * 3 + 2]],
                    [vertices[p3 * 3], vertices[p3 * 3 + 1], vertices[p3 * 3 + 2]],
                ];
                if (inputs.reversedPoints) {
                    pts = pts.reverse();
                }
                res.push(pts);
            }
        });
        return res;
    }
    shapesToMeshes(inputs) {
        return inputs.shapes.map(shape => this.shapeToMesh({ shape, precision: inputs.precision, adjustYtoZ: inputs.adjustYtoZ }));
    }
    shapeToMesh(inputs) {
        const faceList = [];
        const edgeList = [];
        const pointsList = [];
        let shapeToUse = inputs.shape;
        if (shapeToUse.IsNull())
            return { faceList, edgeList, pointsList: [] };
        // This could be made optional...
        // Clean cached triangulation data for the shape.
        // This allows to get lower res models out of higher res that was once computed and cached.
        this.occ.BRepTools.Clean(shapeToUse, true);
        if (inputs.adjustYtoZ) {
            const shapeToUseRotated = this.transformsService.rotate({ shape: inputs.shape, axis: [1, 0, 0], angle: -90 });
            const shapeMirrored = this.transformsService.mirrorAlongNormal({ shape: shapeToUseRotated, origin: [0, 0, 0], normal: [0, 0, 1] });
            shapeToUseRotated.delete();
            shapeToUse.delete();
            shapeToUse = shapeMirrored;
        }
        // Iterate through the faces and triangulate each one
        const triangulations = [];
        const faces = this.shapeGettersService.getFaces({ shape: shapeToUse });
        let incrementalMeshBuilder;
        if (faces && faces.length) {
            incrementalMeshBuilder = new this.occ.BRepMesh_IncrementalMesh_2(shapeToUse, inputs.precision, false, 0.5, false);
            faces.forEach((myFace, faceIndex) => {
                const aLocation = new this.occ.TopLoc_Location_1();
                const myT = this.occ.BRep_Tool.Triangulation(myFace, aLocation, 0);
                if (myT.IsNull()) {
                    console.error("Encountered Null Face!");
                    return;
                }
                const thisFace = {
                    vertex_coord: [],
                    normal_coord: [],
                    uvs: [],
                    tri_indexes: [],
                    vertex_coord_vec: [],
                    number_of_triangles: 0,
                    center_point: undefined,
                    center_normal: undefined,
                    face_index: faceIndex
                };
                thisFace.center_point = this.facesService.pointOnUV({ shape: myFace, paramU: 0.5, paramV: 0.5 });
                thisFace.center_normal = this.facesService.normalOnUV({ shape: myFace, paramU: 0.5, paramV: 0.5 });
                const pc = new this.occ.Poly_Connect_2(myT);
                const triangulation = myT.get();
                // write vertex buffer
                thisFace.vertex_coord = new Array(triangulation.NbNodes() * 3);
                thisFace.vertex_coord_vec = [];
                for (let i = 0; i < triangulation.NbNodes(); i++) {
                    const p = triangulation.Node(i + 1).Transformed(aLocation.Transformation());
                    const uv = triangulation.UVNode(i + 1);
                    thisFace.uvs[(i * 2) + 0] = uv.X();
                    thisFace.uvs[(i * 2) + 1] = uv.Y();
                    thisFace.vertex_coord[(i * 3) + 0] = p.X();
                    thisFace.vertex_coord[(i * 3) + 1] = p.Y();
                    thisFace.vertex_coord[(i * 3) + 2] = p.Z();
                    thisFace.vertex_coord_vec.push([p.X(), p.Y(), p.Z()]);
                }
                // write normal buffer
                const myNormal = new this.occ.TColgp_Array1OfDir_2(1, triangulation.NbNodes());
                this.occ.StdPrs_ToolTriangulatedShape.Normal(myFace, pc, myNormal);
                thisFace.normal_coord = new Array(myNormal.Length() * 3);
                for (let i = 0; i < myNormal.Length(); i++) {
                    const d = myNormal.Value(i + 1);
                    thisFace.normal_coord[(i * 3) + 0] = d.X();
                    thisFace.normal_coord[(i * 3) + 1] = d.Y();
                    thisFace.normal_coord[(i * 3) + 2] = d.Z();
                }
                // write triangle buffer
                const orient = myFace.Orientation_1();
                const triangles = myT.get().Triangles();
                thisFace.tri_indexes = new Array(triangles.Length() * 3);
                let validFaceTriCount = 0;
                for (let nt = 1; nt <= myT.get().NbTriangles(); nt++) {
                    const t = triangles.Value(nt);
                    let n1 = t.Value(1);
                    let n2 = t.Value(2);
                    const n3 = t.Value(3);
                    if (orient !== this.occ.TopAbs_Orientation.TopAbs_FORWARD) {
                        const tmp = n1;
                        n1 = n2;
                        n2 = tmp;
                    }
                    thisFace.tri_indexes[(validFaceTriCount * 3) + 0] = n1 - 1;
                    thisFace.tri_indexes[(validFaceTriCount * 3) + 1] = n2 - 1;
                    thisFace.tri_indexes[(validFaceTriCount * 3) + 2] = n3 - 1;
                    validFaceTriCount++;
                }
                thisFace.number_of_triangles = validFaceTriCount;
                faceList.push(thisFace);
                triangulations.push(myT);
                aLocation.delete();
                myNormal.delete();
                triangles.delete();
                pc.delete();
            });
        }
        // Nullify Triangulations between runs so they're not stored in the cache
        for (let i = 0; i < triangulations.length; i++) {
            triangulations[i].Nullify();
            triangulations[i].delete();
        }
        // Get the free edges that aren't on any triangulated face/surface
        const edges = this.shapeGettersService.getEdges({ shape: shapeToUse });
        edges.forEach((myEdge, index) => {
            const thisEdge = {
                vertex_coord: [],
                middle_point: undefined,
                edge_index: -1
            };
            thisEdge.middle_point = this.edgesService.pointOnEdgeAtParam({ shape: myEdge, param: 0.5 });
            const aLocation = new this.occ.TopLoc_Location_1();
            const adaptorCurve = new this.occ.BRepAdaptor_Curve_2(myEdge);
            const tangDef = new this.occ.GCPnts_TangentialDeflection_2(adaptorCurve, inputs.precision, 0.1, 2, 1.0e-9, 1.0e-7);
            // write vertex buffer
            thisEdge.vertex_coord = [];
            const nrPoints = tangDef.NbPoints();
            const tangDefValues = [];
            for (let j = 0; j < nrPoints; j++) {
                const tangDefVal = tangDef.Value(j + 1);
                thisEdge.vertex_coord.push([
                    tangDefVal.X(),
                    tangDefVal.Y(),
                    tangDefVal.Z()
                ]);
                tangDefValues.push(tangDefVal);
            }
            thisEdge.edge_index = index;
            edgeList.push(thisEdge);
            tangDefValues.forEach(v => v.delete());
            aLocation.delete();
            adaptorCurve.delete();
            tangDef.delete();
            this.occ.BRepTools.Clean(myEdge, true);
        });
        const vertices = this.shapeGettersService.getVertices({ shape: shapeToUse });
        if (vertices.length > 0) {
            vertices.forEach(v => {
                const pt = this.occ.BRep_Tool.Pnt(v);
                pointsList.push([pt.X(), pt.Y(), pt.Z()]);
                pt.delete();
            });
        }
        if (incrementalMeshBuilder) {
            incrementalMeshBuilder.Delete();
        }
        this.occ.BRepTools.Clean(shapeToUse, true);
        return { faceList, edgeList, pointsList };
    }
    meshMeshIntersectionWires(inputs) {
        const shape1 = inputs.shape1;
        const shape2 = inputs.shape2;
        const mesh1 = this.shapeFacesToPolygonPoints({ shape: shape1, precision: inputs.precision1, adjustYtoZ: false, reversedPoints: false });
        const mesh2 = this.shapeFacesToPolygonPoints({ shape: shape2, precision: inputs.precision2, adjustYtoZ: false, reversedPoints: false });
        const res = this.base.mesh.meshMeshIntersectionPolylines({
            mesh1, mesh2
        });
        const wires = [];
        res.forEach(r => {
            if (r.points && r.points.length > 0) {
                if (r.isClosed) {
                    wires.push(this.wiresService.createPolygonWire({
                        points: r.points,
                    }));
                }
                else {
                    wires.push(this.wiresService.createPolylineWire({
                        points: r.points,
                    }));
                }
            }
        });
        return wires;
    }
    meshMeshIntersectionPoints(inputs) {
        const shape1 = inputs.shape1;
        const shape2 = inputs.shape2;
        const mesh1 = this.shapeFacesToPolygonPoints({ shape: shape1, precision: inputs.precision1, adjustYtoZ: false, reversedPoints: false });
        const mesh2 = this.shapeFacesToPolygonPoints({ shape: shape2, precision: inputs.precision2, adjustYtoZ: false, reversedPoints: false });
        return this.base.mesh.meshMeshIntersectionPoints({ mesh1, mesh2 });
    }
    meshMeshIntersectionOfShapesWires(inputs) {
        const wireIntersections = [];
        inputs.shapes.forEach((shape, index) => {
            const shape1 = inputs.shape;
            const shape2 = inputs.shapes[index];
            let precision2 = inputs.precision;
            if (inputs.precisionShapes && inputs.precisionShapes.length > 0) {
                const p = inputs.precisionShapes[index];
                if (p) {
                    precision2 = p;
                }
            }
            const wires = this.meshMeshIntersectionWires({ shape1, shape2, precision1: inputs.precision, precision2 });
            wireIntersections.push(...wires);
        });
        return wireIntersections;
    }
    meshMeshIntersectionOfShapesPoints(inputs) {
        const pointIntersections = [];
        inputs.shapes.forEach((shape, index) => {
            const shape1 = inputs.shape;
            const shape2 = inputs.shapes[index];
            let precision2 = inputs.precision;
            if (inputs.precisionShapes && inputs.precisionShapes.length > 0) {
                const p = inputs.precisionShapes[index];
                if (p) {
                    precision2 = p;
                }
            }
            const points = this.meshMeshIntersectionPoints({ shape1, shape2, precision1: inputs.precision, precision2 });
            pointIntersections.push(...points);
        });
        return pointIntersections;
    }
}
