import { typeSpecificityEnum } from "../../occ-helper";
import * as Inputs from "../../api/inputs/inputs";
export class OCCTWire {
    constructor(occ, och) {
        this.occ = occ;
        this.och = och;
    }
    createPolygonWire(inputs) {
        return this.och.createPolygonWire(inputs);
    }
    createPolygons(inputs) {
        const wires = inputs.polygons.map(p => this.createPolygonWire(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    createPolylineWire(inputs) {
        return this.och.createPolylineWire(inputs);
    }
    createPolylines(inputs) {
        const wires = inputs.polylines.map(p => this.createPolylineWire(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    createLineWire(inputs) {
        return this.och.createLineWire(inputs);
    }
    createLines(inputs) {
        const wires = inputs.lines.map(p => this.createLineWire(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    createBezier(inputs) {
        const ptList = new this.occ.TColgp_Array1OfPnt_2(1, inputs.points.length + (inputs.closed ? 1 : 0));
        for (let pIndex = 1; pIndex <= inputs.points.length; pIndex++) {
            ptList.SetValue(pIndex, this.och.gpPnt(inputs.points[pIndex - 1]));
        }
        if (inputs.closed) {
            ptList.SetValue(inputs.points.length + 1, ptList.Value(1));
        }
        const geomBezierCurveHandle = new this.occ.Geom_BezierCurve_1(ptList);
        const geomCurve = new this.occ.Handle_Geom_Curve_2(geomBezierCurveHandle);
        const edgeMaker = new this.occ.BRepBuilderAPI_MakeEdge_24(geomCurve);
        const edge = edgeMaker.Edge();
        const makeWire = new this.occ.BRepBuilderAPI_MakeWire_2(edge);
        const result = makeWire.Wire();
        makeWire.delete();
        edgeMaker.delete();
        edge.delete();
        geomCurve.delete();
        ptList.delete();
        return result;
    }
    createBezierWires(inputs) {
        const wires = inputs.bezierWires.map(p => this.createBezier(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    interpolatePoints(inputs) {
        return this.och.interpolatePoints(inputs);
    }
    interpolateWires(inputs) {
        const wires = inputs.interpolations.map(p => this.interpolatePoints(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    splitOnPoints(inputs) {
        const tolerance = 1.0e-7;
        const startPointOnWire = this.och.startPointOnWire({ shape: inputs.shape });
        const endPointOnWire = this.och.endPointOnWire({ shape: inputs.shape });
        // This is needed to make follow up algorithm to work properly on open wires
        const wireIsClosed = this.och.vecHelper.vectorsTheSame(endPointOnWire, startPointOnWire, tolerance);
        if (!wireIsClosed) {
            if (!inputs.points.some(p => this.och.vecHelper.vectorsTheSame(p, startPointOnWire, tolerance))) {
                inputs.points.push(startPointOnWire);
            }
            if (!inputs.points.some(p => this.och.vecHelper.vectorsTheSame(p, endPointOnWire, tolerance))) {
                inputs.points.push(endPointOnWire);
            }
        }
        const shortLines = this.createLines({
            lines: inputs.points.map(p => ({ start: p, end: [p[0], p[1] + tolerance, p[2]] })),
            returnCompound: false
        });
        const diff = this.och.difference({ shape: inputs.shape, shapes: shortLines, keepEdges: true });
        const edges = this.och.getEdges({ shape: diff });
        const groupedEdges = [];
        edges.forEach((e) => {
            const latestArray = groupedEdges[groupedEdges.length - 1];
            // direction of edges seem to be reversed, but it does not matter for this algorithm
            // better not to start reversing things and just deal with it
            const endPointOnEdge = this.och.endPointOnEdge({ shape: e });
            const startPointOnEdge = this.och.startPointOnEdge({ shape: e });
            const pointExistsOnEdgeEnd = inputs.points.some(p => this.och.vecHelper.vectorsTheSame(endPointOnEdge, p, tolerance));
            const pointExistsOnEdgeStart = inputs.points.some(p => this.och.vecHelper.vectorsTheSame(startPointOnEdge, p, tolerance));
            if (pointExistsOnEdgeEnd && !pointExistsOnEdgeStart) {
                groupedEdges.push([e]);
            }
            else if (!pointExistsOnEdgeEnd && pointExistsOnEdgeStart) {
                if (latestArray) {
                    latestArray.push(e);
                }
                else {
                    groupedEdges.push([e]);
                }
            }
            else if (pointExistsOnEdgeEnd && pointExistsOnEdgeStart) {
                groupedEdges.push([e]);
            }
            else if (!pointExistsOnEdgeEnd && !pointExistsOnEdgeStart) {
                if (latestArray) {
                    latestArray.push(e);
                }
                else {
                    groupedEdges.push([e]);
                }
            }
        });
        const wires = [];
        groupedEdges.forEach(g => {
            const wire = this.combineEdgesAndWiresIntoAWire({
                shapes: g
            });
            wires.push(wire);
        });
        // when wire is closed and first wire and last wire share the corner, they need to be combined if there's no split point that matches that corner
        if (wireIsClosed && wires.length > 1) {
            const endPointOnFirstWire = this.och.endPointOnWire({ shape: wires[0] });
            const startPointOnLastWire = this.och.startPointOnWire({ shape: wires[wires.length - 1] });
            if (this.och.vecHelper.vectorsTheSame(endPointOnFirstWire, startPointOnLastWire, tolerance)) {
                const pt = inputs.points.find(p => this.och.vecHelper.vectorsTheSame(p, endPointOnFirstWire, tolerance));
                if (!pt) {
                    const combined = this.addEdgesAndWiresToWire({ shape: wires[wires.length - 1], shapes: [wires[0]] });
                    wires[0] = combined;
                    wires.pop();
                }
            }
        }
        return wires;
    }
    combineEdgesAndWiresIntoAWire(inputs) {
        return this.och.combineEdgesAndWiresIntoAWire(inputs);
    }
    createWireFromEdge(inputs) {
        return this.och.createWireFromEdges(inputs);
    }
    addEdgesAndWiresToWire(inputs) {
        const makeWire = new this.occ.BRepBuilderAPI_MakeWire_1();
        makeWire.Add_2(inputs.shape);
        inputs.shapes.forEach((shape) => {
            if (shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_EDGE) {
                makeWire.Add_1(shape);
            }
            else if (shape.ShapeType() === this.occ.TopAbs_ShapeEnum.TopAbs_WIRE) {
                makeWire.Add_2(shape);
            }
        });
        let result;
        if (makeWire.IsDone()) {
            result = makeWire.Wire();
        }
        else {
            throw new Error("Wire could not be constructed. Check if edges and wires do not have disconnected elements.");
        }
        makeWire.delete();
        return result;
    }
    createBSpline(inputs) {
        return this.och.createBSpline(inputs);
    }
    createBSplines(inputs) {
        const wires = inputs.bSplines.map(p => this.createBSpline(p)).filter(s => s !== undefined);
        return this.och.makeCompoundIfNeeded(wires, inputs.returnCompound);
    }
    divideWireByParamsToPoints(inputs) {
        return this.och.divideWireByParamsToPoints(inputs);
    }
    divideWiresByParamsToPoints(inputs) {
        return inputs.shapes.map(s => this.divideWireByParamsToPoints(Object.assign(Object.assign({}, inputs), { shape: s })));
    }
    divideWireByEqualDistanceToPoints(inputs) {
        return this.och.divideWireByEqualDistanceToPoints(inputs);
    }
    divideWiresByEqualDistanceToPoints(inputs) {
        return inputs.shapes.map(s => this.divideWireByEqualDistanceToPoints(Object.assign(Object.assign({}, inputs), { shape: s })));
    }
    pointOnWireAtParam(inputs) {
        return this.och.pointOnWireAtParam(inputs);
    }
    pointOnWireAtLength(inputs) {
        return this.och.pointOnWireAtLength(inputs);
    }
    tangentOnWireAtParam(inputs) {
        return this.och.tangentOnWireAtParam(inputs);
    }
    tangentOnWireAtLength(inputs) {
        return this.och.tangentOnWireAtLength(inputs);
    }
    derivativesOnWireAtLength(inputs) {
        const wire = inputs.shape;
        const curve = new this.occ.BRepAdaptor_CompCurve_2(wire, false);
        const absc = new this.occ.GCPnts_AbscissaPoint_2(curve, inputs.length, curve.FirstParameter());
        const param = absc.Parameter();
        const gpPnt = this.och.gpPnt([0, 0, 0]);
        const der1 = this.och.gpVec([0, 0, 0]);
        const der2 = this.och.gpVec([0, 0, 0]);
        const der3 = this.och.gpVec([0, 0, 0]);
        curve.D3(param, gpPnt, der1, der2, der3);
        const der = [[der1.X(), der1.Y(), der1.Z()], [der2.X(), der2.Y(), der2.Z()], [der3.X(), der3.Y(), der3.Z()]];
        der1.delete();
        der2.delete();
        der3.delete();
        curve.delete();
        absc.delete();
        gpPnt.delete();
        return der;
    }
    derivativesOnWireAtParam(inputs) {
        const wire = inputs.shape;
        const curve = new this.occ.BRepAdaptor_CompCurve_2(wire, false);
        const gpPnt = this.och.gpPnt([0, 0, 0]);
        const der1 = this.och.gpVec([0, 0, 0]);
        const der2 = this.och.gpVec([0, 0, 0]);
        const der3 = this.och.gpVec([0, 0, 0]);
        const param = this.och.remap(inputs.param, 0, 1, curve.FirstParameter(), curve.LastParameter());
        curve.D3(param, gpPnt, der1, der2, der3);
        const der = [[der1.X(), der1.Y(), der1.Z()], [der2.X(), der2.Y(), der2.Z()], [der3.X(), der3.Y(), der3.Z()]];
        der1.delete();
        der2.delete();
        der3.delete();
        curve.delete();
        gpPnt.delete();
        return der;
    }
    startPointOnWire(inputs) {
        return this.och.startPointOnWire(inputs);
    }
    endPointOnWire(inputs) {
        return this.och.endPointOnWire(inputs);
    }
    createCircleWire(inputs) {
        return this.och.createCircle(inputs.radius, inputs.center, inputs.direction, typeSpecificityEnum.wire);
    }
    createEllipseWire(inputs) {
        return this.och.createEllipse(inputs.radiusMinor, inputs.radiusMajor, inputs.center, inputs.direction, typeSpecificityEnum.wire);
    }
    createSquareWire(inputs) {
        return this.och.createSquareWire(inputs);
    }
    createStarWire(inputs) {
        return this.och.createStarWire(inputs);
    }
    createParallelogramWire(inputs) {
        return this.och.createParallelogramWire(inputs);
    }
    createHeartWire(inputs) {
        return this.och.createHeartWire(inputs);
    }
    createNGonWire(inputs) {
        return this.och.createNGonWire(inputs);
    }
    createRectangleWire(inputs) {
        return this.och.createRectangleWire(inputs);
    }
    createLPolygonWire(inputs) {
        return this.och.createLPolygonWire(inputs);
    }
    getWire(inputs) {
        if (!inputs.shape || inputs.shape.IsNull()) {
            throw (new Error("Shape is not provided or is null"));
        }
        const shapeType = this.och.getShapeTypeEnum(inputs.shape);
        if ((shapeType === Inputs.OCCT.shapeTypeEnum.wire ||
            shapeType === Inputs.OCCT.shapeTypeEnum.edge ||
            shapeType === Inputs.OCCT.shapeTypeEnum.vertex)) {
            throw (new Error("Shape is of incorrect type"));
        }
        if (!inputs.index) {
            inputs.index = 0;
        }
        let innerWire;
        this.och.forEachWire(inputs.shape, (i, s) => {
            if (i === inputs.index) {
                innerWire = this.occ.TopoDS.Wire_1(s);
            }
        });
        if (!innerWire) {
            throw (Error("Wire not found"));
        }
        return innerWire;
    }
    getWires(inputs) {
        return this.och.getWires(inputs);
    }
    getWireLength(inputs) {
        return this.och.getWireLength(inputs);
    }
    getWiresLengths(inputs) {
        return this.och.getWiresLengths(inputs);
    }
    getWireCenterOfMass(inputs) {
        return this.och.getWireCenterOfMass(inputs);
    }
    getWiresCentersOfMass(inputs) {
        return inputs.shapes.map(w => this.och.getWireCenterOfMass({
            shape: w
        }));
    }
    reversedWire(inputs) {
        const wire = inputs.shape;
        const reversed = wire.Reversed();
        const result = this.och.getActualTypeOfShape(reversed);
        reversed.delete();
        return result;
    }
    placeWireOnFace(inputs) {
        const wire = inputs.wire;
        const face = inputs.face;
        const srf = this.och.surfaceFromFace({ shape: face });
        const result = this.och.placeWire(wire, srf);
        return result;
    }
    placeWiresOnFace(inputs) {
        const wires = inputs.wires;
        const face = inputs.face;
        const srf = this.och.surfaceFromFace({ shape: face });
        const result = wires.map(wire => this.och.placeWire(wire, srf));
        return result;
    }
    closeOpenWire(inputs) {
        const wire = inputs.shape;
        const firstPoint = this.och.startPointOnWire({ shape: wire });
        const lastPoint = this.och.endPointOnWire({ shape: wire });
        const tolerance = 1.0e-7;
        if (this.och.vecHelper.vectorsTheSame(firstPoint, lastPoint, tolerance)) {
            return wire;
        }
        const edgeWire = this.createLineWire({ start: lastPoint, end: firstPoint });
        const result = this.addEdgesAndWiresToWire({ shape: wire, shapes: [edgeWire] });
        edgeWire.delete();
        return result;
    }
    project(inputs) {
        const wire = inputs.wire;
        const gpDir = this.och.gpDir(inputs.direction);
        const proj = new this.occ.BRepProj_Projection_1(wire, inputs.shape, gpDir);
        const shape = proj.Shape();
        gpDir.delete();
        proj.delete();
        return shape;
    }
    projectWires(inputs) {
        const shapes = [];
        inputs.wires.forEach(wire => {
            const gpDir = this.och.gpDir(inputs.direction);
            const proj = new this.occ.BRepProj_Projection_1(wire, inputs.shape, gpDir);
            const shape = proj.Shape();
            shapes.push(shape);
            gpDir.delete();
            proj.delete();
        });
        return shapes;
    }
}
