import { Point } from '../../..';
import { GeomConstants } from '../../../math/geometry';
import { GenericBinaryHeapPriorityQueue } from '../../../structs/genericBinaryHeapPriorityQueue';
import { compareNumbers } from '../../../utils/compare';
//  Calculates node radii with 'water algorithm'
export class HubRadiiCalculator {
    constructor(metroGraphData, bundlingSettings) {
        this.metroGraphData = metroGraphData;
        this.bundlingSettings = bundlingSettings;
    }
    //  calculate node radii with fixed hubs
    CreateNodeRadii() {
        // set radii to zero
        for (const v of this.metroGraphData.VirtualNodes()) {
            v.Radius = 0;
            v.cachedIdealRadius = HubRadiiCalculator.CalculateIdealHubRadiusWithNeighborsMBS(this.metroGraphData, this.bundlingSettings, v);
        }
        // TimeMeasurer.DebugOutput("Initial cost of radii: " + Cost());
        this.GrowHubs(false);
        // maximally use free space
        this.GrowHubs(true);
        // TimeMeasurer.DebugOutput("Optimized cost of radii: " + Cost());
        // ensure radii are not zero
        for (const v of this.metroGraphData.VirtualNodes()) {
            v.Radius = Math.max(v.Radius, this.bundlingSettings.MinHubRadius);
        }
    }
    //  Grow hubs
    GrowHubs(useHalfEdgesAsIdealR) {
        const queue = new GenericBinaryHeapPriorityQueue(compareNumbers);
        for (const v of this.metroGraphData.VirtualNodes()) {
            queue.Enqueue(v, -this.CalculatePotential(v, useHalfEdgesAsIdealR));
        }
        let progress = false;
        // choose a hub with the greatest potential
        while (!queue.IsEmpty()) {
            const t = { priority: 0 };
            const v = queue.DequeueAndGetPriority(t);
            if (t.priority >= 0) {
                break;
            }
            // grow the hub
            if (this.TryGrowHub(v, useHalfEdgesAsIdealR)) {
                queue.Enqueue(v, -this.CalculatePotential(v, useHalfEdgesAsIdealR));
                progress = true;
            }
        }
        return progress;
    }
    TryGrowHub(v, useHalfEdgesAsIdealR) {
        const allowedRadius = this.CalculateAllowedHubRadius(v);
        //Debug.Assert(allowedRadius > 0);
        if (v.Radius >= allowedRadius)
            return false;
        const idealR = useHalfEdgesAsIdealR
            ? HubRadiiCalculator.CalculateIdealHubRadiusWithAdjacentEdges(this.bundlingSettings, v)
            : v.cachedIdealRadius;
        //Debug.Assert(idealR > 0);
        if (v.Radius >= idealR)
            return false;
        const step = 0.05;
        let delta = step * (idealR - v.Radius);
        if (delta < 1.0)
            delta = 1.0;
        const newR = Math.min(v.Radius + delta, allowedRadius);
        if (newR <= v.Radius)
            return false;
        v.Radius = newR;
        return true;
    }
    CalculatePotential(v, useHalfEdgesAsIdealR) {
        const idealR = useHalfEdgesAsIdealR
            ? HubRadiiCalculator.CalculateIdealHubRadiusWithAdjacentEdges(this.bundlingSettings, v)
            : v.cachedIdealRadius;
        if (idealR <= v.Radius) {
            return 0;
        }
        return (idealR - v.Radius) / idealR;
    }
    //  Returns the maximal possible radius of the node
    CalculateAllowedHubRadius(node) {
        let r = this.bundlingSettings.MaxHubRadius;
        //adjacent nodes
        for (const adj of node.Neighbors) {
            const dist = adj.Position.sub(node.Position).length;
            //Debug.Assert(dist - 0.05 * (node.Radius + adj.Radius) + 1 >= node.Radius + adj.Radius);
            r = Math.min(r, dist / 1.05 - adj.Radius);
        }
        //TODO: still we can have two intersecting hubs for not adjacent nodes
        //obstacles
        const minimalDistance = this.metroGraphData.tightIntersections.GetMinimalDistanceToObstacles(node, node.Position, r);
        if (minimalDistance < r)
            r = minimalDistance - 0.001;
        return Math.max(r, 0.1);
    }
    //  Returns the ideal radius of the hub
    static CalculateIdealHubRadius(metroGraphData, bundlingSettings, node) {
        let r = 1.0;
        for (const adj of node.Neighbors) {
            const width = metroGraphData.GetWidthSSN(adj, node, bundlingSettings.EdgeSeparation);
            const nr = width / 2.0 + bundlingSettings.EdgeSeparation;
            r = Math.max(r, nr);
        }
        r = Math.min(r, 2 * bundlingSettings.MaxHubRadius);
        return r;
    }
    //  Returns the ideal radius of the hub
    static CalculateIdealHubRadiusWithNeighborsMBS(metroGraphData, bundlingSettings, node) {
        return HubRadiiCalculator.CalculateIdealHubRadiusWithNeighborsMBNP(metroGraphData, bundlingSettings, node, node.Position);
    }
    //  Returns the ideal radius of the hub
    static CalculateIdealHubRadiusWithNeighborsMBNP(metroGraphData, bundlingSettings, node, newPosition) {
        let r = HubRadiiCalculator.CalculateIdealHubRadius(metroGraphData, bundlingSettings, node);
        if (node.Neighbors.length > 1) {
            const adjNodes = node.Neighbors;
            // there must be enough space between neighbor bundles
            for (let i = 0; i < adjNodes.length; i++) {
                const adj = adjNodes[i];
                const nextAdj = adjNodes[(i + 1) % adjNodes.length];
                r = Math.max(r, HubRadiiCalculator.GetMinRadiusForTwoAdjacentBundles(r, node, newPosition, adj, nextAdj, metroGraphData, bundlingSettings));
            }
        }
        r = Math.min(r, 2 * bundlingSettings.MaxHubRadius);
        return r;
    }
    //  Returns the ideal radius of the hub
    static CalculateIdealHubRadiusWithAdjacentEdges(bundlingSettings, node) {
        let r = bundlingSettings.MaxHubRadius;
        for (const adj of node.Neighbors)
            r = Math.min(r, node.Position.sub(adj.Position).length / 2);
        return r;
    }
    static GetMinRadiusForTwoAdjacentBundles(r, node, nodePosition, adj0, adj1, metroGraphData, bundlingSettings) {
        const w0 = metroGraphData.GetWidthSSN(node, adj0, bundlingSettings.EdgeSeparation);
        const w1 = metroGraphData.GetWidthSSN(node, adj1, bundlingSettings.EdgeSeparation);
        return HubRadiiCalculator.GetMinRadiusForTwoAdjacentBundlesNPPPNNB(r, nodePosition, adj0.Position, adj1.Position, w0, w1, bundlingSettings);
    }
    //  Radius we need to draw to separate adjacent bundles ab and ac
    static GetMinRadiusForTwoAdjacentBundlesNPPPNNB(r, a, b, c, widthAB, widthAC, bundlingSettings) {
        if (widthAB < GeomConstants.distanceEpsilon || widthAC < GeomConstants.distanceEpsilon) {
            return r;
        }
        let angle = Point.anglePCP(b, a, c);
        angle = Math.min(angle, Math.PI * 2 - angle);
        if (angle < GeomConstants.distanceEpsilon) {
            return 2 * bundlingSettings.MaxHubRadius;
        }
        if (angle >= Math.PI / 2) {
            return r * 1.05;
        }
        // find the intersection point of two bundles
        const sina = Math.sin(angle);
        const cosa = Math.cos(angle);
        const aa = widthAB / (4 * sina);
        const bb = widthAC / (4 * sina);
        let d = 2 * Math.sqrt(aa * aa + (bb * bb + 2 * (aa * (bb * cosa))));
        d = Math.min(d, 2 * bundlingSettings.MaxHubRadius);
        d = Math.max(d, r);
        return d;
    }
}
//# sourceMappingURL=HubRadiiCalculator.js.map