import { RectilinearEdgeRouter } from '../routing/rectilinear/RectilinearEdgeRouter';
import { GeomGraph, optimalPackingRunner } from './core/GeomGraph';
import { CurveFactory } from '../math/geometry/curveFactory';
import { Point } from '../math/geometry/point';
import { Edge } from '../structs/edge';
import { Graph, shallowConnectedComponents } from '../structs/graph';
import { GeomEdge } from './core/geomEdge';
import { SugiyamaLayoutSettings } from './layered/SugiyamaLayoutSettings';
import { LayeredLayout, MdsLayoutSettings } from '..';
import { PivotMDS } from './mds/PivotMDS';
import { EdgeRoutingMode } from '../routing/EdgeRoutingMode';
import { straightLineEdgePatcher } from '../routing/StraightLineEdges';
import { routeSplines } from '../routing/splineRouter';
import { EdgeRoutingSettings } from '../routing/EdgeRoutingSettings';
import { GeomObject } from './core/geomObject';
// function routeEdges(
//   geomG: GeomGraph,
//   edgeRoutingSettings: EdgeRoutingSettings,
//   cornerFitRadius = 3,
// ) {
//   if (edgeRoutingSettings.edgeRoutingMode != EdgeRoutingMode.Rectilinear) {
//     // TODO: enable other modes
//     routeStraightEdges(geomG)
//   } else {
//     if (edgeRoutingSettings.EdgeRoutingMode == EdgeRoutingMode.Rectilinear)
//       routeRectilinearEdges(geomG, edgeRoutingSettings.padding, cornerFitRadius)
//   }
// }
// function routeStraightEdges(geomG: GeomGraph) {
//   for (const u of geomG.deepNodes()) {
//     for (const e of u.outEdges()) {
//       if (e.curve == null) StraightLineEdges.RouteEdge(e, 0)
//     }
//     for (const e of u.selfEdges()) {
//       if (e.curve == null) StraightLineEdges.RouteEdge(e, 0)
//     }
//   }
// }
export function enforceLayoutSettings(geomGraph, ss) {
    if (!geomGraph.layoutSettings)
        geomGraph.layoutSettings = ss;
    for (const n of geomGraph.shallowNodes()) {
        if (n.isGraph()) {
            enforceLayoutSettings(n, geomGraph.layoutSettings);
        }
    }
}
export function layoutGeomGraph(geomGraph, cancelToken) {
    createSettingsIfNeeded(geomGraph);
    layoutGeomGraphDetailed(geomGraph, cancelToken, layoutEngine, edgeRouter, optimalPackingRunner);
    // end of the function body
    function createSettingsIfNeeded(geomGraph) {
        if (!geomGraph.layoutSettings) {
            geomGraph.layoutSettings = figureOutSettings(geomGraph);
        }
    }
    function layoutEngine(geomGraph, cancelToken) {
        createSettingsIfNeeded(geomGraph);
        if (geomGraph.layoutSettings instanceof SugiyamaLayoutSettings) {
            const ll = new LayeredLayout(geomGraph, geomGraph.layoutSettings, cancelToken);
            ll.run();
        }
        else if (geomGraph.layoutSettings instanceof MdsLayoutSettings) {
            const pivotMds = new PivotMDS(geomGraph, cancelToken, () => 1, geomGraph.layoutSettings);
            pivotMds.run();
        }
        else {
            throw Error('not implemented');
        }
    }
    function figureOutSettings(geomGraph) {
        if (geomGraph.shallowNodeCount < 200 || geomGraph.edgeCount < 200)
            return new SugiyamaLayoutSettings();
        return new MdsLayoutSettings();
    }
} // end of layoutGeomGraph
export function getEdgeRoutingSettingsFromAncestors(geomGraph) {
    do {
        if (geomGraph.layoutSettings && geomGraph.layoutSettings.edgeRoutingSettings) {
            return geomGraph.layoutSettings.edgeRoutingSettings;
        }
        const parent = geomGraph.graph.parent;
        if (parent) {
            geomGraph = GeomObject.getGeom(parent);
        }
        else {
            break;
        }
    } while (true);
    const ers = new EdgeRoutingSettings();
    ers.EdgeRoutingMode = EdgeRoutingMode.Spline;
    return ers;
}
export function edgeRouter(geomGraph, edgesToRoute, cancelToken) {
    const ers = getEdgeRoutingSettingsFromAncestors(geomGraph);
    if (ers.EdgeRoutingMode == EdgeRoutingMode.Rectilinear) {
        routeRectilinearEdges(geomGraph, edgesToRoute, cancelToken);
    }
    else if (ers.EdgeRoutingMode == EdgeRoutingMode.Spline || ers.EdgeRoutingMode == EdgeRoutingMode.SplineBundling) {
        routeSplines(geomGraph, edgesToRoute, cancelToken);
    }
    else if (ers.EdgeRoutingMode == EdgeRoutingMode.StraightLine) {
        straightLineEdgePatcher(geomGraph, edgesToRoute, cancelToken);
    }
}
// Lays out a GeomGraph, which is possibly disconnected and might have sub-graphs
export function layoutGeomGraphDetailed(geomG, cancelToken, layoutEngine, edgeRouter, packing, flipToScreenCoords = true) {
    if (geomG.graph.isEmpty()) {
        throw new Error('no empty subgraphs');
        // if there are no nodes in the graph, create a circle for its boundary and exit
        geomG.boundaryCurve = CurveFactory.mkCircle(10, new Point(0, 0));
        geomG.boundingBox = geomG.boundaryCurve.boundingBox;
        return;
    }
    const removedEdges = removeEdgesLeadingOutOfGraph();
    layoutShallowSubgraphs(geomG);
    const liftedEdges = createLiftedEdges(geomG.graph);
    const connectedGraphs = getConnectedComponents(geomG);
    // TODO: possible optimization for a connected graph
    layoutComps();
    liftedEdges.forEach((e) => {
        e[0].edge.remove();
        e[1].add();
    });
    connectedGraphs.forEach((g) => {
        for (const n of g.graph.shallowNodes)
            n.parent = geomG.graph;
    });
    const edgesToRoute = getUnroutedEdges(geomG);
    removedEdges.forEach((e) => e.add());
    edgeRouter(geomG, edgesToRoute, cancelToken);
    //the final touches
    if (geomG.graph.parent == null) {
        if (flipToScreenCoords) {
            geomG.FlipYAndMoveLeftTopToOrigin();
        }
    }
    // end of function body
    function getUnroutedEdges(g) {
        const edges = [];
        for (const n of g.deepNodes()) {
            for (const e of n.outEdges())
                if (e.curve == null)
                    edges.push(e);
            for (const e of n.selfEdges())
                if (e.curve == null)
                    edges.push(e);
        }
        return edges;
    }
    function layoutShallowSubgraphs(geomG) {
        for (const n of geomG.shallowNodes()) {
            if (n.isGraph()) {
                const g = n;
                layoutGeomGraphDetailed(g, cancelToken, layoutEngine, edgeRouter, packing);
                if (!g.graph.isEmpty()) {
                    const bb = g._boundingBox;
                    if (bb && !bb.isEmpty()) {
                        n.boundaryCurve = CurveFactory.mkRectangleWithRoundedCorners(bb.width, bb.height, Math.min(10, bb.width / 10), Math.min(10, bb.height / 10), bb.center);
                    }
                }
            }
        }
    }
    function removeEdgesLeadingOutOfGraph() {
        const ret = new Set();
        const graphUnderSurgery = geomG.graph;
        if (graphUnderSurgery.parent == null)
            return ret;
        for (const n of graphUnderSurgery.shallowNodes) {
            for (const e of n.outEdges) {
                if (graphUnderSurgery.liftNode(e.target) == null)
                    ret.add(e);
            }
            for (const e of n.inEdges) {
                if (graphUnderSurgery.liftNode(e.source) == null)
                    ret.add(e);
            }
        }
        for (const e of ret)
            e.remove();
        return ret;
    }
    function layoutComps() {
        if (connectedGraphs.length == 0)
            return;
        for (const cg of connectedGraphs) {
            layoutEngine(cg, cancelToken);
        }
        packing(geomG, connectedGraphs);
    }
} // end of layoutGeomGraphDetailed
// returns arrays of pairs (new lifted GeomEdge, existing Edge)
function createLiftedEdges(graph) {
    const liftedEdges = new Array();
    for (const u of graph.deepNodes) {
        const liftedU = graph.liftNode(u);
        if (liftedU == null)
            continue;
        for (const uv of u.outEdges.values()) {
            const v = uv.target;
            const liftedV = graph.liftNode(v);
            if (liftedV == null || (liftedU == u && liftedV == v) || liftedU == liftedV) {
                continue;
            }
            uv.remove();
            const newLiftedEdge = new Edge(liftedU, liftedV);
            const newLiftedGeomEdge = new GeomEdge(newLiftedEdge);
            liftedEdges.push([newLiftedGeomEdge, uv]);
        }
    }
    return liftedEdges;
}
function getConnectedComponents(parentGeomGraph) {
    const parentGraph = parentGeomGraph.graph;
    const comps = shallowConnectedComponents(parentGraph);
    const ret = [];
    let i = 0;
    for (const comp of comps) {
        const g = new Graph(parentGraph.id + i++);
        g.parent = parentGraph;
        const geomG = new GeomGraph(g);
        geomG.layoutSettings = parentGeomGraph.layoutSettings;
        for (const n of comp) {
            n.parent = g;
            g.addNode(n); // this changes the parent - should be restored to graph
        }
        ret.push(geomG);
    }
    return ret;
}
export function routeRectilinearEdges(geomG, edgesToRoute, cancelToken, nodePadding = 1, cornerFitRadius = 3) {
    const rr = RectilinearEdgeRouter.constructorGNNB(geomG, nodePadding, cornerFitRadius, true);
    rr.run();
}
//# sourceMappingURL=driver.js.map