import { shapeTypeEnum, typeSpecificityEnum } from "../occ-helper";
import * as Inputs from "../api/inputs/inputs";
export class OCCTOperations {
    constructor(occ, och) {
        this.occ = occ;
        this.och = och;
    }
    closestPointsBetweenTwoShapes(inputs) {
        if (inputs.shapes === undefined || inputs.shapes.length < 2) {
            throw (Error(("Shapes needs to be an array of length 2")));
        }
        return this.och.closestPointsBetweenTwoShapes(inputs.shapes[0], inputs.shapes[1]);
    }
    closestPointsOnShapeFromPoints(inputs) {
        const vertexes = inputs.points.map(p => this.och.makeVertex(p));
        const pointsOnShape = vertexes.map(v => this.och.closestPointsBetweenTwoShapes(v, inputs.shape));
        return pointsOnShape.map(p => p[1]);
    }
    closestPointsOnShapesFromPoints(inputs) {
        const vertexes = inputs.points.map(p => this.och.makeVertex(p));
        const result = [];
        inputs.shapes.forEach((s) => {
            const pointsOnShape = vertexes.map(v => this.och.closestPointsBetweenTwoShapes(v, s));
            result.push(...pointsOnShape.map(p => p[1]));
        });
        return result;
    }
    loft(inputs) {
        const pipe = new this.occ.BRepOffsetAPI_ThruSections(inputs.makeSolid, false, 1.0e-06);
        inputs.shapes.forEach((wire) => {
            pipe.AddWire(wire);
        });
        pipe.CheckCompatibility(false);
        const pipeShape = pipe.Shape();
        const res = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        return res;
    }
    loftAdvanced(inputs) {
        if (inputs.periodic && !inputs.closed) {
            throw new Error("Cant construct periodic non closed loft.");
        }
        const pipe = new this.occ.BRepOffsetAPI_ThruSections(inputs.makeSolid, inputs.straight, inputs.tolerance);
        const wires = [];
        const vertices = [];
        if (inputs.startVertex) {
            const v = this.och.makeVertex(inputs.startVertex);
            pipe.AddVertex(v);
            vertices.push(v);
        }
        if (inputs.closed && !inputs.periodic) {
            inputs.shapes.push(inputs.shapes[0]);
        }
        else if (inputs.closed && inputs.periodic) {
            const pointsOnCrvs = [];
            inputs.shapes.forEach((s) => {
                const pts = this.och.divideWireByParamsToPoints({ shape: s, nrOfDivisions: inputs.nrPeriodicSections, removeStartPoint: false, removeEndPoint: false });
                pointsOnCrvs.push(pts);
            });
            for (let i = 0; i < inputs.nrPeriodicSections; i++) {
                const ptsForPerpWire = pointsOnCrvs.map(p => p[i]);
                const periodicWire = this.och.interpolatePoints({ points: ptsForPerpWire, tolerance: inputs.tolerance, periodic: true });
                pipe.AddWire(periodicWire);
                wires.push(periodicWire);
            }
        }
        if (!inputs.periodic) {
            inputs.shapes.forEach((wire) => {
                pipe.AddWire(wire);
            });
        }
        const endVertices = [];
        if (inputs.endVertex) {
            const v = this.och.makeVertex(inputs.endVertex);
            pipe.AddVertex(v);
            endVertices.push(v);
        }
        if (inputs.useSmoothing) {
            pipe.SetSmoothing(inputs.useSmoothing);
        }
        if (inputs.maxUDegree) {
            pipe.SetMaxDegree(inputs.maxUDegree);
        }
        let parType = undefined;
        if (inputs.parType === Inputs.OCCT.ApproxParametrizationTypeEnum.approxChordLength) {
            parType = this.occ.Approx_ParametrizationType.Approx_ChordLength;
        }
        else if (inputs.parType === Inputs.OCCT.ApproxParametrizationTypeEnum.approxCentripetal) {
            parType = this.occ.Approx_ParametrizationType.Approx_Centripetal;
        }
        else if (inputs.parType === Inputs.OCCT.ApproxParametrizationTypeEnum.approxIsoParametric) {
            parType = this.occ.Approx_ParametrizationType.Approx_IsoParametric;
        }
        if (parType) {
            pipe.SetParType(parType);
        }
        pipe.CheckCompatibility(false);
        const pipeShape = pipe.Shape();
        const res = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        wires.forEach(w => w.delete());
        vertices.forEach(v => v.delete());
        endVertices.forEach(v => v.delete());
        return res;
    }
    offset(inputs) {
        return this.offsetAdv({ shape: inputs.shape, distance: inputs.distance, tolerance: inputs.tolerance, joinType: Inputs.OCCT.JoinTypeEnum.arc, removeIntEdges: false });
    }
    offsetAdv(inputs) {
        if (!inputs.tolerance) {
            inputs.tolerance = 0.1;
        }
        if (inputs.distance === 0.0) {
            return inputs.shape;
        }
        let offset;
        const joinType = this.getJoinType(inputs.joinType);
        // only this mode is implemented currently, so we cannot expose others...
        const brepOffsetMode = this.occ.BRepOffset_Mode.BRepOffset_Skin;
        const wires = [];
        if ((this.och.getShapeTypeEnum(inputs.shape) === shapeTypeEnum.wire ||
            this.och.getShapeTypeEnum(inputs.shape) === shapeTypeEnum.edge)) {
            let wire;
            if (this.och.getShapeTypeEnum(inputs.shape) === shapeTypeEnum.edge) {
                wire = this.och.bRepBuilderAPIMakeWire(inputs.shape);
                wires.push(wire);
            }
            else {
                wire = inputs.shape;
            }
            try {
                offset = new this.occ.BRepOffsetAPI_MakeOffset_1();
                offset.Init_2(joinType, false);
                offset.AddWire(wire);
                offset.Perform(inputs.distance, 0.0);
            }
            catch (ex) {
                // if first method fails we can still try the second one on wire
                offset = new this.occ.BRepOffsetAPI_MakeOffsetShape();
                offset.PerformByJoin(wire, inputs.distance, inputs.tolerance, brepOffsetMode, false, false, joinType, inputs.removeIntEdges, new this.occ.Message_ProgressRange_1());
            }
        }
        else {
            const shapeToOffset = inputs.shape;
            offset = new this.occ.BRepOffsetAPI_MakeOffsetShape();
            offset.PerformByJoin(shapeToOffset, inputs.distance, inputs.tolerance, brepOffsetMode, false, false, joinType, inputs.removeIntEdges, new this.occ.Message_ProgressRange_1());
        }
        const offsetShape = offset.Shape();
        const result = this.och.getActualTypeOfShape(offsetShape);
        offsetShape.delete();
        if (offset) {
            offset.delete();
        }
        wires.forEach(w => w.delete());
        return result;
    }
    extrudeShapes(inputs) {
        return inputs.shapes.map(shape => {
            const extruded = this.extrude({ shape, direction: inputs.direction });
            const result = this.och.getActualTypeOfShape(extruded);
            extruded.delete();
            return result;
        });
    }
    extrude(inputs) {
        return this.och.extrude(inputs);
    }
    splitShapeWithShapes(inputs) {
        return this.och.splitShapeWithShapes(inputs);
    }
    revolve(inputs) {
        if (!inputs.angle) {
            inputs.angle = 360.0;
        }
        if (!inputs.direction) {
            inputs.direction = [0, 0, 1];
        }
        let result;
        if (inputs.angle >= 360.0) {
            const pt1 = new this.occ.gp_Pnt_3(0, 0, 0);
            const dir = new this.occ.gp_Dir_4(inputs.direction[0], inputs.direction[1], inputs.direction[2]);
            const ax1 = new this.occ.gp_Ax1_2(pt1, dir);
            const makeRevol = new this.occ.BRepPrimAPI_MakeRevol_2(inputs.shape, ax1, inputs.copy);
            result = makeRevol.Shape();
            makeRevol.delete();
            pt1.delete();
            dir.delete();
            ax1.delete();
        }
        else {
            const pt1 = new this.occ.gp_Pnt_3(0, 0, 0);
            const dir = new this.occ.gp_Dir_4(inputs.direction[0], inputs.direction[1], inputs.direction[2]);
            const ax1 = new this.occ.gp_Ax1_2(pt1, dir);
            const makeRevol = new this.occ.BRepPrimAPI_MakeRevol_1(inputs.shape, ax1, inputs.angle * 0.0174533, inputs.copy);
            result = makeRevol.Shape();
            makeRevol.delete();
            pt1.delete();
            dir.delete();
            ax1.delete();
        }
        const actual = this.och.getActualTypeOfShape(result);
        result.delete();
        return actual;
    }
    rotatedExtrude(inputs) {
        const translatedShape = this.och.translate({
            translation: [0, inputs.height, 0],
            shape: inputs.shape,
        });
        const upperPolygon = this.och.rotate({
            axis: [0, 1, 0],
            angle: inputs.angle,
            shape: translatedShape
        });
        // Define the straight spine going up the middle of the sweep
        const spineWire = this.och.createBSpline({
            points: [
                [0, 0, 0],
                [0, inputs.height, 0]
            ],
            closed: false,
        });
        // Define the guiding helical auxiliary spine (which controls the rotation)
        const steps = 30;
        const aspinePoints = [];
        for (let i = 0; i <= steps; i++) {
            const alpha = i / steps;
            aspinePoints.push([
                20 * Math.sin(alpha * inputs.angle * 0.0174533),
                inputs.height * alpha,
                20 * Math.cos(alpha * inputs.angle * 0.0174533),
            ]);
        }
        const aspineWire = this.och.createBSpline({ points: aspinePoints, closed: false });
        // Sweep the face wires along the spine to create the extrusion
        const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(spineWire);
        pipe.SetMode_5(aspineWire, true, this.occ.BRepFill_TypeOfContact.BRepFill_NoContact);
        pipe.Add_1(inputs.shape, false, false);
        pipe.Add_1(upperPolygon, false, false);
        pipe.Build(new this.occ.Message_ProgressRange_1());
        pipe.MakeSolid();
        const pipeShape = pipe.Shape();
        const result = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        aspineWire.delete();
        spineWire.delete();
        upperPolygon.delete();
        translatedShape.delete();
        return result;
    }
    pipe(inputs) {
        const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(inputs.shape);
        inputs.shapes.forEach(sh => {
            pipe.Add_1(sh, false, false);
        });
        pipe.Build(new this.occ.Message_ProgressRange_1());
        pipe.MakeSolid();
        const pipeShape = pipe.Shape();
        const result = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        return result;
    }
    pipePolylineWireNGon(inputs) {
        const wire = inputs.shape;
        const shapesToPassThrough = [];
        const edges = this.och.getEdges({ shape: wire });
        edges.forEach((e, index) => {
            const edgeStartPt = this.och.startPointOnEdge({ shape: e });
            const tangent = this.och.tangentOnEdgeAtParam({ shape: e, param: 0 });
            let tangentPreviousEdgeEnd;
            let averageTangentVec = tangent;
            if (index > 0 && index < edges.length - 1) {
                const previousEdge = edges[index - 1];
                tangentPreviousEdgeEnd = this.och.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 });
                averageTangentVec = [tangent[0] + tangentPreviousEdgeEnd[0] / 2, tangent[1] + tangentPreviousEdgeEnd[1] / 2, tangent[2] + tangentPreviousEdgeEnd[2] / 2];
            }
            const ngon = this.och.createNGonWire({ radius: inputs.radius, center: edgeStartPt, direction: averageTangentVec, nrCorners: inputs.nrCorners });
            shapesToPassThrough.push(ngon);
            if (index === edges.length - 1) {
                const edgeEndPt = this.och.endPointOnEdge({ shape: e });
                const tangentEndPt = this.och.tangentOnEdgeAtParam({ shape: e, param: 1 });
                const ngon = this.och.createNGonWire({ radius: inputs.radius, center: edgeEndPt, direction: tangentEndPt, nrCorners: inputs.nrCorners });
                shapesToPassThrough.push(ngon);
            }
        });
        const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(wire);
        shapesToPassThrough.forEach(s => {
            pipe.Add_1(s, false, false);
        });
        pipe.Build(new this.occ.Message_ProgressRange_1());
        pipe.MakeSolid();
        const pipeShape = pipe.Shape();
        const result = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        return result;
    }
    pipeWireCylindrical(inputs) {
        const wire = inputs.shape;
        const shapesToPassThrough = [];
        const edges = this.och.getEdges({ shape: wire });
        edges.forEach((e, index) => {
            const edgeStartPt = this.och.startPointOnEdge({ shape: e });
            const tangent = this.och.tangentOnEdgeAtParam({ shape: e, param: 0 });
            let tangentPreviousEdgeEnd;
            let averageTangentVec = tangent;
            if (index > 0 && index < edges.length - 1) {
                const previousEdge = edges[index - 1];
                tangentPreviousEdgeEnd = this.och.tangentOnEdgeAtParam({ shape: previousEdge, param: 1 });
                averageTangentVec = [tangent[0] + tangentPreviousEdgeEnd[0] / 2, tangent[1] + tangentPreviousEdgeEnd[1] / 2, tangent[2] + tangentPreviousEdgeEnd[2] / 2];
            }
            const circle = this.och.createCircle(inputs.radius, edgeStartPt, averageTangentVec, typeSpecificityEnum.wire);
            shapesToPassThrough.push(circle);
            if (index === edges.length - 1) {
                const edgeEndPt = this.och.endPointOnEdge({ shape: e });
                const tangentEndPt = this.och.tangentOnEdgeAtParam({ shape: e, param: 1 });
                const line = this.och.createCircle(inputs.radius, edgeEndPt, tangentEndPt, typeSpecificityEnum.wire);
                shapesToPassThrough.push(line);
            }
        });
        const pipe = new this.occ.BRepOffsetAPI_MakePipeShell(wire);
        shapesToPassThrough.forEach(s => {
            pipe.Add_1(s, false, false);
        });
        pipe.Build(new this.occ.Message_ProgressRange_1());
        pipe.MakeSolid();
        const pipeShape = pipe.Shape();
        const result = this.och.getActualTypeOfShape(pipeShape);
        pipeShape.delete();
        pipe.delete();
        return result;
    }
    pipeWiresCylindrical(inputs) {
        return inputs.shapes.map(wire => {
            return this.pipeWireCylindrical({ shape: wire, radius: inputs.radius });
        });
    }
    makeThickSolidSimple(inputs) {
        const maker = new this.occ.BRepOffsetAPI_MakeThickSolid();
        maker.MakeThickSolidBySimple(inputs.shape, inputs.offset);
        maker.Build(new this.occ.Message_ProgressRange_1());
        const makerShape = maker.Shape();
        const result = this.och.getActualTypeOfShape(makerShape);
        maker.delete();
        makerShape.delete();
        return result;
    }
    makeThickSolidByJoin(inputs) {
        const facesToRemove = new this.occ.TopTools_ListOfShape_1();
        inputs.shapes.forEach(shape => {
            facesToRemove.Append_1(shape);
        });
        const myBody = new this.occ.BRepOffsetAPI_MakeThickSolid();
        const jointType = this.getJoinType(inputs.joinType);
        myBody.MakeThickSolidByJoin(inputs.shape, facesToRemove, inputs.offset, inputs.tolerance, this.occ.BRepOffset_Mode.BRepOffset_Skin, // currently a single option
        inputs.intersection, inputs.selfIntersection, jointType, inputs.removeIntEdges, new this.occ.Message_ProgressRange_1());
        const makeThick = myBody.Shape();
        const result = this.och.getActualTypeOfShape(makeThick);
        makeThick.delete();
        myBody.delete();
        facesToRemove.delete();
        return result;
    }
    getJoinType(jointType) {
        let res;
        switch (jointType) {
            case Inputs.OCCT.JoinTypeEnum.arc: {
                res = this.occ.GeomAbs_JoinType.GeomAbs_Arc;
                break;
            }
            case Inputs.OCCT.JoinTypeEnum.intersection: {
                res = this.occ.GeomAbs_JoinType.GeomAbs_Intersection;
                break;
            }
            case Inputs.OCCT.JoinTypeEnum.tangent: {
                res = this.occ.GeomAbs_JoinType.GeomAbs_Tangent;
                break;
            }
        }
        return res;
    }
    getBRepOffsetMode(offsetMode) {
        let res;
        switch (offsetMode) {
            case Inputs.OCCT.BRepOffsetModeEnum.skin: {
                res = this.occ.BRepOffset_Mode.BRepOffset_Skin;
                break;
            }
            case Inputs.OCCT.BRepOffsetModeEnum.pipe: {
                res = this.occ.BRepOffset_Mode.BRepOffset_Pipe;
                break;
            }
            case Inputs.OCCT.BRepOffsetModeEnum.rectoVerso: {
                res = this.occ.BRepOffset_Mode.BRepOffset_RectoVerso;
                break;
            }
        }
        return res;
    }
}
