import { Stack } from 'stack-typescript';
import { HitTestBehavior } from './HitTestBehavior';
function DivideNodes(nodes, seed0, seed1, gr0, gr1, t) {
    const groupSplitThreshold = 2;
    for (let i = 0; i < nodes.length; i++) {
        if (i == seed0 || i == seed1)
            continue;
        // ReSharper disable InconsistentNaming
        const box0_ = t.box0.add_rect(nodes[i].irect);
        const delta0 = box0_.area - t.box0.area;
        const box1_ = t.box1.add_rect(nodes[i].irect);
        const delta1 = box1_.area - t.box1.area;
        // ReSharper restore InconsistentNaming
        //keep the tree roughly balanced
        if (gr0.length * groupSplitThreshold < gr1.length) {
            gr0.push(nodes[i]);
            t.box0 = box0_;
        }
        else if (gr1.length * groupSplitThreshold < gr0.length) {
            gr1.push(nodes[i]);
            t.box1 = box1_;
        }
        else if (delta0 < delta1) {
            gr0.push(nodes[i]);
            t.box0 = box0_;
        }
        else if (delta1 < delta0) {
            gr1.push(nodes[i]);
            t.box1 = box1_;
        }
        else if (t.box0.area < t.box1.area) {
            gr0.push(nodes[i]);
            t.box0 = box0_;
        }
        else {
            gr1.push(nodes[i]);
            t.box1 = box1_;
        }
    }
}
export function CreateRectangleNodeOnListOfNodes(nodes) {
    if (nodes.length == 0)
        return null;
    if (nodes.length == 1)
        return nodes[0];
    //Finding the seeds
    const t = { b0: nodes[0].irect, seed0: 1 };
    const seed1 = ChooseSeeds(nodes, t);
    //We have two seeds at hand. Build two groups.
    const gr0 = [];
    const gr1 = [];
    gr0.push(nodes[t.seed0]);
    gr1.push(nodes[seed1]);
    //divide nodes on two groups
    const p = { box0: nodes[t.seed0].irect, box1: nodes[seed1].irect };
    DivideNodes(nodes, t.seed0, seed1, gr0, gr1, p);
    const ret = mkRectangleNodeWithCount(nodes.length);
    ret.irect = p.box0.add_rect(p.box1);
    ret.Left = CreateRectangleNodeOnListOfNodes(gr0);
    ret.Right = CreateRectangleNodeOnListOfNodes(gr1);
    return ret;
}
function areaoftwo(a, b) {
    return a.add_rect(b).area;
}
function ChooseSeeds(nodes, t) {
    let area = areaoftwo(t.b0, nodes[t.seed0].irect);
    for (let i = 2; i < nodes.length; i++) {
        const area0 = areaoftwo(t.b0, nodes[i].irect);
        if (area0 > area) {
            t.seed0 = i;
            area = area0;
        }
    }
    //Got the first seed seed0
    //Now looking for a seed for the second group
    let seed1;
    //init seed1
    for (let i = 0; i < nodes.length; i++) {
        if (i != t.seed0) {
            seed1 = i;
            break;
        }
    }
    area = nodes[t.seed0].irect.add_rect(nodes[seed1].irect).area;
    //Now try to improve the second seed
    for (let i = 0; i < nodes.length; i++) {
        if (i == t.seed0)
            continue;
        const area1 = nodes[t.seed0].irect.add_rect(nodes[i].irect).area;
        if (area1 > area) {
            seed1 = i;
            area = area1;
        }
    }
    return seed1;
}
// calculates a tree based on the given nodes
export function CreateRectangleNodeOnEnumeration(nodes) {
    if (nodes == null)
        return null;
    return CreateRectangleNodeOnListOfNodes(nodes);
}
//calculates a tree based on the given nodes
export function CreateRectangleNodeOnData(dataEnumeration, rectangleDelegate) {
    if (dataEnumeration == null || rectangleDelegate == null)
        return null;
    const nodeList = Array.from(dataEnumeration).map((d) => mkRectangleNode(d, rectangleDelegate(d)));
    return CreateRectangleNodeOnListOfNodes(nodeList);
}
export function mkRectangleNodeWithCount(count) {
    const r = new RectangleNode();
    r.Count = count;
    return r;
}
export function mkRectangleNode(data, rect) {
    const r = new RectangleNode();
    r.UserData = data;
    r.irect = rect;
    r.Count = 1;
    return r;
}
// it should be a static function of a class but declaring it such creates an error
function VisitTreeStatic(rectangleNode, hitTest, hitRectangle) {
    if (rectangleNode.irect.intersects_rect(hitRectangle)) {
        if (hitTest(rectangleNode.UserData) == HitTestBehavior.Continue) {
            if (rectangleNode.Left != null) {
                // If rectangleNode.Left is not null, rectangleNode.Right won't be either.
                if (VisitTreeStatic(rectangleNode.Left, hitTest, hitRectangle) == HitTestBehavior.Continue &&
                    VisitTreeStatic(rectangleNode.Right, hitTest, hitRectangle) == HitTestBehavior.Continue) {
                    return HitTestBehavior.Continue;
                }
                return HitTestBehavior.Stop;
            }
            return HitTestBehavior.Continue;
        }
        return HitTestBehavior.Stop;
    }
    return HitTestBehavior.Continue;
}
// Represents a node containing a box and some user data.
// Is used of curve intersections routines.
export class RectangleNode {
    toString() {
        return this.IsLeaf ? this.Count.toString() + ' ' + this.UserData : this.Count.toString();
    }
    // false if it is an internal node and true if it is a leaf
    get IsLeaf() {
        return this.left == null; /*&& right==null*/
    } //if left is a null then right is also a null
    //
    get Left() {
        return this.left;
    }
    set Left(value) {
        if (this.left != null && this.left.Parent == this)
            this.left.Parent = null;
        this.left = value;
        if (this.left != null)
            this.left.Parent = this;
    }
    get Right() {
        return this.right;
    }
    set Right(value) {
        if (this.right != null && this.right.Parent == this)
            this.right.Parent = null;
        this.right = value;
        if (this.right != null)
            this.right.Parent = this;
    }
    get IsLeftChild() {
        /*Assert.assert(this.Parent != null)*/
        return this == this.Parent.Left;
    }
    // brings the first leaf which rectangle was hit and the delegate is happy with the object
    FirstHitNodePF(point, hitTestForPointDelegate) {
        var _a;
        if (this.irect.contains_point(point)) {
            if (this.IsLeaf) {
                if (hitTestForPointDelegate != null) {
                    return hitTestForPointDelegate(point, this.UserData) == HitTestBehavior.Stop ? this : null;
                }
                return this;
            }
            return (_a = this.Left.FirstHitNodePF(point, hitTestForPointDelegate)) !== null && _a !== void 0 ? _a : this.Right.FirstHitNodePF(point, hitTestForPointDelegate);
        }
        return null;
    }
    // brings the first leaf which rectangle was intersected
    FirstIntersectedNode(r) {
        var _a;
        if (r.intersects_rect(this.irect)) {
            if (this.IsLeaf)
                return this;
            return (_a = this.Left.FirstIntersectedNode(r)) !== null && _a !== void 0 ? _a : this.Right.FirstIntersectedNode(r);
        }
        return null;
    }
    FirstHitNodeWithPredicate(point, hitTest) {
        var _a;
        if (!this.irect.contains_point(point))
            return null;
        if (this.IsLeaf) {
            return hitTest(point, this.UserData) == HitTestBehavior.Stop ? this : null;
        }
        return (_a = this.Left.FirstHitNodeWithPredicate(point, hitTest)) !== null && _a !== void 0 ? _a : this.Right.FirstHitNodeWithPredicate(point, hitTest);
    }
    // brings the first leaf which rectangle was hit and the delegate is happy with the object
    FirstHitNode(point) {
        var _a;
        if (this.irect.contains_point(point)) {
            if (this.IsLeaf)
                return this;
            return (_a = this.Left.FirstHitNode(point)) !== null && _a !== void 0 ? _a : this.Right.FirstHitNode(point);
        }
        return null;
    }
    // returns all leaf nodes for which the rectangle was hit and the delegate is happy with the object
    *AllHitItems(rectanglePar, hitTestAccept) {
        const stack = new Stack();
        stack.push(this);
        while (stack.size > 0) {
            const node = stack.pop();
            if (node.irect.intersects_rect(rectanglePar)) {
                if (node.IsLeaf) {
                    if (hitTestAccept == null || hitTestAccept(node.UserData)) {
                        yield node.UserData;
                    }
                }
                else {
                    stack.push(node.left);
                    stack.push(node.right);
                }
            }
        }
    }
    // returns all items for which the rectangle contains the point
    *AllHitItems_(point) {
        const stack = new Stack();
        stack.push(this);
        while (stack.size > 0) {
            const node = stack.pop();
            if (node.irect.contains_point(point)) {
                if (node.IsLeaf)
                    yield node.UserData;
                else {
                    stack.push(node.left);
                    stack.push(node.right);
                }
            }
        }
    }
    // Returns all leaves whose rectangles intersect hitRectangle (or all leaves before hitTest returns false).
    VisitTree(hitTest, hitRectangle) {
        VisitTreeStatic(this, hitTest, hitRectangle);
    }
    //
    Clone() {
        const ret = mkRectangleNodeWithCount(this.Count);
        ret.UserData = this.UserData;
        ret.irect = this.irect;
        if (this.Left != null)
            ret.Left = this.Left.Clone();
        if (this.Right != null)
            ret.Right = this.Right.Clone();
        return ret;
    }
    // yields all leaves which rectangles intersect the given one. We suppose that leaves are all nodes having UserData not a null.
    *GetNodeItemsIntersectingRectangle(rectanglePar) {
        for (const n of this.GetLeafRectangleNodesIntersectingRectangle(rectanglePar))
            yield n.UserData;
    }
    // yields all leaves whose rectangles intersect the given one. We suppose that leaves are all nodes having UserData not a null.
    *GetLeafRectangleNodesIntersectingRectangle(rectanglePar) {
        const stack = new Stack();
        stack.push(this);
        while (stack.size > 0) {
            const node = stack.pop();
            if (node.irect.intersects_rect(rectanglePar)) {
                if (node.IsLeaf) {
                    yield node;
                }
                else {
                    stack.push(node.left);
                    stack.push(node.right);
                }
            }
        }
    }
    // Walk the tree and return the data from all leaves
    *GetAllLeaves() {
        for (const n of this.GetAllLeafNodes())
            yield n.UserData;
    }
    *GetAllLeafNodes() {
        //return this.EnumRectangleNodes(true /*leafOnly*/)
        for (const p of this.EnumRectangleNodes(true))
            yield p;
    }
    *EnumRectangleNodes(leafOnly) {
        const stack = new Stack();
        stack.push(this);
        while (stack.size > 0) {
            const node = stack.pop();
            if (node.IsLeaf || !leafOnly) {
                yield node;
            }
            if (!node.IsLeaf) {
                stack.push(node.left);
                stack.push(node.right);
            }
        }
    }
    //
    // Walk the tree from node down and apply visitor to all nodes
    TraverseHierarchy(node, visitor) {
        visitor(node);
        if (node.Left != null)
            this.TraverseHierarchy(node.Left, visitor);
        if (node.Right != null)
            this.TraverseHierarchy(node.Right, visitor);
    }
}
//# sourceMappingURL=RectangleNode.js.map