import { shapeTypeEnum, typeSpecificityEnum } from "../../occ-helper";
export class OCCTFace {
    constructor(occ, och) {
        this.occ = occ;
        this.och = och;
    }
    createFaceFromWire(inputs) {
        let result;
        if (this.och.getShapeTypeEnum(inputs.shape) !== shapeTypeEnum.wire) {
            throw new Error("Provided input shape is not a wire");
        }
        if (inputs.planar) {
            const wire = this.occ.TopoDS.Wire_1(inputs.shape);
            result = this.och.bRepBuilderAPIMakeFaceFromWire(wire, inputs.planar);
            wire.delete();
        }
        else {
            const Degree = 3;
            const NbPtsOnCur = 15;
            const NbIter = 2;
            const Anisotropie = false;
            const Tol2d = 0.00001;
            const Tol3d = 0.0001;
            const TolAng = 0.01;
            const TolCurv = 0.1;
            const MaxDeg = 8;
            const MaxSegments = 9;
            const bs = new this.occ.BRepFill_Filling(Degree, NbPtsOnCur, NbIter, Anisotropie, Tol2d, Tol3d, TolAng, TolCurv, MaxDeg, MaxSegments);
            const edges = this.och.getEdges(inputs);
            edges.forEach(e => {
                bs.Add_1(e, this.occ.GeomAbs_Shape.GeomAbs_C0, true);
            });
            bs.Build();
            if (!bs.IsDone()) {
                throw new Error("Could not create non planar face");
            }
            result = bs.Face();
            bs.delete();
            edges.forEach(e => e.delete());
        }
        return result;
    }
    createFacesFromWires(inputs) {
        const result = inputs.shapes.map(shape => {
            return this.createFaceFromWire({ shape, planar: inputs.planar });
        });
        return result;
    }
    faceFromSurface(inputs) {
        const face = this.och.bRepBuilderAPIMakeFaceFromSurface(inputs.shape, inputs.tolerance);
        if (face.IsNull()) {
            face.delete();
            throw new Error("Could not construct a face from the surface. Check if surface is not infinite.");
        }
        else {
            return face;
        }
    }
    faceFromSurfaceAndWire(inputs) {
        if (inputs.shapes === undefined || inputs.shapes.length < 2) {
            throw (Error(("Shapes needs to be an array of length 2")));
        }
        const face = this.och.bRepBuilderAPIMakeFaceFromSurfaceAndWire(inputs.shapes[0], inputs.shapes[1], inputs.inside);
        if (face.IsNull()) {
            face.delete();
            throw new Error("Could not construct a face from the surface. Check if surface is not infinite.");
        }
        else {
            return face;
        }
    }
    getUMinBound(inputs) {
        const face = inputs.shape;
        const { uMin } = this.och.getUVBounds(face);
        return uMin;
    }
    getUMaxBound(inputs) {
        const face = inputs.shape;
        const { uMax } = this.och.getUVBounds(face);
        return uMax;
    }
    getVMinBound(inputs) {
        const face = inputs.shape;
        const { vMin } = this.och.getUVBounds(face);
        return vMin;
    }
    getVMaxBound(inputs) {
        const face = inputs.shape;
        const { vMax } = this.och.getUVBounds(face);
        return vMax;
    }
    subdivideToPointsControlled(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const surface = handle.get();
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const points = [];
        for (let i = 0; i < inputs.nrDivisionsU; i++) {
            const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
            const halfStepU = stepU / 2;
            const stepsU = stepU * i;
            for (let j = 0; j < inputs.nrDivisionsV; j++) {
                const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                let v = vMin + stepsV;
                v += (inputs.shiftHalfStepNthV && (i + inputs.shiftHalfStepVOffsetN) % inputs.shiftHalfStepNthV === 0) ? halfStepV : 0;
                let u = uMin + stepsU;
                u += (inputs.shiftHalfStepNthU && (j + inputs.shiftHalfStepUOffsetN) % inputs.shiftHalfStepNthU === 0) ? halfStepU : 0;
                const gpPnt = this.och.gpPnt([0, 0, 0]);
                surface.D0(u, v, gpPnt);
                const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
                let shouldPush = true;
                if (i === 0 && inputs.removeStartEdgeNthU && (j + inputs.removeStartEdgeUOffsetN) % inputs.removeStartEdgeNthU === 0) {
                    shouldPush = false;
                }
                else if (i === inputs.nrDivisionsU - 1 && inputs.removeEndEdgeNthU && (j + inputs.removeEndEdgeUOffsetN) % inputs.removeEndEdgeNthU === 0) {
                    shouldPush = false;
                }
                else if (j === 0 && inputs.removeStartEdgeNthV && (i + inputs.removeStartEdgeVOffsetN) % inputs.removeStartEdgeNthV === 0) {
                    shouldPush = false;
                }
                else if (j === inputs.nrDivisionsV - 1 && inputs.removeEndEdgeNthV && (i + inputs.removeEndEdgeVOffsetN) % inputs.removeEndEdgeNthV === 0) {
                    shouldPush = false;
                }
                if (shouldPush) {
                    points.push(pt);
                }
                gpPnt.delete();
            }
        }
        handle.delete();
        return points;
    }
    subdivideToPoints(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const surface = handle.get();
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const points = [];
        const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
        const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
        const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
        const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
        for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
            const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
            const halfStepU = stepU / 2;
            const stepsU = stepU * i;
            const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
            for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
                const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
                const gpPnt = this.och.gpPnt([0, 0, 0]);
                surface.D0(u, v, gpPnt);
                const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
                points.push(pt);
                gpPnt.delete();
            }
        }
        handle.delete();
        return points;
    }
    subdivideToNormals(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const points = [];
        const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
        const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
        const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
        const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
        for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
            const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
            const halfStepU = stepU / 2;
            const stepsU = stepU * i;
            const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
            for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
                const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
                const gpDir = this.och.gpDir([0, 1, 0]);
                const gpUv = this.och.gpPnt2d([u, v]);
                this.occ.GeomLib.NormEstim(handle, gpUv, 1e-7, gpDir);
                const pt = [gpDir.X(), gpDir.Y(), gpDir.Z()];
                points.push(pt);
                gpDir.delete();
                gpUv.delete();
            }
        }
        handle.delete();
        return points;
    }
    subdivideToPointsOnParam(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const surface = handle.get();
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const points = [];
        const removeStart = inputs.removeStartPoint ? 1 : 0;
        const removeEnd = inputs.removeEndPoint ? 1 : 0;
        let param = inputs.param;
        if (inputs.isU) {
            param = uMin + (uMax - uMin) * param;
        }
        else {
            param = vMin + (vMax - vMin) * param;
        }
        for (let j = 0 + removeStart; j < inputs.nrPoints - removeEnd; j++) {
            let p;
            if (inputs.isU) {
                const stepV = (vMax - vMin) / (inputs.nrPoints - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                p = vMin + (inputs.shiftHalfStep ? halfStepV : 0) + stepsV;
            }
            else {
                const stepU = (uMax - uMin) / (inputs.nrPoints - 1);
                const halfStepU = stepU / 2;
                const stepsU = stepU * j;
                p = uMin + (inputs.shiftHalfStep ? halfStepU : 0) + stepsU;
            }
            const gpPnt = this.och.gpPnt([0, 0, 0]);
            if (inputs.isU) {
                surface.D0(param, p, gpPnt);
            }
            else {
                surface.D0(p, param, gpPnt);
            }
            const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
            points.push(pt);
            gpPnt.delete();
        }
        handle.delete();
        return points;
    }
    subdivideToUVOnParam(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const uvs = [];
        const removeStart = inputs.removeStartPoint ? 1 : 0;
        const removeEnd = inputs.removeEndPoint ? 1 : 0;
        let param = inputs.param;
        if (inputs.isU) {
            param = uMin + (uMax - uMin) * param;
        }
        else {
            param = vMin + (vMax - vMin) * param;
        }
        for (let j = 0 + removeStart; j < inputs.nrPoints - removeEnd; j++) {
            let p;
            if (inputs.isU) {
                const stepV = (vMax - vMin) / (inputs.nrPoints - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                p = vMin + (inputs.shiftHalfStep ? halfStepV : 0) + stepsV;
            }
            else {
                const stepU = (uMax - uMin) / (inputs.nrPoints - 1);
                const halfStepU = stepU / 2;
                const stepsU = stepU * j;
                p = uMin + (inputs.shiftHalfStep ? halfStepU : 0) + stepsU;
            }
            let uv;
            if (inputs.isU) {
                uv = [param, p];
            }
            else {
                uv = [p, param];
            }
            uvs.push(uv);
        }
        return uvs;
    }
    subdivideToUV(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const uvs = [];
        const uStartRemoval = inputs.removeStartEdgeU ? 1 : 0;
        const uEndRemoval = inputs.removeEndEdgeU ? 1 : 0;
        const vStartRemoval = inputs.removeStartEdgeV ? 1 : 0;
        const vEndRemoval = inputs.removeEndEdgeV ? 1 : 0;
        for (let i = 0 + uStartRemoval; i < inputs.nrDivisionsU - uEndRemoval; i++) {
            const stepU = (uMax - uMin) / (inputs.nrDivisionsU - 1);
            const halfStepU = stepU / 2;
            const stepsU = stepU * i;
            const u = uMin + (inputs.shiftHalfStepU ? halfStepU : 0) + stepsU;
            for (let j = 0 + vStartRemoval; j < inputs.nrDivisionsV - vEndRemoval; j++) {
                const stepV = (vMax - vMin) / (inputs.nrDivisionsV - 1);
                const halfStepV = stepV / 2;
                const stepsV = stepV * j;
                const v = vMin + (inputs.shiftHalfStepV ? halfStepV : 0) + stepsV;
                uvs.push([u, v]);
            }
        }
        return uvs;
    }
    uvOnFace(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const u = uMin + (uMax - uMin) * inputs.paramU;
        const v = vMin + (vMax - vMin) * inputs.paramV;
        return [u, v];
    }
    pointsOnUVs(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const surface = handle.get();
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const pts = inputs.paramsUV.map(uv => {
            const u = uMin + (uMax - uMin) * uv[0];
            const v = vMin + (vMax - vMin) * uv[1];
            const gpPnt = this.och.gpPnt([0, 0, 0]);
            surface.D0(u, v, gpPnt);
            const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
            gpPnt.delete();
            return pt;
        });
        surface.delete();
        return pts;
    }
    normalsOnUVs(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const nrmls = inputs.paramsUV.map(uv => {
            const u = uMin + (uMax - uMin) * uv[0];
            const v = vMin + (vMax - vMin) * uv[1];
            const gpDir = this.och.gpDir([0, 1, 0]);
            this.occ.GeomLib.NormEstim(handle, this.och.gpPnt2d([u, v]), 1e-7, gpDir);
            const pt = [gpDir.X(), gpDir.Y(), gpDir.Z()];
            gpDir.delete();
            return pt;
        });
        handle.delete();
        return nrmls;
    }
    pointOnUV(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const surface = handle.get();
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const u = uMin + (uMax - uMin) * inputs.paramU;
        const v = vMin + (vMax - vMin) * inputs.paramV;
        const gpPnt = this.och.gpPnt([0, 0, 0]);
        surface.D0(u, v, gpPnt);
        const pt = [gpPnt.X(), gpPnt.Y(), gpPnt.Z()];
        gpPnt.delete();
        handle.delete();
        return pt;
    }
    normalOnUV(inputs) {
        if (inputs.shape === undefined) {
            throw (Error(("Face not defined")));
        }
        const face = inputs.shape;
        const handle = this.occ.BRep_Tool.Surface_2(face);
        const { uMin, uMax, vMin, vMax } = this.och.getUVBounds(face);
        const u = uMin + (uMax - uMin) * inputs.paramU;
        const v = vMin + (vMax - vMin) * inputs.paramV;
        const gpDir = this.och.gpDir([0, 1, 0]);
        this.occ.GeomLib.NormEstim(handle, this.och.gpPnt2d([u, v]), 1e-7, gpDir);
        const dir = [gpDir.X(), gpDir.Y(), gpDir.Z()];
        gpDir.delete();
        handle.delete();
        return dir;
    }
    createPolygonFace(inputs) {
        const wire = this.och.createPolygonWire(inputs);
        const result = this.och.bRepBuilderAPIMakeFaceFromWire(wire, false);
        wire.delete();
        return result;
    }
    createCircleFace(inputs) {
        return this.och.createCircle(inputs.radius, inputs.center, inputs.direction, typeSpecificityEnum.face);
    }
    createEllipseFace(inputs) {
        return this.och.createEllipse(inputs.radiusMinor, inputs.radiusMajor, inputs.center, inputs.direction, typeSpecificityEnum.face);
    }
    createSquareFace(inputs) {
        return this.och.createSquareFace(inputs);
    }
    createRectangleFace(inputs) {
        return this.och.createRectangleFace(inputs);
    }
    getFace(inputs) {
        if (!inputs.shape || this.och.getShapeTypeEnum(inputs.shape) < shapeTypeEnum.shell || inputs.shape.IsNull()) {
            throw (new Error("Shape is not provided or is of incorrect type"));
        }
        if (!inputs.index) {
            inputs.index = 0;
        }
        let innerFace = {};
        let facesFound = 0;
        this.och.forEachFace(inputs.shape, (i, s) => {
            if (i === inputs.index) {
                innerFace = this.occ.TopoDS.Face_1(s);
            }
            facesFound++;
        });
        if (facesFound < inputs.index || inputs.index < 0) {
            throw (new Error("Face index is out of range"));
        }
        else {
            return innerFace;
        }
    }
    getFaces(inputs) {
        return this.och.getFaces(inputs);
    }
    reversedFace(inputs) {
        const face = inputs.shape;
        const reversed = face.Reversed();
        const result = this.och.getActualTypeOfShape(reversed);
        reversed.delete();
        return result;
    }
    getFaceArea(inputs) {
        return this.och.getFaceArea(inputs);
    }
    getFacesAreas(inputs) {
        return this.och.getFacesAreas(inputs);
    }
    getFaceCenterOfMass(inputs) {
        return this.och.getFaceCenterOfMass(inputs);
    }
    getFacesCentersOfMass(inputs) {
        return this.och.getFacesCentersOfMass(inputs);
    }
}
