import { RectangleNode, mkRectangleNode, CreateRectangleNodeOnEnumeration, CreateRectangleNodeOnListOfNodes } from './RectangleNode';
//  A search tree for rapid lookup of T objects keyed by rectangles inside a given rectangular region
//  It is very similar to "R-TREES. A DYNAMIC INDEX STRUCTURE FOR SPATIAL SEARCHING" by Antonin Guttman
//  Create the query tree for a given enumerable of T keyed by Rectangles
export function mkRTree(rectsAndData) {
    return new RTree(CreateRectangleNodeOnEnumeration(rectsAndData.map(([k, v]) => mkRectangleNode(v, k))));
}
function TransferFromSibling(parent, sibling) {
    parent.UserData = sibling.UserData;
    parent.Left = sibling.Left;
    parent.Right = sibling.Right;
    parent.Count--;
    parent.irect = sibling.irect;
}
function UpdateParent(parent) {
    for (let node = parent.Parent; node != null; node = node.Parent) {
        node.Count--;
        node.irect = node.Left.irect.add_rect(node.Right.irect);
    }
}
function RebuildUnderNodeWithoutLeaf(nodeForRebuild, leaf) {
    /*Assert.assert(leaf.IsLeaf)*/
    /*Assert.assert(!nodeForRebuild.IsLeaf)*/
    const t = new Array();
    for (const n of nodeForRebuild.GetAllLeafNodes()) {
        if (n != leaf) {
            t.push(n);
        }
    }
    const newNode = CreateRectangleNodeOnEnumeration(t);
    nodeForRebuild.Count = newNode.Count;
    nodeForRebuild.Left = newNode.Left;
    nodeForRebuild.Right = newNode.Right;
    nodeForRebuild.irect = newNode.Left.irect.add_rect(newNode.Right.irect);
}
function FindTopUnbalancedNode(node) {
    for (let parent = node.Parent; parent != null; parent = parent.Parent)
        if (!Balanced(parent))
            return parent;
    return null;
}
function Balanced(rectangleNode) {
    return 2 * rectangleNode.Left.Count >= rectangleNode.Right.Count && 2 * rectangleNode.Right.Count >= rectangleNode.Left.Count;
}
function NumberOfIntersectedIsLessThanBoundOnNode(node, rect, t, conditionFunc) {
    /*Assert.assert(t.bound > 0)*/
    if (!node.irect.intersects_rect(rect))
        return true;
    if (node.IsLeaf) {
        if (conditionFunc(node.UserData))
            return --t.bound != 0;
        return true;
    }
    return (NumberOfIntersectedIsLessThanBoundOnNode(node.Left, rect, t, conditionFunc) &&
        NumberOfIntersectedIsLessThanBoundOnNode(node.Right, rect, t, conditionFunc));
}
export class RTree {
    //  Create a query tree for a given root node
    constructor(rootNode) {
        this._rootNode = rootNode;
    }
    // Removes everything from the tree
    // <
    Clear() {
        this.RootNode = null;
    }
    NumberOfIntersectedIsLessThanBound(rect, bound, conditionFunc) {
        return NumberOfIntersectedIsLessThanBoundOnNode(this._rootNode, rect, { bound: bound }, conditionFunc);
    }
    get RootNode() {
        return this._rootNode;
    }
    set RootNode(value) {
        this._rootNode = value;
    }
    *GetAllLeaves() {
        if (this._rootNode != null && this.Count > 0) {
            for (const l of this._rootNode.GetAllLeaves())
                yield l;
        }
    }
    //  The number of data elements of the tree (number of leaf nodes)
    get Count() {
        return this._rootNode == null ? 0 : this._rootNode.Count;
    }
    Add(key, value) {
        this.AddNode(mkRectangleNode(value, key));
    }
    AddNode(node) {
        if (this._rootNode == null)
            this._rootNode = node;
        else if (this.Count <= 2)
            this._rootNode = CreateRectangleNodeOnListOfNodes(Array.from(this._rootNode.GetAllLeafNodes()).concat([node]));
        else
            this.AddNodeToTreeRecursive(node, this._rootNode);
    }
    Rebuild() {
        this._rootNode = CreateRectangleNodeOnEnumeration(Array.from(this._rootNode.GetAllLeafNodes()));
    }
    AddNodeToTreeRecursive(newNode, existingNode) {
        if (existingNode.IsLeaf) {
            existingNode.Left = mkRectangleNode(existingNode.UserData, existingNode.irect);
            existingNode.Right = newNode;
            existingNode.Count = 2;
        }
        else {
            existingNode.Count++;
            let leftBox;
            let rightBox;
            if (2 * existingNode.Left.Count < existingNode.Right.Count) {
                // keep the balance
                this.AddNodeToTreeRecursive(newNode, existingNode.Left);
                existingNode.Left.irect = existingNode.Left.irect.add_rect(newNode.irect);
            }
            else if (2 * existingNode.Right.Count < existingNode.Left.Count) {
                // keep the balance
                this.AddNodeToTreeRecursive(newNode, existingNode.Right);
                existingNode.Right.irect = existingNode.Right.irect.add_rect(newNode.irect);
            }
            else {
                // decide basing on the boxes
                leftBox = existingNode.Left.irect.add_rect(newNode.irect);
                const delLeft = leftBox.area - existingNode.Left.irect.area;
                rightBox = existingNode.Right.irect.add_rect(newNode.irect);
                const delRight = rightBox.area - existingNode.Right.irect.area;
                if (delLeft < delRight) {
                    this.AddNodeToTreeRecursive(newNode, existingNode.Left);
                    existingNode.Left.irect = leftBox;
                }
                else if (delLeft > delRight) {
                    this.AddNodeToTreeRecursive(newNode, existingNode.Right);
                    existingNode.Right.irect = rightBox;
                }
                else {
                    // the deltas are the same; add to the smallest
                    if (leftBox.area < rightBox.area) {
                        this.AddNodeToTreeRecursive(newNode, existingNode.Left);
                        existingNode.Left.irect = leftBox;
                    }
                    else {
                        this.AddNodeToTreeRecursive(newNode, existingNode.Right);
                        existingNode.Right.irect = rightBox;
                    }
                }
            }
        }
        existingNode.irect = existingNode.Left.irect.add_rect(existingNode.Right.irect);
    }
    GetAllIntersecting(queryRegion) {
        return this._rootNode == null || this.Count == 0 ? [] : Array.from(this._rootNode.GetNodeItemsIntersectingRectangle(queryRegion));
    }
    OneIntersecting(queryRegion) {
        if (this._rootNode == null || this.Count == 0) {
            return;
        }
        const ret = this._rootNode.FirstIntersectedNode(queryRegion);
        if (ret == null) {
            return;
        }
        return { intersectedLeaf: ret.UserData };
    }
    //  Get all leaf nodes with rectangles intersecting the specified rectangular region
    GetAllLeavesIntersectingRectangle(queryRegion) {
        return this._rootNode == null || this.Count == 0 ? [] : this._rootNode.GetLeafRectangleNodesIntersectingRectangle(queryRegion);
    }
    //  Does minimal work to determine if any objects of the tree intersect with the query region
    IsIntersecting(queryRegion) {
        if (this._rootNode == null || this.Count == 0)
            return false;
        for (const n of this._rootNode.GetNodeItemsIntersectingRectangle(queryRegion)) {
            return true;
        }
        return false;
    }
    //  return true iff there is a node with the rectangle and UserData that equals to the parameter "userData"
    Contains(rectangle, userData) {
        if (this._rootNode == null) {
            return false;
        }
        for (const node of this._rootNode.GetLeafRectangleNodesIntersectingRectangle(rectangle)) {
            if (node.UserData == userData)
                return true;
        }
        return false;
    }
    Remove(rectangle, userData) {
        if (this._rootNode == null) {
            return;
        }
        let ret = undefined;
        for (const node of this._rootNode.GetLeafRectangleNodesIntersectingRectangle(rectangle)) {
            if (node.UserData == userData) {
                ret = node;
            }
        }
        if (ret == null) {
            return;
        }
        if (this.RootNode.Count == 1) {
            this.RootNode = null;
        }
        else {
            this.RemoveLeaf(ret);
        }
        return ret.UserData;
    }
    RemoveLeaf(leaf) {
        /*Assert.assert(leaf.IsLeaf)*/
        const unbalancedNode = FindTopUnbalancedNode(leaf);
        if (unbalancedNode != null) {
            RebuildUnderNodeWithoutLeaf(unbalancedNode, leaf);
            UpdateParent(unbalancedNode);
        }
        else {
            //replace the parent with the sibling and update bounding boxes and counts
            const parent = leaf.Parent;
            if (parent == null) {
                /*Assert.assert(this._rootNode == leaf)*/
                this._rootNode = new RectangleNode();
            }
            else {
                TransferFromSibling(parent, leaf.IsLeftChild ? parent.Right : parent.Left);
                UpdateParent(parent);
            }
        }
        //   Assert.assert(TreeIsCorrect(RootNode));
    }
    UnbalancedNode(node) {
        for (let parent = node.Parent; parent != null; parent = parent.Parent) {
            if (!Balanced(parent)) {
                return parent;
            }
        }
        return null;
    }
}
//# sourceMappingURL=RTree.js.map