//  Adjust current bundle-routing
import { Point } from '../../..';
import { Assert } from '../../../utils/assert';
import { random } from '../../../utils/random';
import { CostCalculator } from './CostCalculator';
import { IntersectionCache } from './IntersectionCache';
export class SimulatedAnnealing {
    constructor(metroGraphData, bundlingSettings) {
        this.stepsWithProgress = 0;
        this.metroGraphData = metroGraphData;
        this.bundlingSettings = bundlingSettings;
        this.costCalculator = new CostCalculator(this.metroGraphData, this.bundlingSettings);
        this.cache = new IntersectionCache(this.metroGraphData, this.bundlingSettings, this.costCalculator, this.metroGraphData.Cdt);
    }
    //  fix routing by simulated annealing algorithm
    static FixRouting(metroGraphData, bundlingSettings) {
        return this.FixRoutingMBP(metroGraphData, bundlingSettings, null);
    }
    static FixRoutingMBP(metroGraphData, bundlingSettings, changedPoints) {
        return new SimulatedAnnealing(metroGraphData, bundlingSettings).FixRoutingP(changedPoints);
    }
    //  Use constraint edge routing to reduce ink
    FixRoutingP(changedPoints) {
        this.stationsForOptimizations = this.GetStationsForOptimizations(changedPoints);
        this.cache.InitializeCostCache();
        let step = SimulatedAnnealing.MaxStep;
        let energy = Number.POSITIVE_INFINITY;
        let x = this.metroGraphData.VirtualNodes().map((v) => v.Position);
        let iteration = 0;
        while (iteration++ < SimulatedAnnealing.MaxIterations) {
            const coordinatesChanged = this.TryMoveNodes();
            //TimeMeasurer.DebugOutput("  #iter = " + iteration + " moved: " + cnt + "/" + metroGraphData.VirtualNodes().Count() + " step: " + step);
            if (iteration <= 1 && !coordinatesChanged)
                return false;
            if (!coordinatesChanged)
                break;
            const oldEnergy = energy;
            energy = CostCalculator.Cost(this.metroGraphData, this.bundlingSettings);
            //TimeMeasurer.DebugOutput("energy: " + energy);
            step = this.UpdateMaxStep(step, oldEnergy, energy);
            const oldX = x;
            x = this.metroGraphData.VirtualNodes().map((v) => v.Position);
            if (step < SimulatedAnnealing.MinStep || this.Converged(step, oldX, x)) {
                break;
            }
        }
        // TimeMeasurer.DebugOutput("SA completed after " + iteration + " iterations");
        return true;
    }
    GetStationsForOptimizations(changedPoints) {
        if (changedPoints == null) {
            return new Set(this.metroGraphData.VirtualNodes());
        }
        else {
            const result = new Set();
            for (const p of changedPoints) {
                const s = this.metroGraphData.PointToStations.get(p);
                if (s && !s.IsRealNode)
                    result.add(s);
            }
            return result;
        }
    }
    //  stop SA if relative changes are small
    Converged(step, oldx, newx) {
        // return false;
        let den = 0;
        let num = 0;
        for (let i = 0; i < oldx.length; i++) {
            num += oldx[i].sub(newx[i]).lengthSquared;
            den += oldx[i].lengthSquared;
        }
        const res = Math.sqrt(num / den);
        return res < SimulatedAnnealing.MinRelativeChange;
    }
    UpdateMaxStep(step, oldEnergy, newEnergy) {
        // cooling factor
        const T = 0.8;
        if (newEnergy + 1 < oldEnergy) {
            this.stepsWithProgress++;
            if (this.stepsWithProgress >= 5) {
                this.stepsWithProgress = 0;
                step = Math.min(SimulatedAnnealing.MaxStep, step / T);
            }
        }
        else {
            this.stepsWithProgress = 0;
            step *= T;
        }
        return step;
    }
    TryMoveNodes() {
        let coordinatesChanged = false;
        const movedStations = new Set();
        // for (var node of metroGraphData.VirtualNodes()) {
        for (const node of this.stationsForOptimizations) {
            if (this.TryMoveNode(node)) {
                Assert.assert(this.stationsForOptimizations.has(node));
                coordinatesChanged = true;
                movedStations.add(node);
                for (const adj of node.Neighbors) {
                    if (!adj.IsRealNode) {
                        movedStations.add(adj);
                    }
                }
            }
        }
        this.stationsForOptimizations = movedStations;
        return coordinatesChanged;
    }
    //  Move node to decrease the cost of the drawing
    //  Returns true iff position has changed
    TryMoveNode(node) {
        let direction = this.BuildDirection(node);
        if (direction.length == 0) {
            return false;
        }
        let stepLength = this.BuildStepLength(node, direction);
        if (stepLength < SimulatedAnnealing.MinStep) {
            // try random direction
            direction = RandomPoint();
            stepLength = this.BuildStepLength(node, direction);
            if (stepLength < SimulatedAnnealing.MinStep) {
                return false;
            }
        }
        const step = direction.mul(stepLength);
        const newPosition = node.Position.add(step);
        // can this happen?
        if (this.metroGraphData.PointToStations.has(newPosition)) {
            return false;
        }
        this.metroGraphData.MoveNode(node, newPosition);
        this.cache.UpdateCostCache(node);
        return true;
    }
    //  Calculate the direction to improve the ink function
    BuildDirection(node) {
        const forceInk = this.BuildForceForInk(node);
        const forcePL = this.BuildForceForPathLengths(node);
        const forceR = this.BuildForceForRadius(node);
        const forceBundle = this.BuildForceForBundle(node);
        const force = forceInk.add(forcePL.add(forceR.add(forceBundle)));
        if (force.length < 0.1) {
            return new Point(0, 0);
        }
        return force.normalize();
    }
    BuildStepLength(node, direction) {
        let stepLength = SimulatedAnnealing.MinStep;
        let costGain = this.CostGain(node, node.Position.add(direction.mul(stepLength)));
        if (costGain < 0.01) {
            return 0;
        }
        while (2 * stepLength <= SimulatedAnnealing.MaxStep) {
            const newCostGain = this.CostGain(node, node.Position.add(direction.mul(stepLength * 2)));
            if (newCostGain <= costGain) {
                break;
            }
            stepLength *= 2;
            costGain = newCostGain;
        }
        return stepLength;
    }
    //  Computes cost delta when moving the node
    //  the cost will be negative if a new position overlaps obstacles
    CostGain(node, newPosition) {
        const MInf = -12345678;
        const rGain = this.costCalculator.RadiusGain(node, newPosition);
        if (rGain < MInf) {
            return MInf;
        }
        const bundleGain = this.costCalculator.BundleGain(node, newPosition);
        if (bundleGain < MInf) {
            return MInf;
        }
        const inkGain = this.costCalculator.InkGain(node, newPosition);
        const plGain = this.costCalculator.PathLengthsGain(node, newPosition);
        return rGain + inkGain + plGain + bundleGain;
    }
    //  force to decrease ink
    BuildForceForInk(node) {
        //return new Point(0,0);
        let direction = new Point(0, 0);
        for (const adj of node.Neighbors) {
            const p = adj.Position.sub(node.Position);
            direction = direction.add(p.normalize());
        }
        //derivative
        const force = direction.mul(this.bundlingSettings.InkImportance);
        return force;
    }
    //  direction to decrease path lengths
    BuildForceForPathLengths(node) {
        // return new Point(0,0);
        let direction = new Point(0, 0);
        for (const mni of this.metroGraphData.MetroNodeInfosOfNode(node)) {
            const metroline = mni.Metroline;
            const u = mni.PolyPoint.next.point;
            const v = mni.PolyPoint.prev.point;
            const p1 = u.sub(node.Position);
            const p2 = v.sub(node.Position);
            direction = direction.add(p1.div(p1.length * metroline.IdealLength));
            direction = direction.add(p2.div(p2.length * metroline.IdealLength));
        }
        // derivative
        const force = direction.mul(this.bundlingSettings.PathLengthImportance);
        return force;
    }
    //  direction to increase radii
    BuildForceForRadius(node) {
        let direction = new Point(0, 0);
        const idealR = node.cachedIdealRadius;
        const t = { touchedObstacles: [] };
        const res = this.metroGraphData.looseIntersections.HubAvoidsObstaclesSPNBA(node, node.Position, idealR, t);
        Assert.assert(res); // problem here
        for (const d of t.touchedObstacles) {
            const dist = d[1].sub(node.Position).length;
            Assert.assert(dist <= idealR);
            const lforce = 2 * (1 - dist / idealR);
            const dir = node.Position.sub(d[1]).normalize();
            direction = direction.add(dir.mul(lforce));
        }
        // derivative
        const force = direction.mul(this.bundlingSettings.HubRepulsionImportance);
        return force;
    }
    //  direction to push a bundle away from obstacle
    BuildForceForBundle(node) {
        let direction = new Point(0, 0);
        for (const adj of node.Neighbors) {
            const idealWidth = this.metroGraphData.GetWidthSSN(node, adj, this.bundlingSettings.EdgeSeparation);
            const t = { closestDist: [] };
            const res = this.metroGraphData.cdtIntersections.BundleAvoidsObstacles(node, adj, node.Position, adj.Position, idealWidth / 2, t);
            if (!res) {
                // #if TEST_MSAGL&& TEST_MSAGL
                //                     HubDebugger.ShowHubs(metroGraphData, bundlingSettings, new LineSegment(node.Position, adj.Position));
                // #endif
            }
            //Debug.Assert(res);  //todo : still unsolved
            for (const d of t.closestDist) {
                const dist = d[0].sub(d[1]).length;
                // Debug.Assert(LessOrEqual(dist, idealWidth / 2));
                const lforce = 2.0 * (1.0 - dist / (idealWidth / 2));
                const dir = d[0].sub(d[1]).normalize().neg();
                direction = direction.add(dir.mul(lforce));
            }
        }
        //derivative
        const force = direction.mul(this.bundlingSettings.BundleRepulsionImportance);
        return force;
    }
}
SimulatedAnnealing.MaxIterations = 100;
SimulatedAnnealing.MaxStep = 50;
SimulatedAnnealing.MinStep = 1;
SimulatedAnnealing.MinRelativeChange = 0.0005;
function RandomPoint() {
    return new Point(1 + 2 * random(), 1 + 2 * random());
}
//# sourceMappingURL=SimulatedAnnealing.js.map