//  The implementation follows
// "Fast and Simple Horizontal Coordinate Assignment" of Ulrik Brandes and Boris K�opf
//  The paper has two serious bugs that this code resolves.
import { TopologicalSort } from '../../math/graphAlgorithms/topologicalSort';
import { mkGraphOnEdgesN } from '../../structs/basicGraphOnEdges';
import { IntPair } from '../../utils/IntPair';
import { IntPairSet } from '../../utils/IntPairSet';
import { PolyIntEdge } from './polyIntEdge';
export class XCoordsWithAlignment {
    // // private constructor
    // // <
    constructor(layerArrays, layeredGraph, nOfOriginalVs, anchorsP, ns) {
        // four arrays for four different direction combinations
        this.xCoords = new Array(4);
        this.la = layerArrays;
        this.graph = layeredGraph;
        this.nOfOriginalVertices = nOfOriginalVs;
        this.nOfVertices = this.graph.NodeCount;
        this.markedEdges = new IntPairSet();
        this.h = this.la.Layers.length;
        this.root = new Array(this.nOfVertices);
        this.align = new Array(this.nOfVertices);
        //  this.sink = new int[nOfVertices];
        //  this.shift = new double[nOfVertices];
        this.anchors = anchorsP;
        this.nodeSep = ns;
    }
    get CurrentEnumRightUp() {
        return (this.LR ? 0 : 1) + 2 * (this.BT ? 0 : 1);
    }
    //  Returns true if v is a virtual vertex
    IsVirtual(v) {
        return v >= this.nOfOriginalVertices;
    }
    Source(edge) {
        return this.BT ? edge.Source : edge.Target;
    }
    Target(edge) {
        return this.BT ? edge.Target : edge.Source;
    }
    static CalculateXCoordinates(layerArrays, layeredGraph, nOfOriginalVs, anchors, nodeSeparation) {
        const x = new XCoordsWithAlignment(layerArrays, layeredGraph, nOfOriginalVs, anchors, nodeSeparation);
        x.Calculate();
    }
    Calculate() {
        this.SortInAndOutEdges();
        this.RightUpSetup();
        this.CalcBiasedAlignment();
        this.LeftUpSetup();
        this.CalcBiasedAlignment();
        this.RightDownSetup();
        this.CalcBiasedAlignment();
        this.LeftDownSetup();
        this.CalcBiasedAlignment();
        this.HorizontalBalancing();
    }
    // We need to find a median of a vertex neighbors from a specific layer. That is, if we have a vertex v and edges (v,coeff), (v,side1), (v,cornerC)
    //  going down, and X[coeff]<X[side1]<X[cornerC], then side1 is the median.
    // There is an algorithm that finds the median with expected linear number of steps,
    // see for example http://www.ics.uci.edu/~eppstein/161/960125.html. However, I think we are better off
    // with sorting, since we are taking median at least twice.
    // Notice, that the sorting should be done only for original vertices since dummy vertices
    // have only one incoming edge and one outcoming edge.
    // Consider here reusing the sorting that comes from the ordering step,
    // if it is not broken by layer insertions.
    SortInAndOutEdges() {
        this.FillLowMedians();
        this.FillUpperMedins();
    }
    FillUpperMedins() {
        this.upperMedians = new Array(this.graph.NodeCount);
        for (let i = 0; i < this.graph.NodeCount; i++) {
            this.FillUpperMediansForNode(i);
        }
    }
    CompareByX(a, b) {
        return this.la.x[a] - this.la.x[b];
    }
    FillUpperMediansForNode(i) {
        let count = this.graph.InEdgesCount(i);
        if (count > 0) {
            const predecessors = new Array(count);
            count = 0;
            for (const e of this.graph.InEdges(i)) {
                predecessors[count++] = e.Source;
            }
            predecessors.sort((a, b) => this.CompareByX(a, b));
            const m = Math.floor(count / 2);
            if (m * 2 == count) {
                this.upperMedians[i] = new IntPair(predecessors[m - 1], predecessors[m]);
            }
            else {
                this.upperMedians[i] = predecessors[m];
            }
        }
        else {
            this.upperMedians[i] = -1;
        }
    }
    FillLowMedians() {
        this.lowMedians = new Array(this.graph.NodeCount);
        for (let i = 0; i < this.graph.NodeCount; i++) {
            this.FillLowMediansForNode(i);
        }
    }
    FillLowMediansForNode(i) {
        let count = this.graph.OutEdgesCount(i);
        if (count > 0) {
            const successors = new Array(count);
            count = 0;
            for (const e of this.graph.OutEdges(i)) {
                successors[count++] = e.Target;
            }
            successors.sort((a, b) => this.CompareByX(a, b));
            const m = Math.floor(count / 2);
            if (m * 2 == count) {
                this.lowMedians[i] = new IntPair(successors[m - 1], successors[m]);
            }
            else {
                this.lowMedians[i] = successors[m];
            }
        }
        else {
            this.lowMedians[i] = -1;
        }
    }
    HorizontalBalancing() {
        let leastWidthAssignment = -1;
        const a = new Array(4);
        const b = new Array(4);
        let leastWidth = Number.MAX_VALUE;
        for (let i = 0; i < 4; i++) {
            const t = { a: 0, b: 0 };
            this.AssignmentBounds(i, t);
            a[i] = t.a;
            b[i] = t.b;
            const w = b[i] - a[i];
            if (w < leastWidth) {
                leastWidthAssignment = i;
                leastWidth = w;
            }
        }
        for (let i = 0; i < 4; i++) {
            let delta;
            if (XCoordsWithAlignment.IsLeftMostAssignment(i)) {
                delta = a[leastWidthAssignment] - a[i];
            }
            else {
                delta = b[leastWidthAssignment] - b[i];
            }
            this.x = this.xCoords[i];
            if (delta != 0) {
                for (let j = 0; j < this.nOfVertices; j++) {
                    this.x[j] = this.x[j] + delta;
                }
            }
        }
        const arr = new Array(4);
        for (let v = 0; v < this.nOfVertices; v++) {
            arr[0] = this.xCoords[0][v];
            arr[1] = this.xCoords[1][v];
            arr[2] = this.xCoords[2][v];
            arr[3] = this.xCoords[3][v];
            arr.sort();
            this.anchors[v].x = (arr[1] + arr[2]) / 2;
        }
        //     Layout.ShowDataBase(dataBase);
    }
    static IsLeftMostAssignment(i) {
        return i == 0 || i == 2;
    }
    AssignmentBounds(i, t) {
        if (this.nOfVertices == 0) {
            t.a = 0;
            t.b = 0;
        }
        else {
            this.x = this.xCoords[i];
            t.a = t.b = this.x[0];
            for (let j = 1; j < this.nOfVertices; j++) {
                const r = this.x[j];
                if (r < t.a) {
                    t.a = r;
                }
                else if (r > t.b) {
                    t.b = r;
                }
            }
        }
    }
    CalcBiasedAlignment() {
        this.ConflictElimination();
        this.Align();
    }
    LeftUpSetup() {
        this.LR = false;
        this.BT = true;
    }
    LeftDownSetup() {
        this.LR = false;
        this.BT = false;
    }
    RightDownSetup() {
        this.LR = true;
        this.BT = false;
    }
    RightUpSetup() {
        this.LR = true;
        this.BT = true;
    }
    //  The code is written as if we go left up, but in fact the settings define the directions.
    //
    //  We need to create a subgraph for alignment:
    //  where no edge segments intersect, and every vertex has
    //  at most one incoming and at most one outcoming edge.
    //  This function marks edges to resolve conflicts with only one inner segment.
    //  An inner segment is a segment between two dummy nodes.
    //  We mark edges that later will not participate in the alignment.
    //  Inner segments are preferred to other ones. So, in a conflict with one inner and one
    //  non-inner edges we leave the inner edge to participate in the alignment.
    //  At the moment we mark as not participating both of the two intersecting inner segments
    ConflictElimination() {
        this.RemoveMarksFromEdges();
        this.MarkConflictingEdges();
    }
    // iterator
    *UpperEdgeMedians(target) {
        const medians = this.BT ? this.upperMedians[target] : this.lowMedians[target];
        const isIp = !(typeof medians === 'number');
        if (isIp) {
            const ip = medians;
            if (this.LR) {
                yield ip.x;
                yield ip.y;
            }
            else {
                yield ip.y;
                yield ip.x;
            }
        }
        else {
            const i = medians;
            if (i >= 0) {
                yield i;
            }
        }
    }
    //  here we eliminate all constraints
    MarkConflictingEdges() {
        let i = this.LowerOf(0, this.h - 1);
        const lowest = i;
        const upperBound = this.UpperOf(0, this.h - 1);
        const nextBelowUpperBound = this.NextLower(upperBound);
        // our top layer has index h-1, our bottom layer has index 0
        // inner segments can appear only between layers with indices i+1 and i where i>0 and i<h-1
        for (; this.IsBelow(i, upperBound); i = this.NextUpper(i)) {
            if (this.IsBelow(lowest, i) && this.IsBelow(i, nextBelowUpperBound)) {
                this.ConflictsWithAtLeastOneInnerEdgeForALayer(i);
            }
        }
    }
    //  parameterized next upper
    NextUpper(i) {
        return this.BT ? i + 1 : i - 1;
    }
    //  parameterized next lower
    NextLower(i) {
        return this.BT ? i - 1 : i + 1;
    }
    //  parameterize highest of two numbers
    UpperOf(i, j) {
        return this.BT ? Math.max(i, j) : Math.min(i, j);
    }
    //  parameterized lowest of a pair
    LowerOf(i, j) {
        return this.BT ? Math.min(i, j) : Math.max(i, j);
    }
    //  returns parameterized below
    IsBelow(i, j) {
        return this.BT ? i < j : j < i;
    }
    //  returns the "parameterized" left of the two positions
    LeftMost(pos0, pos1) {
        return this.LR ? Math.min(pos0, pos1) : Math.max(pos0, pos1);
    }
    //  returns the "parameterized" right of the two positions
    RightMost(pos0, pos1) {
        return this.LR ? Math.max(pos0, pos1) : Math.min(pos0, pos1);
    }
    //  Return true if i is to the left or equal to pos in a "parameterized" fasion
    IsNotRightFrom(i, pos) {
        return this.LR ? i <= pos : pos <= i;
    }
    //  Parameterized left relation
    IsLeftFrom(i, j) {
        return this.LR ? i < j : j < i;
    }
    //  parameterized next right
    NextRight(i) {
        return this.LR ? i + 1 : i - 1;
    }
    //  parameterized next left
    NextLeft(i) {
        return this.LR ? i - 1 : i + 1;
    }
    // // Eliminates conflicts with at least one inner edge inside of one layer
    // // <
    ConflictsWithAtLeastOneInnerEdgeForALayer(layerIndex) {
        if (layerIndex >= 0 && layerIndex < this.la.Layers.length) {
            const lowerLayer = this.la.Layers[layerIndex];
            let innerEdge = null;
            // start looking for the first inner edge from the left of lowerLayer
            let targetPos = this.LeftMost(0, lowerLayer.length - 1);
            const lastTargetPos = this.RightMost(0, lowerLayer.length - 1);
            for (; this.IsNotRightFrom(targetPos, lastTargetPos) && innerEdge == null; targetPos = this.NextRight(targetPos)) {
                innerEdge = this.InnerEdgeByTarget(lowerLayer[targetPos]);
            }
            // now targetPos points to the right of the innerEdge target at lowerLayer
            if (innerEdge != null) {
                const positionOfInnerEdgeSource = this.Pos(this.Source(innerEdge));
                // We are still not in the main loop.
                // We mark conflicting edges with targets to the left of targetPos,
                // That of course means
                // that the sources of conflicting edges lie to the right of Source(innerEdge)
                for (let j = this.LeftMost(0, lowerLayer.length - 1); this.IsLeftFrom(j, targetPos); j = this.NextRight(j)) {
                    for (const ie of this.InEdges(lowerLayer[j])) {
                        if (this.IsLeftFrom(positionOfInnerEdgeSource, this.Pos(this.Source(ie)))) {
                            this.MarkEdge(ie);
                        }
                    }
                }
                let innerSourcePos = this.Pos(this.Source(innerEdge));
                // starting the main loop
                while (this.IsNotRightFrom(targetPos, lastTargetPos)) {
                    // Now we look for the next inner edge in the alignment to the right of the current innerEdge,
                    // and we mark the conflicts later. Marking the conflicts later makes sense.
                    // We would have to go through positions between innerEdge and newInnerEdge targets
                    // again anyway to resolve conflicts with not inner edges and newInnerEdge
                    const newInnerEdge = this.AlignmentToTheRightOfInner(lowerLayer, targetPos, positionOfInnerEdgeSource);
                    targetPos = this.NextRight(targetPos);
                    if (newInnerEdge != null) {
                        const newInnerSourcePos = this.Pos(this.Source(newInnerEdge));
                        this.MarkEdgesBetweenInnerAndNewInnerEdges(lowerLayer, innerEdge, newInnerEdge, innerSourcePos, newInnerSourcePos);
                        innerEdge = newInnerEdge;
                        innerSourcePos = newInnerSourcePos;
                    }
                }
                // look for conflicting edges with targets to the right from the target of innerEdge
                for (let k = this.NextRight(this.Pos(this.Target(innerEdge))); this.IsNotRightFrom(k, lastTargetPos); k = this.NextRight(k)) {
                    for (const ie of this.InEdges(lowerLayer[k])) {
                        if (this.IsLeftFrom(this.Pos(this.Source(ie)), this.Pos(this.Source(innerEdge)))) {
                            this.MarkEdge(ie);
                        }
                    }
                }
            }
        }
    }
    InEdgeOfVirtualNode(v) {
        return this.BT ? this.graph.InEdgeOfVirtualNode(v) : this.graph.OutEdgeOfVirtualNode(v);
    }
    InEdges(v) {
        return this.BT ? this.graph.InEdges(v) : this.graph.OutEdges(v);
    }
    // // This function marks conflicting edges with targets positioned between innerEdge and newInnerEdge targets.
    // // <
    MarkEdgesBetweenInnerAndNewInnerEdges(lowerLayer, innerEdge, newInnerEdge, innerEdgeSourcePos, newInnerEdgeSourcePos) {
        let u = this.NextRight(this.Pos(this.Target(innerEdge)));
        for (; this.IsLeftFrom(u, this.Pos(this.Target(newInnerEdge))); u = this.NextRight(u)) {
            for (const ie of this.InEdges(lowerLayer[u])) {
                const ieSourcePos = this.Pos(this.Source(ie));
                if (this.IsLeftFrom(ieSourcePos, innerEdgeSourcePos)) {
                    this.MarkEdge(ie);
                }
                else if (this.IsLeftFrom(newInnerEdgeSourcePos, ieSourcePos)) {
                    this.MarkEdge(ie);
                }
            }
        }
    }
    // // Returns the inner non-conflicting edge incoming into i-th position
    // // of the layer or null if there is no such edge
    // // <
    AlignmentToTheRightOfInner(lowLayer, i, posInnerSource) {
        const numOfInEdges = this.NumberOfInEdges(lowLayer[i]);
        if (numOfInEdges == 1) {
            let ie = null;
            for (const e of this.InEdges(lowLayer[i])) {
                ie = e;
            }
            if (this.IsInnerEdge(ie) && this.IsLeftFrom(posInnerSource, this.Pos(ie.Source))) {
                return ie;
            }
            return null;
        }
        return null;
    }
    NumberOfInEdges(v) {
        return this.BT ? this.graph.InEdgesCount(v) : this.graph.OutEdgesCount(v);
    }
    Pos(v) {
        return this.la.x[v];
    }
    InnerEdgeByTarget(v) {
        if (this.IsVirtual(v)) {
            const ie = this.InEdgeOfVirtualNode(v);
            // there is exactly one edge entering in to the dummy node
            if (this.IsVirtual(this.Source(ie))) {
                return ie;
            }
        }
        return null;
    }
    IsInnerEdge(e) {
        return this.IsVirtual(e.Source) && this.IsVirtual(e.Target);
    }
    RemoveMarksFromEdges() {
        this.markedEdges.clear();
    }
    // Calculate the alignment based on the marked edges and greedily resolve the remaining conflicts on the fly, without marking
    Align() {
        this.CreateBlocks();
        this.AssignCoordinatesByLongestPath();
    }
    AssignCoordinatesByLongestPath() {
        this.x = this.xCoords[this.CurrentEnumRightUp] = new Array(this.nOfVertices);
        // create the graph first
        const edges = new Array();
        for (let v = 0; v < this.nOfVertices; v++) {
            if (v == this.root[v]) {
                //v is a root
                let w = v; //w will be running over the block
                do {
                    const rn = { neighbor: 0 };
                    if (this.TryToGetRightNeighbor(w, rn))
                        edges.push(new PolyIntEdge(v, this.root[rn.neighbor], null));
                    w = this.align[w];
                } while (w != v);
            }
        }
        const blockGraph = mkGraphOnEdgesN(edges, this.nOfVertices);
        // sort the graph in the topological order
        const topoSort = TopologicalSort.getOrderOnGraph(blockGraph);
        // start placing the blocks according to the order
        for (const v of topoSort) {
            if (v == this.root[v]) {
                let vx = 0;
                let vIsLeftMost = true;
                let w = v;
                // w is running over the block
                do {
                    const wLn = { neighbor: 0 };
                    if (this.TryToGetLeftNeighbor(w, wLn)) {
                        if (vIsLeftMost) {
                            vx = this.x[this.root[wLn.neighbor]] + this.DeltaBetweenVertices(wLn.neighbor, w);
                            vIsLeftMost = false;
                        }
                        else {
                            vx = this.RightMost(vx, this.x[this.root[wLn.neighbor]] + this.DeltaBetweenVertices(wLn.neighbor, w));
                        }
                    }
                    w = this.align[w];
                } while (w != v);
                this.x[v] = vx;
            }
        }
        // push the roots of the graph maximally to the right
        for (const v of topoSort) {
            if (v == this.root[v]) {
                if (blockGraph.inEdges[v].length == 0) {
                    let w = v;
                    // w runs over the block
                    let xLeftMost = this.RightMost(XCoordsWithAlignment.infinity * -1, XCoordsWithAlignment.infinity);
                    const xl = xLeftMost;
                    do {
                        const wRn = { neighbor: 0 };
                        if (this.TryToGetRightNeighbor(w, wRn)) {
                            xLeftMost = this.LeftMost(xLeftMost, this.x[this.root[wRn.neighbor]] - this.DeltaBetweenVertices(w, wRn.neighbor));
                        }
                        w = this.align[w];
                    } while (w != v);
                    if (xl != xLeftMost) {
                        this.x[v] = xLeftMost;
                    }
                }
            }
        }
        for (let v = 0; v < this.nOfVertices; v++) {
            if (v != this.root[v]) {
                this.x[v] = this.x[this.root[v]];
            }
        }
    }
    //  returns true is u has a right neighbor on its layer
    TryToGetRightNeighbor(u, t) {
        const neighborPos = this.NextRight(this.Pos(u));
        const layer = this.la.Layers[this.la.y[u]];
        if (neighborPos >= 0 && neighborPos < layer.length) {
            t.neighbor = layer[neighborPos];
            return true;
        }
        else {
            return false;
        }
    }
    //  returns true is u has a right neighbor on its layer
    TryToGetLeftNeighbor(u, t) {
        const neighborPos = this.NextLeft(this.Pos(u));
        const layer = this.la.Layers[this.la.y[u]];
        if (neighborPos >= 0 && neighborPos < layer.length) {
            t.neighbor = layer[neighborPos];
            return true;
        }
        else {
            return false;
        }
    }
    //  Organizes the vertices into blocks. A block is a maximal path in the alignment subgraph.
    //  The alignment is defined by array align. Every vertex is connected to the top vertex of
    //  the block by using root array. The alignment is cyclic. If we start from a root vertex v and
    //  apply align then we return to v at some point.
    CreateBlocks() {
        for (let v = 0; v < this.nOfVertices; v++) {
            this.root[v] = this.align[v] = v;
        }
        const lowBound = this.LowerOf(0, this.h - 1);
        // i points to the last layer before the highest one
        for (let i = this.NextLower(this.UpperOf(0, this.h - 1)); !this.IsBelow(i, lowBound); i = this.NextLower(i)) {
            const layer = this.la.Layers[i];
            let r = this.LeftMost(-1, this.la.Layers[this.NextUpper(i)].length);
            // We align vertices of the layer above the i-th one only if their positions are
            // to the right of r. This moves us forward on the layer above the current and resolves the conflicts.
            const rightBound = this.RightMost(0, layer.length - 1);
            for (let k = this.LeftMost(0, layer.length - 1); this.IsNotRightFrom(k, rightBound); k = this.NextRight(k)) {
                const vk = layer[k];
                for (const upperNeighborOfVk of this.UpperEdgeMedians(vk)) {
                    if (!this.IsMarked(vk, upperNeighborOfVk)) {
                        if (this.IsLeftFrom(r, this.Pos(upperNeighborOfVk))) {
                            this.align[upperNeighborOfVk] = vk;
                            this.root[vk] = this.root[upperNeighborOfVk];
                            this.align[vk] = this.root[upperNeighborOfVk];
                            r = this.Pos(upperNeighborOfVk);
                            break;
                            //  done with the alignement for vk
                        }
                    }
                }
            }
        }
    }
    IsMarked(source, target) {
        if (this.BT) {
            return this.markedEdges.hasxy(target, source);
        }
        else {
            return this.markedEdges.hasxy(source, target);
        }
    }
    MarkEdge(ie) {
        this.markedEdges.addNN(ie.Source, ie.Target);
    }
    //  Calculates the minimum separation between two neighboring vertices: if u is to the left of v on the same layer return positive
    //  number, otherwise negative.
    DeltaBetweenVertices(u, v) {
        let sign;
        if (this.Pos(u) > this.Pos(v)) {
            // swap u and v
            const t = u;
            u = v;
            v = t;
            sign = -1;
        }
        else {
            sign = 1;
        }
        return (this.anchors[u].rightAnchor + this.anchors[v].leftAnchor + this.nodeSep) * sign;
    }
}
//  Assigning xcoords starting from roots
XCoordsWithAlignment.infinity = 10000000;
//# sourceMappingURL=XCoordsWithAlignment.js.map