import { Curve } from '../../math/geometry/curve';
import { LineSegment } from '../../math/geometry/lineSegment';
import { Point } from '../../math/geometry/point';
import { Rectangle } from '../../math/geometry/rectangle';
import { SmoothedPolyline } from '../../math/geometry/smoothedPolyline';
import { Algorithm } from './../../utils/algorithm';
//import { FlatEdgeRouter } from './FlatEdgeRouter'
import { CornerSite } from '../../math/geometry/cornerSite';
import { NodeKind } from './NodeKind';
import { Arrowhead } from '../core/arrowhead';
import { SmoothedPolylineCalculator } from './SmoothedPolylineCalculator';
// import {Assert} from '../../utils/assert'
import { StraightLineEdges } from '../../routing/StraightLineEdges';
//  The class responsible for the routing of splines
export class Routing extends Algorithm {
    constructor(settings, originalGraph, dbP, yLayerArrays, properLayeredGraph, intGraph) {
        super(null); // todo: init with the not null canceltoken
        this.settings = settings;
        this.OriginalGraph = originalGraph;
        this.Database = dbP;
        this.ProperLayeredGraph = properLayeredGraph;
        this.LayerArrays = yLayerArrays;
        this.IntGraph = intGraph;
    }
    //  Executes the actual algorithm.
    run() {
        this.createSplines();
    }
    //  The method does the main work.
    createSplines() {
        this.createRegularSplines();
        this.createSelfSplines();
        if (this.IntGraph != null) {
            this.RouteFlatEdges();
        }
        if (this.OriginalGraph.graph.parent == null) {
            // TODO: just creating straigh line edges for missing ones, hook up SplineRouter
            this.RouteUnroutedEdges();
        }
    }
    RouteUnroutedEdges() {
        for (const n of this.OriginalGraph.deepNodes()) {
            for (const e of n.outEdges()) {
                if (!e.curve)
                    StraightLineEdges.RouteEdge(e, Math.max(e.source.padding, e.target.padding));
            }
        }
    }
    RouteFlatEdges() {
        //  throw new Error('not implemented')
        // const flatEdgeRouter = new FlatEdgeRouter(this.settings, this)
        // flatEdgeRouter.run()
    }
    createRegularSplines() {
        for (const intEdgeList of this.Database.RegularMultiedges()) {
            // Here we try to optimize multi-edge routing
            const m = intEdgeList.length;
            const optimizeShortEdges = m == 1 && !this.FanAtSourceOrTarget(intEdgeList[0]);
            for (let i = Math.floor(m / 2); i < m; i++) {
                this.createSplineForNonSelfEdge(intEdgeList[i], optimizeShortEdges);
            }
            for (let i = Math.floor(m / 2) - 1; i >= 0; i--) {
                this.createSplineForNonSelfEdge(intEdgeList[i], optimizeShortEdges);
            }
        }
    }
    FanAtSourceOrTarget(intEdge) {
        return this.ProperLayeredGraph.OutDegreeIsMoreThanOne(intEdge.source) || this.ProperLayeredGraph.InDegreeIsMoreThanOne(intEdge.target);
    }
    createSelfSplines() {
        for (const [k, v] of this.Database.Multiedges.keyValues()) {
            const ip = k;
            if (ip.x == ip.y) {
                const anchor = this.Database.Anchors[ip.x];
                let offset = anchor.leftAnchor;
                for (const intEdge of v) {
                    const dx = this.settings.NodeSeparation + (this.settings.MinNodeWidth + offset);
                    const dy = anchor.bottomAnchor / 2;
                    const p0 = anchor.origin;
                    const p1 = p0.add(new Point(0, dy));
                    const p2 = p0.add(new Point(dx, dy));
                    const p3 = p0.add(new Point(dx, dy * -1));
                    const p4 = p0.add(new Point(0, dy * -1));
                    let s = CornerSite.mkSiteP(p0);
                    const polyline = new SmoothedPolyline(s);
                    s = CornerSite.mkSiteSP(s, p1);
                    s = CornerSite.mkSiteSP(s, p2);
                    s = CornerSite.mkSiteSP(s, p3);
                    s = CornerSite.mkSiteSP(s, p4);
                    CornerSite.mkSiteSP(s, p0);
                    const c = polyline.createCurve();
                    intEdge.curve = c;
                    intEdge.edge.underlyingPolyline = polyline;
                    offset = dx;
                    if (intEdge.edge.label != null) {
                        offset += intEdge.edge.label.width;
                        const center = (intEdge.edge.label.center = new Point(c.value((c.parStart + c.parEnd) / 2).x + intEdge.labelWidth / 2, anchor.y));
                        const del = new Point(intEdge.edge.label.width / 2, intEdge.edge.label.height / 2);
                        const box = Rectangle.mkPP(center.add(del), center.sub(del));
                        intEdge.edge.label.boundingBox = box;
                    }
                    Arrowhead.trimSplineAndCalculateArrowheadsII(intEdge.edge.edgeGeometry, intEdge.edge.source.boundaryCurve, intEdge.edge.target.boundaryCurve, c, false);
                }
            }
        }
    }
    createSplineForNonSelfEdge(es, optimizeShortEdges) {
        if (es.LayerEdges != null) {
            this.drawSplineBySmothingThePolyline(es, optimizeShortEdges);
            if (!es.IsVirtualEdge) {
                es.updateEdgeLabelPosition(this.Database.Anchors);
                Arrowhead.trimSplineAndCalculateArrowheadsII(es.edge.edgeGeometry, es.edge.source.boundaryCurve, es.edge.target.boundaryCurve, es.curve, true);
            }
        }
    }
    drawSplineBySmothingThePolyline(edgePath, optimizeShortEdges) {
        const scalc = new SmoothedPolylineCalculator(edgePath, this.Database.Anchors, this.OriginalGraph, this.settings, this.LayerArrays, this.ProperLayeredGraph, this.Database);
        const spline = scalc.getSpline(optimizeShortEdges);
        if (edgePath.reversed) {
            edgePath.curve = spline.reverse();
            edgePath.underlyingPolyline = scalc.Reverse().GetPolyline;
        }
        else {
            edgePath.curve = spline;
            edgePath.underlyingPolyline = scalc.GetPolyline;
        }
    }
    // void UpdateEdgeLabelPosition(LayerEdge[][] list, int i) {
    //     IntEdge e;
    //     int labelNodeIndex;
    //     if (Engine.GetLabelEdgeAndVirtualNode(list, i, out e, out labelNodeIndex)) {
    //         UpdateLabel(e, labelNodeIndex, db.Anchors);
    //     }
    // }
    static UpdateLabel(e, anchor) {
        let labelSide = null;
        if (anchor.labelIsToTheRightOfTheSpline) {
            e.label.center = new Point(anchor.x + anchor.rightAnchor / 2, anchor.y);
            labelSide = LineSegment.mkPP(e.labelBBox.leftTop, e.labelBBox.leftBottom);
        }
        else if (anchor.labelIsToTheLeftOfTheSpline) {
            e.label.center = new Point(anchor.x - anchor.leftAnchor / 2, anchor.y);
            labelSide = LineSegment.mkPP(e.labelBBox.rightTop, e.labelBBox.rightBottom);
        }
        const segmentInFrontOfLabel = Routing.GetSegmentInFrontOfLabel(e.curve, e.label.center.y);
        if (segmentInFrontOfLabel == null) {
            return;
        }
        if (Curve.getAllIntersections(e.curve, Curve.polyFromBox(e.labelBBox), false).length == 0) {
            const t = {
                curveClosestPoint: undefined,
                labelSideClosest: undefined,
            };
            if (Routing.FindClosestPoints(t, segmentInFrontOfLabel, labelSide)) {
                // shift the label if needed
                Routing.ShiftLabel(e, t);
            }
            else {
                // assume that the distance is reached at the ends of labelSideClosest
                const u = segmentInFrontOfLabel.closestParameter(labelSide.start);
                const v = segmentInFrontOfLabel.closestParameter(labelSide.end);
                if (segmentInFrontOfLabel.value(u).sub(labelSide.start).length < segmentInFrontOfLabel.value(v).sub(labelSide.end).length) {
                    t.curveClosestPoint = segmentInFrontOfLabel.value(u);
                    t.labelSideClosest = labelSide.start;
                }
                else {
                    t.curveClosestPoint = segmentInFrontOfLabel.value(v);
                    t.labelSideClosest = labelSide.end;
                }
                Routing.ShiftLabel(e, t);
            }
        }
    }
    static ShiftLabel(e, t) {
        const w = e.lineWidth / 2;
        const shift = t.curveClosestPoint.sub(t.labelSideClosest);
        const shiftLength = shift.length;
        //    SugiyamaLayoutSettings.Show(e.Curve, shiftLength > 0 ? new LineSegment(curveClosestPoint, labelSideClosest) : null, PolyFromBox(e.LabelBBox));
        if (shiftLength > w) {
            e.label.center = e.label.center.add(shift.div(shiftLength * (shiftLength - w)));
        }
    }
    static FindClosestPoints(t, segmentInFrontOfLabel, labelSide) {
        const di = Curve.minDistWithinIntervals(segmentInFrontOfLabel, labelSide, segmentInFrontOfLabel.parStart, segmentInFrontOfLabel.parEnd, labelSide.parStart, labelSide.parEnd, (segmentInFrontOfLabel.parStart + segmentInFrontOfLabel.parEnd) / 2, (labelSide.parStart + labelSide.parEnd) / 2);
        if (di) {
            t.curveClosestPoint = di.aX;
            t.labelSideClosest = di.bX;
            return true;
        }
        return false;
    }
    static GetSegmentInFrontOfLabel(edgeCurve, labelY) {
        if (edgeCurve instanceof Curve) {
            const curve = edgeCurve;
            for (const seg of curve.segs) {
                if ((seg.start.y - labelY) * (seg.end.y - labelY) <= 0) {
                    return seg;
                }
            }
        }
        else {
            /*Assert.assert(false)*/
        }
        // not implemented
        return null;
    }
    static GetNodeKind(vertexOffset, edgePath) {
        return vertexOffset == 0 ? NodeKind.Top : vertexOffset < edgePath.count ? NodeKind.Internal : NodeKind.Bottom;
    }
}
//# sourceMappingURL=routing.js.map