import { Point } from '../../math/geometry/point';
import { Rectangle } from '../../math/geometry/rectangle';
import { AllPairsDistances } from './AllPairsDistances';
import { PivotDistances } from './PivotDistances';
import { Transform } from './Transform';
import { Algorithm } from '../../utils/algorithm';
import { MultidimensionalScaling } from './MultiDimensionalScaling';
import { OptimalRectanglePacking } from '../../math/geometry/rectanglePacking/OptimalRectanglePacking';
import { GTreeOverlapRemoval } from '../GTreeOverlapRemoval/GTreeOverlapRemoval';
//  Class for graph layout with multidimensional scaling.
export class MdsGraphLayout extends Algorithm {
    //  Constructs the multidimensional scaling algorithm.
    constructor(settings, geometryGraph, cancelToken, length) {
        super(cancelToken);
        this.settings = settings;
        this.graph = geometryGraph;
        this.length = length;
    }
    //  Executes the algorithm
    run() {
        this.LayoutConnectedGraphWithMds();
        this.SetGraphBoundingBox();
    }
    SetGraphBoundingBox() {
        this.graph.boundingBox = this.graph.pumpTheBoxToTheGraphWithMargins(this.settings.NodeSeparation / 2);
    }
    //  Scales a configuration such that the average edge length in the drawing
    //  equals the average of the given edge lengths.
    static ScaleToAverageEdgeLength(g, x, y, length) {
        const index = new Map();
        let c = 0;
        for (const node of g.shallowNodes()) {
            index.set(node, c);
            c++;
        }
        let avgLength = 0;
        let avgSum = 0;
        for (const edge of g.edges()) {
            const i = index.get(edge.source);
            const j = index.get(edge.target);
            avgSum += Math.sqrt(Math.pow(x[i] - x[j], 2) + Math.pow(y[i] - y[j], 2));
            avgLength += length(edge);
        }
        if (avgLength > 0) {
            avgSum /= avgLength;
        }
        if (avgSum > 0) {
            for (let i = 0; i < x.length; i++) {
                x[i] /= avgSum;
                y[i] /= avgSum;
            }
        }
    }
    //  Layouts a connected graph with Multidimensional Scaling, using
    //  shortest-path distances as Euclidean target distances.
    static LayoutGraphWithMds(geometryGraph, settings, arrays, length) {
        arrays.x = new Array(geometryGraph.shallowNodeCount);
        arrays.y = new Array(geometryGraph.shallowNodeCount);
        if (arrays.x.length == 0) {
            return;
        }
        if (arrays.x.length == 1) {
            arrays.x[0] = arrays.y[0] = 0;
            return;
        }
        const k = Math.min(settings.PivotNumber, geometryGraph.shallowNodeCount);
        const iter = settings.GetNumberOfIterationsWithMajorization(geometryGraph.shallowNodeCount);
        const exponent = settings.Exponent;
        const pivotArray = new Array(k);
        const pivotDistances = new PivotDistances(geometryGraph, pivotArray, length);
        pivotDistances.run();
        const c = pivotDistances.Result;
        MultidimensionalScaling.LandmarkClassicalScaling(c, arrays, pivotArray);
        MdsGraphLayout.ScaleToAverageEdgeLength(geometryGraph, arrays.x, arrays.y, length);
        if (iter > 0) {
            const apd = new AllPairsDistances(geometryGraph, length);
            apd.run();
            const d = apd.Result;
            const w = MultidimensionalScaling.ExponentialWeightMatrix(d, exponent);
            //  MultidimensionalScaling.DistanceScaling(d, x, y, w, iter);
            MultidimensionalScaling.DistanceScalingSubset(d, arrays.x, arrays.y, w, iter);
        }
    }
    LayoutConnectedGraphWithMds() {
        const arrays = { x: [], y: [] };
        MdsGraphLayout.LayoutGraphWithMds(this.graph, this.settings, arrays, this.length);
        if (this.settings.RotationAngle != 0) {
            Transform.Rotate(arrays.x, arrays.y, this.settings.RotationAngle);
        }
        let index = 0;
        for (const node of this.graph.shallowNodes()) {
            if (node.boundingBox) {
                node.center = new Point(arrays.x[index] * this.settings.ScaleX, arrays.y[index] * this.settings.ScaleY);
            }
            index++;
        }
        if (this.settings.RemoveOverlaps) {
            GTreeOverlapRemoval.RemoveOverlaps(Array.from(this.graph.shallowNodes()), this.settings.NodeSeparation);
        }
        this.graph.pumpTheBoxToTheGraphWithMargins(this.settings.NodeSeparation / 2);
    }
    ScaleNodes(nodes, scale) {
        for (const node of nodes) {
            node.center = node.center.mul(scale);
        }
    }
    //   static UpdateTree(tree: RectangleNode<Node, Point>) {
    //     if (tree.IsLeaf) {
    //       tree.irect = tree.UserData.BoundingBox
    //     } else {
    //       MdsGraphLayout.UpdateTree(tree.Left)
    //       MdsGraphLayout.UpdateTree(tree.Right)
    //       tree.rectangle = tree.Left.rectangle
    //       tree.rectangle.Add(tree.Right.rectangle)
    //     }
    //   }
    //   static NumberOfHits(
    //     numberOfChecks: number,
    //     random: Random,
    //     tree: RectangleNode<Node, Point>,
    //     maxNumberOfHits: number,
    //   ): number {
    //     //  var l = new Array<Point>();
    //     let numberOfHits = 0
    //     for (let i = 0; i < numberOfChecks; i++) {
    //       const point: Point = MdsGraphLayout.RandomPointFromBox(
    //         random,
    //         <Rectangle>tree.rectangle,
    //       )
    //       //    l.Add(point);
    //       HitTestBehavior.Stop
    //       null
    //       numberOfHits++
    //       if (numberOfHits == maxNumberOfHits) {
    //         return maxNumberOfHits
    //       }
    //     }
    //     // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(Getdc(tree, l));
    //     return numberOfHits
    //   }
    //   static BuildNodeTree(nodes: IList<Node>): RectangleNode<Node, Point> {
    //     return RectangleNode.CreateRectangleNodeOnEnumeration(
    //       nodes.Select(() => {}, new RectangleNode<Node, Point>(n, n.BoundingBox)),
    //     )
    //   }
    //   static RandomPointFromBox(random: Random, boundingBox: Rectangle): Point {
    //     const x = random.NextDouble()
    //     const y = random.NextDouble()
    //     const p = new Point(
    //       boundingBox.left + boundingBox.width * x,
    //       boundingBox.bottom + boundingBox.height * y,
    //     )
    //     return p
    //   }
    //  Pack the given graph components to the specified aspect ratio
    static PackGraphs(graphs, settings) {
        if (graphs.length == 0) {
            return Rectangle.mkEmpty();
        }
        if (graphs.length == 1)
            return graphs[0].boundingBox;
        const rectangles = graphs.map((g) => g.boundingBox);
        const originalLeftBottoms = new Array();
        for (const g of graphs) {
            originalLeftBottoms.push({ g: g, lb: g.boundingBox.leftBottom.clone() });
        }
        const packing = new OptimalRectanglePacking(rectangles, settings.PackingAspectRatio);
        packing.run();
        for (const { g, lb } of originalLeftBottoms) {
            const delta = g.boundingBox.leftBottom.sub(lb);
            g.translate(delta);
        }
        return new Rectangle({
            left: 0,
            bottom: 0,
            right: packing.PackedWidth,
            top: packing.PackedHeight,
        });
    }
}
//# sourceMappingURL=MDSGraphLayout.js.map