import { Queue } from 'queue-typescript';
import { PolylinePoint } from '../../../math/geometry/polylinePoint';
import { Assert } from '../../../utils/assert';
import { PointMap } from '../../../utils/PointMap';
import { PointSet } from '../../../utils/PointSet';
import { getOrientationOf3Vectors } from './MetroGraphData';
import { PathFixer } from './PathFixer';
export class FlipSwitcher {
    constructor(metroGraphData) {
        this.polylineToEdgeGeom = new Map();
        this.pathsThroughPoints = new PointMap();
        this.interestingPoints = new PointSet();
        this.metroGraphData = metroGraphData;
    }
    get Polylines() {
        return Array.from(this.polylineToEdgeGeom.keys());
    }
    Run() {
        // TimeMeasurer.DebugOutput("switching flips...");
        this.Init();
        this.SwitchFlips();
    }
    Init() {
        for (const e of this.metroGraphData.Edges) {
            this.polylineToEdgeGeom.set(e.curve, e);
        }
        for (const poly of this.Polylines) {
            this.RegisterPolylinePointInPathsThrough(poly.polylinePoints());
        }
    }
    RegisterPolylinePointInPathsThrough(points) {
        for (const pp of points)
            this.RegisterPolylinePointInPathsThroughP(pp);
    }
    RegisterPolylinePointInPathsThroughP(pp) {
        addToPointMap(this.pathsThroughPoints, pp.point, pp);
    }
    UnregisterPolylinePointsInPathsThrough(points) {
        for (const pp of points)
            this.UnregisterPolylinePointInPathsThrough(pp);
    }
    UnregisterPolylinePointInPathsThrough(pp) {
        removeFromPointMap(this.pathsThroughPoints, pp.point, pp);
    }
    SwitchFlips() {
        const queued = new Set(this.Polylines);
        const queue = new Queue();
        for (const e of this.Polylines) {
            queue.enqueue(e);
        }
        while (queue.length > 0) {
            const initialPolyline = queue.dequeue();
            queued.delete(initialPolyline);
            const changedPolyline = this.ProcessPolyline(initialPolyline);
            if (changedPolyline != null) {
                // we changed both polylines
                if (!queued.has(initialPolyline)) {
                    queued.add(initialPolyline);
                    queue.enqueue(initialPolyline);
                }
                if (!queued.has(changedPolyline)) {
                    queued.add(changedPolyline);
                    queue.enqueue(changedPolyline);
                }
            }
        }
    }
    ProcessPolyline(polyline) {
        const departed = new Map();
        for (let pp = polyline.startPoint.next; pp != null; pp = pp.next) {
            this.FillDepartedPolylinePoints(pp, departed);
            // find returning
            for (const polyPoint of this.pathsThroughPoints.get(pp.point)) {
                const departingPP = departed.get(polyPoint.polyline);
                if (departingPP) {
                    if (this.ProcessFlip(pp, departingPP)) {
                        return polyPoint.polyline;
                    }
                    departed.delete(polyPoint.polyline);
                }
            }
        }
        return null;
    }
    FillDepartedPolylinePoints(pp, departed) {
        const prevPoint = pp.prev.point;
        for (const polyPoint of this.pathsThroughPoints.get(prevPoint)) {
            if (!this.IsNeighbor(polyPoint, pp)) {
                Assert.assert(!departed.has(polyPoint.polyline));
                departed.set(polyPoint.polyline, polyPoint);
            }
        }
    }
    ProcessFlip(flipStartPP, flipEndPP) {
        // temporary switching polylines of the same width only
        // need to check capacities here
        const polyA = flipStartPP.polyline;
        const polyB = flipEndPP.polyline;
        const flipStart = flipStartPP.point;
        const flipEnd = flipEndPP.point;
        if (this.polylineToEdgeGeom.get(polyA).lineWidth != this.polylineToEdgeGeom.get(polyB).lineWidth) {
            return false;
        }
        /*    polyA.init()
        polyB.init()
        FlipSwitcher.debugCount++
    
        const da = DebugCurve.mkDebugCurveTWCI(100, 0.2, 'Red', polyA)
        const aStart = DebugCurve.mkDebugCurveTWCI(100, 0.2, 'Red', CurveFactory.mkCircle(10, polyA.start))
        const aEnd = DebugCurve.mkDebugCurveTWCI(100, 0.2, 'Red', CurveFactory.mkCircle(5, polyA.end))
        const db = DebugCurve.mkDebugCurveTWCI(100, 0.1, 'Blue', polyB)
        const bStart = DebugCurve.mkDebugCurveTWCI(100, 0.1, 'Blue', CurveFactory.mkCircle(4, polyB.start))
        const bEnd = DebugCurve.mkDebugCurveTWCI(100, 0.1, 'Blue', CurveFactory.mkCircle(2, polyB.end))
        SvgDebugWriter.dumpDebugCurves('/tmp/dadb_.svg', [da, db, aStart, aEnd, bStart, bEnd])
        throw new Error()
    */
        let pts = this.FindPointsOnPolyline(polyA, flipStart, flipEnd);
        const aFirst = pts[0];
        const aLast = pts[1];
        const forwardOrderA = pts[2];
        pts = this.FindPointsOnPolyline(polyB, flipStart, flipEnd);
        const bFirst = pts[0];
        const bLast = pts[1];
        const forwardOrderB = pts[2];
        Assert.assert(this.PolylinePointsAreInForwardOrder(aFirst, aLast) == forwardOrderA);
        Assert.assert(this.PolylinePointsAreInForwardOrder(bFirst, bLast) == forwardOrderB);
        // 0 - the end
        // 1 - not intersect
        // 2 - intersect
        const rel1 = this.FindRelationOnFirstPoint(aFirst, bFirst, forwardOrderA, forwardOrderB);
        const rel2 = this.FindRelationOnLastPoint(aLast, bLast, forwardOrderA, forwardOrderB);
        // no intersection on both sides
        if (rel1 != 2 && rel2 != 2) {
            return false;
        }
        // can't swap to reduce crossings
        if (rel1 == 1 || rel2 == 1) {
            return false;
        }
        // unregister
        this.UnregisterPolylinePointsInPathsThrough(polyA.polylinePoints());
        this.UnregisterPolylinePointsInPathsThrough(polyB.polylinePoints());
        // switching
        this.Swap(aFirst, bFirst, aLast, bLast, forwardOrderA, forwardOrderB);
        // register back
        this.RegisterPolylinePointInPathsThrough(polyA.polylinePoints());
        this.RegisterPolylinePointInPathsThrough(polyB.polylinePoints());
        this.RegisterInterestingPoint(aFirst.point);
        this.RegisterInterestingPoint(aLast.point);
        this.numberOfReducedCrossings++;
        return true;
    }
    FindPointsOnPolyline(polyline, first, last) {
        let ppFirst;
        let ppLast;
        for (let pp = polyline.startPoint; pp != null; pp = pp.next) {
            if (ppFirst == null) {
                if (pp.point.equal(first)) {
                    if (ppLast != null) {
                        return [pp, ppLast, false];
                    }
                    ppFirst = pp;
                }
                else {
                    if (ppLast == null && pp.point.equal(last)) {
                        ppLast = pp;
                    }
                }
            }
            else {
                // got ppFirst arleady
                if (pp.point.equal(last)) {
                    return [ppFirst, pp, true];
                }
            }
        }
        Assert.assert(false);
    }
    PolylinePointsAreInForwardOrder(u, v) {
        Assert.assert(u.polyline == v.polyline);
        for (let p = u; p != null; p = p.next) {
            if (p == v) {
                return true;
            }
        }
        return false;
    }
    Next(p, forwardOrder) {
        return forwardOrder ? p.next : p.prev;
    }
    Prev(p, forwardOrder) {
        return forwardOrder ? p.prev : p.next;
    }
    FindRelationOnFirstPoint(aFirst, bFirst, forwardOrderA, forwardOrderB) {
        Assert.assert(aFirst.point.equal(bFirst.point));
        const a0 = aFirst;
        const b0 = bFirst;
        while (true) {
            const prevA = this.Prev(aFirst, forwardOrderA);
            const prevB = this.Prev(bFirst, forwardOrderB);
            if (prevA == null || prevB == null) {
                Assert.assert(prevA == null && prevB == null);
                return 0;
            }
            if (!prevA.point.equal(prevB.point)) {
                break;
            }
            aFirst = prevA;
            bFirst = prevB;
        }
        return this.PolylinesIntersect(a0, b0, aFirst, bFirst, forwardOrderA, forwardOrderB);
    }
    FindRelationOnLastPoint(aLast, bLast, forwardOrderA, forwardOrderB) {
        Assert.assert(aLast.point.equal(bLast.point));
        const a0 = aLast;
        const b0 = bLast;
        while (true) {
            const nextA = this.Next(aLast, forwardOrderA);
            const nextB = this.Next(bLast, forwardOrderB);
            if (nextA == null || nextB == null) {
                Assert.assert(nextA == null && nextB == null);
                return 0;
            }
            if (!nextA.point.equal(nextB.point)) {
                break;
            }
            aLast = nextA;
            bLast = nextB;
        }
        while (this.Next(aLast, forwardOrderA).point.equal(this.Prev(bLast, forwardOrderB).point)) {
            aLast = this.Next(aLast, forwardOrderA);
            bLast = this.Prev(bLast, forwardOrderB);
        }
        return this.PolylinesIntersect(aLast, bLast, a0, b0, forwardOrderA, forwardOrderB);
    }
    PolylinesIntersect(a0, b0, a1, b1, forwardOrderA, forwardOrderB) {
        const a0p = this.Prev(a0, forwardOrderA);
        const a0n = this.Next(a0, forwardOrderA);
        const a1n = this.Next(a1, forwardOrderA);
        const a1p = this.Prev(a1, forwardOrderA);
        const b0n = this.Next(b0, forwardOrderB);
        const b1p = this.Prev(b1, forwardOrderB);
        if (a0.point.equal(a1.point)) {
            const bs = a0.point;
            const left0 = getOrientationOf3Vectors(a1p.point.sub(bs), b1p.point.sub(bs), a0n.point.sub(bs));
            const left1 = getOrientationOf3Vectors(a1p.point.sub(bs), b0n.point.sub(bs), a0n.point.sub(bs));
            Assert.assert(left0 != 0 && left1 != 0);
            return left0 == left1 ? 1 : 2;
        }
        else {
            const left0 = getOrientationOf3Vectors(a0p.point.sub(a0.point), a0n.point.sub(a0.point), b0n.point.sub(a0.point));
            const left1 = getOrientationOf3Vectors(a1n.point.sub(a1.point), b1p.point.sub(a1.point), a1p.point.sub(a1.point));
            Assert.assert(left0 != 0 && left1 != 0);
            return left0 == left1 ? 1 : 2;
        }
    }
    Swap(aFirst, bFirst, aLast, bLast, forwardOrderA, forwardOrderB) {
        const intermediateAPoints = this.GetRangeOnPolyline(this.Next(aFirst, forwardOrderA), aLast, forwardOrderA);
        const intermediateBPoints = this.GetRangeOnPolyline(this.Next(bFirst, forwardOrderB), bLast, forwardOrderB);
        // changing a
        this.ChangePolylineSegment(aFirst, aLast, forwardOrderA, intermediateBPoints);
        // changing b
        this.ChangePolylineSegment(bFirst, bLast, forwardOrderB, intermediateAPoints);
        // resulting polylines might have cycles
        PathFixer.RemoveSelfCyclesFromPolyline(aFirst.polyline);
        Assert.assert(this.PolylineIsOK(aFirst.polyline));
        PathFixer.RemoveSelfCyclesFromPolyline(bFirst.polyline);
        Assert.assert(this.PolylineIsOK(bFirst.polyline));
    }
    ChangePolylineSegment(aFirst, aLast, forwardOrderA, intermediateBPoints) {
        let curA = aFirst;
        for (const b of intermediateBPoints) {
            const newp = PolylinePoint.mkFromPoint(b.point);
            newp.polyline = curA.polyline;
            if (forwardOrderA) {
                newp.prev = curA;
                curA.next = newp;
            }
            else {
                newp.next = curA;
                curA.prev = newp;
            }
            curA = newp;
        }
        if (forwardOrderA) {
            curA.next = aLast;
            aLast.prev = curA;
        }
        else {
            curA.prev = aLast;
            aLast.next = curA;
        }
    }
    GetRangeOnPolyline(start, end, forwardOrder) {
        const res = new Array();
        for (let pp = start; pp != end; pp = this.Next(pp, forwardOrder)) {
            res.push(pp);
        }
        return res;
    }
    IsNeighbor(a, b) {
        return (a.prev != null && a.prev.point.equal(b.point)) || (a.next != null && a.next.point.equal(b.point));
    }
    RegisterInterestingPoint(p) {
        if (!this.interestingPoints.has(p)) {
            this.interestingPoints.add(p);
        }
    }
    GetChangedHubs() {
        return this.interestingPoints;
    }
    NumberOfReducedCrossings() {
        return this.numberOfReducedCrossings;
    }
    PolylineIsOK(poly) {
        const pointsToPP = new PointSet();
        for (let pp = poly.startPoint; pp != null; pp = pp.next) {
            if (pp == poly.startPoint) {
                if (pp.prev != null) {
                    return false;
                }
            }
            else if (pp.prev.next != pp) {
                return false;
            }
            if (pp == poly.endPoint) {
                if (pp.next != null) {
                    return false;
                }
            }
            else if (pp.next.prev != pp) {
                return false;
            }
            if (pointsToPP.has(pp.point)) {
                return false;
            }
            pointsToPP.add(pp.point);
        }
        if (poly.startPoint.prev != null) {
            return false;
        }
        if (poly.endPoint.next != null) {
            return false;
        }
        return true;
    }
}
FlipSwitcher.debugCount = 0;
function addToPointMap(pointMap, point, pp) {
    let s = pointMap.get(point);
    if (!s) {
        s = new Set();
        pointMap.set(point, s);
    }
    s.add(pp);
}
function removeFromPointMap(pathsThroughPoints, point, pp) {
    const s = pathsThroughPoints.get(point);
    if (!s)
        return;
    s.delete(pp);
    if (s.size == 0) {
        pathsThroughPoints.deleteP(point);
    }
}
//# sourceMappingURL=FlipSwitcher.js.map