"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedBlackTree = exports.RBColor = void 0;
/* eslint-disable @typescript-eslint/consistent-type-assertions */
/* Remove once strictNullCheck is enabled */
/**
 * @internal
 */
exports.RBColor = {
    RED: 0,
    BLACK: 1,
};
/**
 * @internal
 */
class RedBlackTree {
    constructor(compareKeys, aug) {
        this.compareKeys = compareKeys;
        this.aug = aug;
    }
    makeNode(key, data, color, size) {
        return { key, data, color, size };
    }
    isRed(node) {
        return !!node && node.color === exports.RBColor.RED;
    }
    nodeSize(node) {
        return node ? node.size : 0;
    }
    size() {
        return this.nodeSize(this.root);
    }
    isEmpty() {
        return !this.root;
    }
    get(key) {
        if (key !== undefined) {
            return this.nodeGet(this.root, key);
        }
    }
    nodeGet(node, key) {
        let _node = node;
        while (_node) {
            const cmp = this.compareKeys(key, _node.key);
            if (cmp < 0) {
                _node = _node.left;
            }
            else if (cmp > 0) {
                _node = _node.right;
            }
            else {
                return _node;
            }
        }
    }
    contains(key) {
        return this.get(key);
    }
    gather(key, matcher) {
        const results = [];
        if (key !== undefined) {
            this.nodeGather(this.root, results, key, matcher);
        }
        return results;
    }
    nodeGather(node, results, key, matcher) {
        if (node) {
            if (matcher.continueSubtree(node.left, key)) {
                this.nodeGather(node.left, results, key, matcher);
            }
            if (matcher.matchNode(node, key)) {
                results.push(node);
            }
            if (matcher.continueSubtree(node.right, key)) {
                this.nodeGather(node.right, results, key, matcher);
            }
        }
    }
    walkExactMatchesForward(compareFn, actionFn, continueLeftFn, continueRightFn) {
        this.nodeWalkExactMatchesForward(this.root, compareFn, actionFn, continueLeftFn, continueRightFn);
    }
    nodeWalkExactMatchesForward(node, compareFn, actionFn, continueLeftFn, continueRightFn) {
        if (!node) {
            return;
        }
        const result = compareFn(node);
        if (continueLeftFn(result)) {
            this.nodeWalkExactMatchesForward(node.left, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
        if (result === 0) {
            actionFn(node);
        }
        if (continueRightFn(result)) {
            this.nodeWalkExactMatchesForward(node.right, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
    }
    walkExactMatchesBackward(compareFn, actionFn, continueLeftFn, continueRightFn) {
        this.nodeWalkExactMatchesBackward(this.root, compareFn, actionFn, continueLeftFn, continueRightFn);
    }
    nodeWalkExactMatchesBackward(node, compareFn, actionFn, continueLeftFn, continueRightFn) {
        if (!node) {
            return;
        }
        const result = compareFn(node);
        if (continueRightFn(result)) {
            this.nodeWalkExactMatchesBackward(node.right, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
        if (result === 0) {
            actionFn(node);
        }
        if (continueLeftFn(result)) {
            this.nodeWalkExactMatchesBackward(node.left, compareFn, actionFn, continueLeftFn, continueRightFn);
        }
    }
    put(key, data, conflict) {
        if (key !== undefined) {
            if (data === undefined) {
                this.remove(key);
            }
            else {
                this.root = this.nodePut(this.root, key, data, conflict);
                this.root.color = exports.RBColor.BLACK;
            }
        }
    }
    nodePut(node, key, data, conflict) {
        let _node = node;
        if (!_node) {
            return this.makeNode(key, data, exports.RBColor.RED, 1);
        }
        else {
            const cmp = this.compareKeys(key, _node.key);
            if (cmp < 0) {
                _node.left = this.nodePut(_node.left, key, data, conflict);
            }
            else if (cmp > 0) {
                _node.right = this.nodePut(_node.right, key, data, conflict);
            }
            else {
                if (conflict) {
                    const kd = conflict(key, _node.key, data, _node.data);
                    if (kd.key) {
                        _node.key = kd.key;
                    }
                    _node.data = kd.data ? kd.data : data;
                }
                else {
                    _node.data = data;
                }
            }
            if (this.isRed(_node.right) && !this.isRed(_node.left)) {
                _node = this.rotateLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if (this.isRed(_node.left) && this.isRed(_node.left.left)) {
                _node = this.rotateRight(_node);
            }
            if (this.isRed(_node.left) && this.isRed(_node.right)) {
                this.flipColors(_node);
            }
            _node.size = this.nodeSize(_node.left) + this.nodeSize(_node.right) + 1;
            if (this.aug) {
                this.updateLocal(_node);
            }
            return _node;
        }
    }
    updateLocal(node) {
        if (this.aug) {
            if (this.isRed(node.left)) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.aug.update(node.left);
            }
            if (this.isRed(node.right)) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                this.aug.update(node.right);
            }
            this.aug.update(node);
        }
    }
    nodeRemoveMin(node) {
        let _node = node;
        if (_node.left) {
            if (!this.isRed(_node.left) && !this.isRed(_node.left.left)) {
                _node = this.moveRedLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.left = this.nodeRemoveMin(_node.left);
            return this.balance(_node);
        }
    }
    remove(key) {
        if (key !== undefined) {
            if (!this.contains(key)) {
                return;
            }
            this.removeExisting(key);
        }
        // TODO: error on undefined key
    }
    removeExisting(key) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (!this.isRed(this.root.left) && !this.isRed(this.root.right)) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            this.root.color = exports.RBColor.RED;
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        this.root = this.nodeRemove(this.root, key);
    }
    nodeRemove(node, key) {
        let _node = node;
        if (this.compareKeys(key, _node.key) < 0) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if (!this.isRed(_node.left) && !this.isRed(_node.left.left)) {
                _node = this.moveRedLeft(_node);
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.left = this.nodeRemove(_node.left, key);
        }
        else {
            if (this.isRed(_node.left)) {
                _node = this.rotateRight(_node);
            }
            if (this.compareKeys(key, _node.key) === 0 && !_node.right) {
                return undefined;
            }
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            if (!this.isRed(_node.right) && !this.isRed(_node.right.left)) {
                _node = this.moveRedRight(_node);
            }
            if (this.compareKeys(key, _node.key) === 0) {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                const subtreeMin = this.nodeMin(_node.right);
                _node.key = subtreeMin.key;
                _node.data = subtreeMin.data;
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                _node.right = this.nodeRemoveMin(_node.right);
            }
            else {
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                _node.right = this.nodeRemove(_node.right, key);
            }
        }
        return this.balance(_node);
    }
    /**
     * @returns The largest node in this tree which compares less than or equal to `key`
     */
    floor(key) {
        if (!this.isEmpty()) {
            return this.nodeFloor(this.root, key);
        }
    }
    nodeFloor(node, key) {
        if (node) {
            const cmp = this.compareKeys(key, node.key);
            if (cmp === 0) {
                return node;
            }
            else if (cmp < 0) {
                return this.nodeFloor(node.left, key);
            }
            else {
                const rightFloor = this.nodeFloor(node.right, key);
                return rightFloor ? rightFloor : node;
            }
        }
    }
    /**
     * @returns The smallest node in this tree which compares greater than or equal to `key`
     */
    ceil(key) {
        if (!this.isEmpty()) {
            return this.nodeCeil(this.root, key);
        }
    }
    nodeCeil(node, key) {
        if (node) {
            const cmp = this.compareKeys(key, node.key);
            if (cmp === 0) {
                return node;
            }
            else if (cmp > 0) {
                return this.nodeCeil(node.right, key);
            }
            else {
                const leftCeil = this.nodeCeil(node.left, key);
                return leftCeil ? leftCeil : node;
            }
        }
    }
    min() {
        if (this.root) {
            return this.nodeMin(this.root);
        }
    }
    nodeMin(node) {
        return !node.left ? node : this.nodeMin(node.left);
    }
    max() {
        if (this.root) {
            return this.nodeMax(this.root);
        }
    }
    nodeMax(node) {
        return !node.right ? node : this.nodeMax(node.right);
    }
    rotateRight(node) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const leftChild = node.left;
        node.left = leftChild.right;
        leftChild.right = node;
        leftChild.color = leftChild.right.color;
        leftChild.right.color = exports.RBColor.RED;
        leftChild.size = node.size;
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.updateLocal(node);
            this.updateLocal(leftChild);
        }
        return leftChild;
    }
    rotateLeft(node) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        const rightChild = node.right;
        node.right = rightChild.left;
        rightChild.left = node;
        rightChild.color = rightChild.left.color;
        rightChild.left.color = exports.RBColor.RED;
        rightChild.size = node.size;
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.updateLocal(node);
            this.updateLocal(rightChild);
        }
        return rightChild;
    }
    oppositeColor(c) {
        return c === exports.RBColor.BLACK ? exports.RBColor.RED : exports.RBColor.BLACK;
    }
    flipColors(node) {
        node.color = this.oppositeColor(node.color);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        node.left.color = this.oppositeColor(node.left.color);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        node.right.color = this.oppositeColor(node.right.color);
    }
    moveRedLeft(node) {
        let _node = node;
        this.flipColors(_node);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(_node.right.left)) {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            _node.right = this.rotateRight(_node.right);
            _node = this.rotateLeft(_node);
            this.flipColors(_node);
        }
        return _node;
    }
    moveRedRight(node) {
        let _node = node;
        this.flipColors(_node);
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(_node.left.left)) {
            _node = this.rotateRight(_node);
            this.flipColors(_node);
        }
        return _node;
    }
    balance(input) {
        let node = input;
        if (this.isRed(node.right)) {
            node = this.rotateLeft(node);
        }
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        if (this.isRed(node.left) && this.isRed(node.left.left)) {
            node = this.rotateRight(node);
        }
        if (this.isRed(node.left) && this.isRed(node.right)) {
            this.flipColors(node);
        }
        node.size = this.nodeSize(node.left) + this.nodeSize(node.right) + 1;
        if (this.aug) {
            this.aug.update(node);
        }
        return node;
    }
    mapRange(action, accum, start, end) {
        this.nodeMap(this.root, action, start, end);
    }
    map(action, accum) {
        // TODO: optimize to avoid comparisons
        this.nodeMap(this.root, action, accum);
    }
    keys() {
        const keyList = [];
        const actions = {
            showStructure: true,
            infix: (node) => {
                keyList.push(node.key);
                return true;
            },
        };
        this.walk(actions);
        return keyList;
    }
    /**
     * Depth-first traversal with custom action; if action returns
     * false, traversal is halted.
     * @param action - action to apply to each node
     */
    walk(actions) {
        this.nodeWalk(this.root, actions);
    }
    walkBackward(actions) {
        this.nodeWalkBackward(this.root, actions);
    }
    nodeWalk(node, actions) {
        let go = true;
        if (node) {
            if (actions.pre) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.pre(node);
                }
            }
            if (node.left) {
                go = this.nodeWalk(node.left, actions);
            }
            if (go && actions.infix) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.infix(node);
                }
            }
            if (go) {
                go = this.nodeWalk(node.right, actions);
            }
            if (go && actions.post) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.post(node);
                }
            }
        }
        return go;
    }
    nodeWalkBackward(node, actions) {
        let go = true;
        if (node) {
            if (actions.pre) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.pre(node);
                }
            }
            if (node.right) {
                go = this.nodeWalkBackward(node.right, actions);
            }
            if (go && actions.infix) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.infix(node);
                }
            }
            if (go) {
                go = this.nodeWalkBackward(node.left, actions);
            }
            if (go && actions.post) {
                if (actions.showStructure || node.color === exports.RBColor.BLACK) {
                    go = actions.post(node);
                }
            }
        }
        return go;
    }
    nodeMap(node, action, accum, start, end) {
        let _start = start;
        let _end = end;
        if (!node) {
            return true;
        }
        if (_start === undefined) {
            _start = this.nodeMin(node).key;
        }
        if (_end === undefined) {
            _end = this.nodeMax(node).key;
        }
        const cmpStart = this.compareKeys(_start, node.key);
        const cmpEnd = this.compareKeys(_end, node.key);
        let go = true;
        if (cmpStart < 0) {
            go = this.nodeMap(node.left, action, accum, _start, _end);
        }
        if (go && cmpStart <= 0 && cmpEnd >= 0) {
            // REVIEW: test for black node here
            go = action(node, accum);
        }
        if (go && cmpEnd > 0) {
            go = this.nodeMap(node.right, action, accum, _start, _end);
        }
        return go;
    }
}
exports.RedBlackTree = RedBlackTree;
//# sourceMappingURL=rbTree.js.map