    //Based on js_es:
    //
    //https://github.com/vadimg/js_bintrees
    //
    //Copyright (C) 2011 by Vadim Graboys
    //
    //Permission is hereby granted, free of charge, to any person obtaining a copy
    //of this software and associated documentation files (the "Software"), to deal
    //in the Software without restriction, including without limitation the rights
    //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    //copies of the Software, and to permit persons to whom the Software is
    //furnished to do so, subject to the following conditions:
    //
    //The above copyright notice and this permission notice shall be included in
    //all copies or substantial portions of the Software.
    //
    //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    //THE SOFTWARE.
    export class TreeBase {
        _root;
        size;
        _comparator;
        // removes all nodes from the tree
        clear() {
            this._root = null;
            this.size = 0;
        };

        // returns node data if found, null otherwise
        find(data) {
            var res = this._root;

            while (res !== null) {
                var c = this._comparator(data, res.data);
                if (c === 0) {
                    return res.data;
                }
                else {
                    res = res.get_child(c > 0);
                }
            }

            return null;
        };

        // returns iterator to node if found, null otherwise
        findIter = function (data) {
            var res = this._root;
            var iter = this.iterator();

            while (res !== null) {
                var c = this._comparator(data, res.data);
                if (c === 0) {
                    iter._cursor = res;
                    return iter;
                }
                else {
                    iter._ancestors.push(res);
                    res = res.get_child(c > 0);
                }
            }

            return null;
        };

        // Returns an interator to the tree node immediately before (or at) the element
        lowerBound(data) {
            return this._bound(data, this._comparator);
        };

        // Returns an interator to the tree node immediately after (or at) the element
        upperBound(data) {
            var cmp = this._comparator;

            function reverse_cmp(a, b) {
                return cmp(b, a);
            }

            return this._bound(data, reverse_cmp);
        };

        // returns null if tree is empty
        min() {
            var res = this._root;
            if (res === null) {
                return null;
            }

            while (res.left !== null) {
                res = res.left;
            }

            return res.data;
        };

        // returns null if tree is empty
        max() {
            var res = this._root;
            if (res === null) {
                return null;
            }

            while (res.right !== null) {
                res = res.right;
            }

            return res.data;
        };

        // returns a null iterator
        // call next() or prev() to point to an element
        iterator(): Iterator {
            return new Iterator(this);
        };

        // calls cb on each node's data, in order
        each(cb) {
            var it = this.iterator(), data;
            while ((data = it.next()) !== null) {
                cb(data);
            }
        };

        // calls cb on each node's data, in reverse order
        reach(cb) {
            var it = this.iterator(), data;
            while ((data = it.prev()) !== null) {
                cb(data);
            }
        };

        // used for lowerBound and upperBound
        _bound(data, cmp) {
            var cur = this._root;
            var iter = this.iterator();

            while (cur !== null) {
                var c = this._comparator(data, cur.data);
                if (c === 0) {
                    iter._cursor = cur;
                    return iter;
                }
                iter._ancestors.push(cur);
                cur = cur.get_child(c > 0);
            }

            for (var i = iter._ancestors.length - 1; i >= 0; --i) {
                cur = iter._ancestors[i];
                if (cmp(data, cur.data) > 0) {
                    iter._cursor = cur;
                    iter._ancestors.length = i;
                    return iter;
                }
            }

            iter._ancestors.length = 0;
            return iter;
        };
    }
    export class Iterator {
        _tree;
        _ancestors;
        _cursor;
        constructor(tree) {
            this._tree = tree;
            this._ancestors = [];
            this._cursor = null;
        }

        data() {
            return this._cursor !== null ? this._cursor.data : null;
        };

        // if null-iterator, returns first node
        // otherwise, returns next node
        next() {
            if (this._cursor === null) {
                var root = this._tree._root;
                if (root !== null) {
                    this._minNode(root);
                }
            }
            else {
                if (this._cursor.right === null) {
                    // no greater node in subtree, go up to parent
                    // if coming from a right child, continue up the stack
                    var save;
                    do {
                        save = this._cursor;
                        if (this._ancestors.length) {
                            this._cursor = this._ancestors.pop();
                        }
                        else {
                            this._cursor = null;
                            break;
                        }
                    } while (this._cursor.right === save);
                }
                else {
                    // get the next node from the subtree
                    this._ancestors.push(this._cursor);
                    this._minNode(this._cursor.right);
                }
            }
            return this._cursor !== null ? this._cursor.data : null;
        };

        // if null-iterator, returns last node
        // otherwise, returns previous node
        prev() {
            if (this._cursor === null) {
                var root = this._tree._root;
                if (root !== null) {
                    this._maxNode(root);
                }
            }
            else {
                if (this._cursor.left === null) {
                    var save;
                    do {
                        save = this._cursor;
                        if (this._ancestors.length) {
                            this._cursor = this._ancestors.pop();
                        }
                        else {
                            this._cursor = null;
                            break;
                        }
                    } while (this._cursor.left === save);
                }
                else {
                    this._ancestors.push(this._cursor);
                    this._maxNode(this._cursor.left);
                }
            }
            return this._cursor !== null ? this._cursor.data : null;
        };

        _minNode(start) {
            while (start.left !== null) {
                this._ancestors.push(start);
                start = start.left;
            }
            this._cursor = start;
        };

        _maxNode(start) {
            while (start.right !== null) {
                this._ancestors.push(start);
                start = start.right;
            }
            this._cursor = start;
        };
    }

    class Node {
        data;
        left;
        right;
        red;
        constructor(data) {
            this.data = data;
            this.left = null;
            this.right = null;
            this.red = true;
        }

        get_child(dir) {
            return dir ? this.right : this.left;
        };

        set_child(dir, val) {
            if (dir) {
                this.right = val;
            }
            else {
                this.left = val;
            }
        };
    }

    export class RBTree<T> extends TreeBase {

        _root;
        _comparator;
        size;
        constructor(comparator: (a: T, b: T) => number) {
            super();
            this._root = null;
            this._comparator = comparator;
            this.size = 0;
        }

        // returns true if inserted, false if duplicate
        insert(data) {
            var ret = false;

            if (this._root === null) {
                // empty tree
                this._root = new Node(data);
                ret = true;
                this.size++;
            }
            else {
                var head = new Node(undefined); // fake tree root

                var dir = false;
                var last = false;

                // setup
                var gp = null; // grandparent
                var ggp = head; // grand-grand-parent
                var p = null; // parent
                var node = this._root;
                ggp.right = this._root;

                // search down
                while (true) {
                    if (node === null) {
                        // insert new node at the bottom
                        node = new Node(data);
                        p.set_child(dir, node);
                        ret = true;
                        this.size++;
                    }
                    else if (RBTree.is_red(node.left) && RBTree.is_red(node.right)) {
                        // color flip
                        node.red = true;
                        node.left.red = false;
                        node.right.red = false;
                    }

                    // fix red violation
                    if (RBTree.is_red(node) && RBTree.is_red(p)) {
                        var dir2 = ggp.right === gp;

                        if (node === p.get_child(last)) {
                            ggp.set_child(dir2, RBTree.single_rotate(gp, !last));
                        }
                        else {
                            ggp.set_child(dir2, RBTree.double_rotate(gp, !last));
                        }
                    }

                    var cmp = this._comparator(node.data, data);

                    // stop if found
                    if (cmp === 0) {
                        break;
                    }

                    last = dir;
                    dir = cmp < 0;

                    // update helpers
                    if (gp !== null) {
                        ggp = gp;
                    }
                    gp = p;
                    p = node;
                    node = node.get_child(dir);
                }

                // update root
                this._root = head.right;
            }

            // make root black
            this._root.red = false;

            return ret;
        };

        // returns true if removed, false if not found
        remove(data) {
            if (this._root === null) {
                return false;
            }

            var head = new Node(undefined); // fake tree root
            var node = head;
            node.right = this._root;
            var p = null; // parent
            var gp = null; // grand parent
            var found = null; // found item
            var dir = true;

            while (node.get_child(dir) !== null) {
                var last = dir;

                // update helpers
                gp = p;
                p = node;
                node = node.get_child(dir);

                var cmp = this._comparator(data, node.data);

                dir = cmp > 0;

                // save found node
                if (cmp === 0) {
                    found = node;
                }

                // push the red node down
                if (!RBTree.is_red(node) && !RBTree.is_red(node.get_child(dir))) {
                    if (RBTree.is_red(node.get_child(!dir))) {
                        var sr = RBTree.single_rotate(node, dir);
                        p.set_child(last, sr);
                        p = sr;
                    }
                    else if (!RBTree.is_red(node.get_child(!dir))) {
                        var sibling = p.get_child(!last);
                        if (sibling !== null) {
                            if (!RBTree.is_red(sibling.get_child(!last)) && !RBTree.is_red(sibling.get_child(last))) {
                                // color flip
                                p.red = false;
                                sibling.red = true;
                                node.red = true;
                            }
                            else {
                                var dir2 = gp.right === p;

                                if (RBTree.is_red(sibling.get_child(last))) {
                                    gp.set_child(dir2, RBTree.double_rotate(p, last));
                                }
                                else if (RBTree.is_red(sibling.get_child(!last))) {
                                    gp.set_child(dir2, RBTree.single_rotate(p, last));
                                }

                                // ensure correct coloring
                                var gpc = gp.get_child(dir2);
                                gpc.red = true;
                                node.red = true;
                                gpc.left.red = false;
                                gpc.right.red = false;
                            }
                        }
                    }
                }
            }

            // replace and remove if found
            if (found !== null) {
                found.data = node.data;
                p.set_child(p.right === node, node.get_child(node.left === null));
                this.size--;
            }

            // update root and make it black
            this._root = head.right;
            if (this._root !== null) {
                this._root.red = false;
            }

            return found !== null;
        };

        static is_red(node) {
            return node !== null && node.red;
        }

        static single_rotate(root, dir) {
            var save = root.get_child(!dir);

            root.set_child(!dir, save.get_child(dir));
            save.set_child(dir, root);

            root.red = true;
            save.red = false;

            return save;
        }

        static double_rotate(root, dir) {
            root.set_child(!dir, RBTree.single_rotate(root.get_child(!dir), !dir));
            return RBTree.single_rotate(root, dir);
        }
    }
