export class FilletsService {
    constructor(occ, vecHelper, iteratorService, converterService, entitiesService, transformsService, shapeGettersService, edgesService, operationsService, facesService) {
        this.occ = occ;
        this.vecHelper = vecHelper;
        this.iteratorService = iteratorService;
        this.converterService = converterService;
        this.entitiesService = entitiesService;
        this.transformsService = transformsService;
        this.shapeGettersService = shapeGettersService;
        this.edgesService = edgesService;
        this.operationsService = operationsService;
        this.facesService = facesService;
    }
    filletEdges(inputs) {
        if (!inputs.indexes || (inputs.indexes.length && inputs.indexes.length === 0)) {
            if (inputs.radius === undefined) {
                throw (Error("Radius not defined"));
            }
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            const anEdgeExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_EDGE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE);
            const edges = [];
            while (anEdgeExplorer.More()) {
                const anEdge = this.occ.TopoDS.Edge_1(anEdgeExplorer.Current());
                edges.push(anEdge);
                mkFillet.Add_2(inputs.radius, anEdge);
                anEdgeExplorer.Next();
            }
            const result = mkFillet.Shape();
            mkFillet.delete();
            anEdgeExplorer.delete();
            edges.forEach(e => e.delete());
            return result;
        }
        else if (inputs.indexes && inputs.indexes.length > 0) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            let foundEdges = 0;
            let curFillet;
            let radiusIndex = 0;
            const inputIndexes = inputs.indexes;
            this.iteratorService.forEachEdge(inputs.shape, (index, edge) => {
                if (inputIndexes.includes(index)) {
                    let radius = inputs.radius;
                    if (inputs.radiusList) {
                        radius = inputs.radiusList[radiusIndex];
                        radiusIndex++;
                    }
                    if (radius === undefined) {
                        throw (Error("Radius not defined, or radiusList not correct length"));
                    }
                    mkFillet.Add_2(radius, edge);
                    foundEdges++;
                }
            });
            if (foundEdges === 0) {
                throw (new Error("Fillet Edges Not Found!  Make sure you are looking at the object _before_ the Fillet is applied!"));
            }
            else {
                curFillet = mkFillet.Shape();
            }
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    filletEdgesListOneRadius(inputs) {
        if (inputs.edges && inputs.edges.length > 0) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            inputs.edges.forEach((edge) => {
                mkFillet.Add_2(inputs.radius, edge);
            });
            const curFillet = mkFillet.Shape();
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    filletEdgesList(inputs) {
        if (inputs.edges && inputs.edges.length > 0 && inputs.radiusList && inputs.radiusList.length > 0 && inputs.edges.length === inputs.radiusList.length) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            inputs.edges.forEach((edge, index) => {
                mkFillet.Add_2(inputs.radiusList[index], edge);
            });
            const curFillet = mkFillet.Shape();
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    filletEdgeVariableRadius(inputs) {
        if (inputs.paramsU && inputs.paramsU.length > 0 && inputs.radiusList && inputs.radiusList.length > 0 && inputs.paramsU.length === inputs.radiusList.length) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            this.assignVariableFilletToEdge(inputs, mkFillet);
            const curFillet = mkFillet.Shape();
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    filletEdgesSameVariableRadius(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.radiusList && inputs.radiusList.length > 0 &&
            inputs.paramsU.length === inputs.radiusList.length) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            inputs.edges.forEach((edge) => {
                this.assignVariableFilletToEdge({
                    edge, paramsU: inputs.paramsU, radiusList: inputs.radiusList, shape: inputs.shape,
                }, mkFillet);
            });
            const curFillet = mkFillet.Shape();
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    filletEdgesVariableRadius(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.radiusLists && inputs.radiusLists.length > 0 &&
            inputs.paramsULists.length === inputs.radiusLists.length &&
            inputs.paramsULists.length === inputs.edges.length &&
            inputs.radiusLists.length === inputs.edges.length) {
            const mkFillet = new this.occ.BRepFilletAPI_MakeFillet(inputs.shape, this.occ.ChFi3d_FilletShape.ChFi3d_Rational);
            inputs.edges.forEach((edge, index) => {
                this.assignVariableFilletToEdge({
                    edge, paramsU: inputs.paramsULists[index], radiusList: inputs.radiusLists[index], shape: inputs.shape,
                }, mkFillet);
            });
            const curFillet = mkFillet.Shape();
            mkFillet.delete();
            const result = this.converterService.getActualTypeOfShape(curFillet);
            curFillet.delete();
            return result;
        }
        return undefined;
    }
    assignVariableFilletToEdge(inputs, mkFillet) {
        const array = new this.occ.TColgp_Array1OfPnt2d_2(1, inputs.paramsU.length);
        inputs.paramsU.forEach((param, index) => {
            array.SetValue(index + 1, this.entitiesService.gpPnt2d([param, inputs.radiusList[index]]));
        });
        mkFillet.Add_5(array, inputs.edge);
    }
    chamferEdges(inputs) {
        if (!inputs.indexes || (inputs.indexes.length && inputs.indexes.length === 0)) {
            if (inputs.distance === undefined) {
                throw (Error("Distance is undefined"));
            }
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            const anEdgeExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_EDGE, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE);
            const edges = [];
            while (anEdgeExplorer.More()) {
                const anEdge = this.occ.TopoDS.Edge_1(anEdgeExplorer.Current());
                edges.push(anEdge);
                mkChamfer.Add_2(inputs.distance, anEdge);
                anEdgeExplorer.Next();
            }
            const result = mkChamfer.Shape();
            mkChamfer.delete();
            anEdgeExplorer.delete();
            edges.forEach(e => e.delete());
            return result;
        }
        else if (inputs.indexes && inputs.indexes.length > 0) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            let foundEdges = 0;
            let curChamfer;
            let distanceIndex = 0;
            const inputIndexes = inputs.indexes;
            this.iteratorService.forEachEdge(inputs.shape, (index, edge) => {
                if (inputIndexes.includes(index)) {
                    let distance = inputs.distance;
                    if (inputs.distanceList) {
                        distance = inputs.distanceList[distanceIndex];
                        distanceIndex++;
                    }
                    if (distance === undefined) {
                        throw (Error("Distance not defined and/or distance list incorrect length"));
                    }
                    mkChamfer.Add_2(distance, edge);
                    foundEdges++;
                }
            });
            if (foundEdges === 0) {
                console.error("Chamfer Edges Not Found!  Make sure you are looking at the object _before_ the Fillet is applied!");
                curChamfer = inputs.shape;
            }
            else {
                curChamfer = mkChamfer.Shape();
            }
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        return undefined;
    }
    chamferEdgesList(inputs) {
        if (inputs.edges && inputs.edges.length > 0 && inputs.distanceList && inputs.distanceList.length > 0 && inputs.edges.length === inputs.distanceList.length) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            inputs.edges.forEach((edge, index) => {
                const distance = inputs.distanceList[index];
                if (distance === undefined) {
                    throw (Error("Distance is not defined"));
                }
                mkChamfer.Add_2(distance, edge);
            });
            const curChamfer = mkChamfer.Shape();
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        return undefined;
    }
    chamferEdgeTwoDistances(inputs) {
        const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
        mkChamfer.Add_3(inputs.distance1, inputs.distance2, inputs.edge, inputs.face);
        const curChamfer = mkChamfer.Shape();
        mkChamfer.delete();
        const result = this.converterService.getActualTypeOfShape(curChamfer);
        curChamfer.delete();
        return result;
    }
    chamferEdgesTwoDistances(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.edges.length === inputs.faces.length) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            inputs.edges.forEach((edge, index) => {
                mkChamfer.Add_3(inputs.distance1, inputs.distance2, edge, inputs.faces[index]);
            });
            const curChamfer = mkChamfer.Shape();
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        else {
            return undefined;
        }
    }
    chamferEdgesTwoDistancesLists(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.faces && inputs.faces.length > 0 &&
            inputs.distances1 && inputs.distances1.length > 0 &&
            inputs.distances2 && inputs.distances2.length > 0 &&
            inputs.edges.length === inputs.faces.length &&
            inputs.edges.length === inputs.distances1.length &&
            inputs.edges.length === inputs.distances2.length) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            inputs.edges.forEach((edge, index) => {
                mkChamfer.Add_3(inputs.distances1[index], inputs.distances2[index], edge, inputs.faces[index]);
            });
            const curChamfer = mkChamfer.Shape();
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        else {
            return undefined;
        }
    }
    chamferEdgeDistAngle(inputs) {
        const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
        const radians = this.vecHelper.degToRad(inputs.angle);
        mkChamfer.AddDA(inputs.distance, radians, inputs.edge, inputs.face);
        const curChamfer = mkChamfer.Shape();
        mkChamfer.delete();
        const result = this.converterService.getActualTypeOfShape(curChamfer);
        curChamfer.delete();
        return result;
    }
    chamferEdgesDistsAngles(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.faces && inputs.faces.length > 0 &&
            inputs.distances && inputs.distances.length > 0 &&
            inputs.angles && inputs.angles.length > 0 &&
            inputs.edges.length === inputs.distances.length &&
            inputs.edges.length === inputs.faces.length &&
            inputs.edges.length === inputs.angles.length) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            inputs.edges.forEach((edge, index) => {
                const radians = this.vecHelper.degToRad(inputs.angles[index]);
                mkChamfer.AddDA(inputs.distances[index], radians, edge, inputs.faces[index]);
            });
            const curChamfer = mkChamfer.Shape();
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        else {
            return undefined;
        }
    }
    chamferEdgesDistAngle(inputs) {
        if (inputs.edges && inputs.edges.length > 0 &&
            inputs.faces && inputs.faces.length > 0 &&
            inputs.edges.length === inputs.faces.length) {
            const mkChamfer = new this.occ.BRepFilletAPI_MakeChamfer(inputs.shape);
            const radians = this.vecHelper.degToRad(inputs.angle);
            inputs.edges.forEach((edge, index) => {
                mkChamfer.AddDA(inputs.distance, radians, edge, inputs.faces[index]);
            });
            const curChamfer = mkChamfer.Shape();
            mkChamfer.delete();
            const result = this.converterService.getActualTypeOfShape(curChamfer);
            curChamfer.delete();
            return result;
        }
        else {
            return undefined;
        }
    }
    fillet2d(inputs) {
        if (inputs.indexes && inputs.radiusList && inputs.radiusList.length !== inputs.indexes.length) {
            throw new Error("When using radius list, length of the list must match index list of corners that you want to fillet.");
        }
        let face;
        let isShapeFace = false;
        if (inputs.shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_FACE) {
            face = this.converterService.getActualTypeOfShape(inputs.shape);
            isShapeFace = true;
        }
        else if (inputs.shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_WIRE) {
            const faceBuilder = new this.occ.BRepBuilderAPI_MakeFace_15(inputs.shape, true);
            const messageProgress = new this.occ.Message_ProgressRange_1();
            faceBuilder.Build(messageProgress);
            const shape = faceBuilder.Shape();
            face = this.converterService.getActualTypeOfShape(shape);
            shape.delete();
            messageProgress.delete();
            faceBuilder.delete();
        }
        else {
            throw new Error("You can only fillet a 2d wire or a 2d face.");
        }
        const filletMaker = new this.occ.BRepFilletAPI_MakeFillet2d_2(face);
        const anVertexExplorer = new this.occ.TopExp_Explorer_2(inputs.shape, this.occ.TopAbs_ShapeEnum.TopAbs_VERTEX, this.occ.TopAbs_ShapeEnum.TopAbs_SHAPE);
        let i = 1;
        const cornerVertices = [];
        for (anVertexExplorer; anVertexExplorer.More(); anVertexExplorer.Next()) {
            const vertex = this.occ.TopoDS.Vertex_1(anVertexExplorer.Current());
            if (i % 2 === 0) {
                cornerVertices.push(vertex);
            }
            i++;
        }
        if (!isShapeFace) {
            const wire = inputs.shape;
            if (!wire.Closed_1()) {
                cornerVertices.pop();
            }
        }
        let radiusAddedCounter = 0;
        cornerVertices.forEach((cvx, index) => {
            if (!inputs.indexes) {
                this.applyRadiusToVertex(inputs, filletMaker, cvx, index);
            }
            else if (inputs.indexes.includes(index + 1)) {
                this.applyRadiusToVertex(inputs, filletMaker, cvx, radiusAddedCounter);
                radiusAddedCounter++;
            }
        });
        const messageProgress = new this.occ.Message_ProgressRange_1();
        filletMaker.Build(messageProgress);
        let result;
        if (isShapeFace) {
            result = filletMaker.Shape();
        }
        else {
            const isDone = filletMaker.IsDone();
            if (isDone) {
                const shape = filletMaker.Shape();
                const filletedWires = this.shapeGettersService.getWires({ shape });
                if (filletedWires.length === 1) {
                    result = filletedWires[0];
                }
            }
            else {
                // Previous algorithm fails if the wire is not made up of circular or straight edges. This algorithm is a failover.
                const normal = this.facesService.faceNormalOnUV({ shape: face, paramU: 0.5, paramV: 0.5 });
                result = this.fillet3DWire({ shape: inputs.shape, radius: inputs.radius, radiusList: inputs.radiusList, indexes: inputs.indexes, direction: normal });
            }
        }
        anVertexExplorer.delete();
        filletMaker.delete();
        messageProgress.delete();
        cornerVertices.forEach(cvx => cvx.delete());
        return result;
    }
    fillet3DWire(inputs) {
        let useRadiusList = false;
        if (inputs.radiusList && inputs.radiusList.length > 0 && inputs.indexes && inputs.indexes.length > 0) {
            if (inputs.radiusList.length !== inputs.indexes.length) {
                throw new Error("Radius list and indexes are not the same length");
            }
            else {
                useRadiusList = true;
            }
        }
        // the goal is to make this fillet the same corner indices as fillet 2d command does with the same radius list.
        // This makes this algorithm quite complex when counting which actual edge indices need to be rounded as it is based on
        // extrusion, which creates specific index definitions.
        // let adjustedRadiusList = [...inputs.radiusList];
        // radius list does not need to be adjusted
        // Closed shapes start corners differently on the connection of the first corner, so we need to readjust the edges
        let wireTouse = this.edgesService.fixEdgeOrientationsAlongWire({ shape: inputs.shape });
        if (useRadiusList && inputs.shape.Closed_1()) {
            const edgesOfWire = this.edgesService.getEdgesAlongWire({ shape: inputs.shape });
            const firstEdge = edgesOfWire.shift();
            const adjustEdges = [...edgesOfWire, firstEdge];
            wireTouse = this.converterService.combineEdgesAndWiresIntoAWire({ shapes: adjustEdges });
        }
        else {
            wireTouse = this.converterService.getActualTypeOfShape(inputs.shape.Reversed());
        }
        const extrusion = this.operationsService.extrude({ shape: wireTouse, direction: inputs.direction });
        let adjustedIndexes = inputs.indexes;
        if (useRadiusList) {
            // So original indexes are based on the number of corners between edges. These corner indexes are used as an input, but extrusion creates 3D edges
            // with different indexes, so we need to adjust the indexes to match the 3D edges.
            // the original indexes are [3, 4, 5, 6, 7, 8, 9, 10, 11, ...]
            // the order is [5, 8, 11, 14, 17, 20, 23, 26, 29, ...]
            // this is needed because of the way edge indexes are made on such shapes
            const filteredEnd = inputs.indexes.filter(i => i > 2);
            const maxNr = Math.max(...filteredEnd);
            const adjacentList = [5];
            let lastNr = 5;
            for (let i = 0; i < maxNr; i++) {
                lastNr += 3;
                adjacentList.push(lastNr);
            }
            adjustedIndexes = inputs.indexes.map((index) => {
                if (inputs.shape.Closed_1()) {
                    if (index <= 2) {
                        return index;
                    }
                    else {
                        return adjacentList[index - 3];
                    }
                }
                else {
                    if (index === 1) {
                        return 2;
                    }
                    else {
                        return adjacentList[index - 2];
                    }
                }
            });
        }
        const filletShape = this.filletEdges({ shape: extrusion, radius: inputs.radius, indexes: adjustedIndexes, radiusList: inputs.radiusList });
        const faceEdges = [];
        const faces = this.shapeGettersService.getFaces({ shape: filletShape });
        faces.forEach((f, i) => {
            // due to reversal of wire in the beginning this is stable index now
            // also we need to translate these edges back along direction
            const edgeToAdd = this.shapeGettersService.getEdges({ shape: f })[3];
            faceEdges.push(edgeToAdd);
        });
        const res = this.converterService.combineEdgesAndWiresIntoAWire({ shapes: faceEdges });
        const result = this.transformsService.translate({ shape: res, translation: inputs.direction.map(s => -s) });
        extrusion.delete();
        filletShape.delete();
        faces.forEach(f => f.delete());
        faceEdges.forEach(e => e.delete());
        return result;
    }
    applyRadiusToVertex(inputs, filletMaker, cvx, index) {
        if (inputs.radiusList) {
            const radiusList = inputs.radiusList;
            filletMaker.AddFillet(cvx, radiusList[index]);
        }
        else if (inputs.radius) {
            filletMaker.AddFillet(cvx, inputs.radius);
        }
    }
}
