"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const TreeNode_1 = __importDefault(require("./TreeNode"));
const index_1 = require("../../ContainerBase/index");
const checkParams_1 = require("../../../utils/checkParams");
class TreeContainer extends index_1.Container {
    constructor(cmp = (x, y) => {
        if (x < y)
            return -1;
        if (x > y)
            return 1;
        return 0;
    }) {
        super();
        this.root = undefined;
        this.header = new TreeNode_1.default();
        /**
         * @description InOrder traversal the tree.
         * @protected
         */
        this.inOrderTraversal = (curNode, callback) => {
            if (curNode === undefined)
                return false;
            const ifReturn = this.inOrderTraversal(curNode.left, callback);
            if (ifReturn)
                return true;
            if (callback(curNode))
                return true;
            return this.inOrderTraversal(curNode.right, callback);
        };
        this.cmp = cmp;
    }
    /**
     * @param curNode The starting node of the search.
     * @param key The key you want to search.
     * @return TreeNode which key is greater than or equals to the given key.
     * @protected
     */
    _lowerBound(curNode, key) {
        let resNode;
        while (curNode) {
            const cmpResult = this.cmp(curNode.key, key);
            if (cmpResult < 0) {
                curNode = curNode.right;
            }
            else if (cmpResult > 0) {
                resNode = curNode;
                curNode = curNode.left;
            }
            else
                return curNode;
        }
        return resNode === undefined ? this.header : resNode;
    }
    /**
     * @param curNode The starting node of the search.
     * @param key The key you want to search.
     * @return TreeNode which key is greater than the given key.
     * @protected
     */
    _upperBound(curNode, key) {
        let resNode;
        while (curNode) {
            const cmpResult = this.cmp(curNode.key, key);
            if (cmpResult <= 0) {
                curNode = curNode.right;
            }
            else if (cmpResult > 0) {
                resNode = curNode;
                curNode = curNode.left;
            }
        }
        return resNode === undefined ? this.header : resNode;
    }
    /**
     * @param curNode The starting node of the search.
     * @param key The key you want to search.
     * @return TreeNode which key is less than or equals to the given key.
     * @protected
     */
    _reverseLowerBound(curNode, key) {
        let resNode;
        while (curNode) {
            const cmpResult = this.cmp(curNode.key, key);
            if (cmpResult < 0) {
                resNode = curNode;
                curNode = curNode.right;
            }
            else if (cmpResult > 0) {
                curNode = curNode.left;
            }
            else
                return curNode;
        }
        return resNode === undefined ? this.header : resNode;
    }
    /**
     * @param curNode The starting node of the search.
     * @param key The key you want to search.
     * @return TreeNode which key is less than the given key.
     * @protected
     */
    _reverseUpperBound(curNode, key) {
        let resNode;
        while (curNode) {
            const cmpResult = this.cmp(curNode.key, key);
            if (cmpResult < 0) {
                resNode = curNode;
                curNode = curNode.right;
            }
            else if (cmpResult >= 0) {
                curNode = curNode.left;
            }
        }
        return resNode === undefined ? this.header : resNode;
    }
    /**
     * @description Make self balance after erase a node.
     * @param curNode The node want to remove.
     * @protected
     */
    eraseNodeSelfBalance(curNode) {
        while (true) {
            const parentNode = curNode.parent;
            if (parentNode === this.header)
                return;
            if (curNode.color === TreeNode_1.default.RED) {
                curNode.color = TreeNode_1.default.BLACK;
                return;
            }
            if (curNode === parentNode.left) {
                const brother = parentNode.right;
                if (brother.color === TreeNode_1.default.RED) {
                    brother.color = TreeNode_1.default.BLACK;
                    parentNode.color = TreeNode_1.default.RED;
                    if (parentNode === this.root) {
                        this.root = parentNode.rotateLeft();
                    }
                    else
                        parentNode.rotateLeft();
                }
                else if (brother.color === TreeNode_1.default.BLACK) {
                    if (brother.right && brother.right.color === TreeNode_1.default.RED) {
                        brother.color = parentNode.color;
                        parentNode.color = TreeNode_1.default.BLACK;
                        brother.right.color = TreeNode_1.default.BLACK;
                        if (parentNode === this.root) {
                            this.root = parentNode.rotateLeft();
                        }
                        else
                            parentNode.rotateLeft();
                        return;
                    }
                    else if (brother.left && brother.left.color === TreeNode_1.default.RED) {
                        brother.color = TreeNode_1.default.RED;
                        brother.left.color = TreeNode_1.default.BLACK;
                        brother.rotateRight();
                    }
                    else {
                        brother.color = TreeNode_1.default.RED;
                        curNode = parentNode;
                    }
                }
            }
            else {
                const brother = parentNode.left;
                if (brother.color === TreeNode_1.default.RED) {
                    brother.color = TreeNode_1.default.BLACK;
                    parentNode.color = TreeNode_1.default.RED;
                    if (parentNode === this.root) {
                        this.root = parentNode.rotateRight();
                    }
                    else
                        parentNode.rotateRight();
                }
                else {
                    if (brother.left && brother.left.color === TreeNode_1.default.RED) {
                        brother.color = parentNode.color;
                        parentNode.color = TreeNode_1.default.BLACK;
                        brother.left.color = TreeNode_1.default.BLACK;
                        if (parentNode === this.root) {
                            this.root = parentNode.rotateRight();
                        }
                        else
                            parentNode.rotateRight();
                        return;
                    }
                    else if (brother.right && brother.right.color === TreeNode_1.default.RED) {
                        brother.color = TreeNode_1.default.RED;
                        brother.right.color = TreeNode_1.default.BLACK;
                        brother.rotateLeft();
                    }
                    else {
                        brother.color = TreeNode_1.default.RED;
                        curNode = parentNode;
                    }
                }
            }
        }
    }
    /**
     * @description Remove a node.
     * @param curNode The node you want to remove.
     * @protected
     */
    eraseNode(curNode) {
        if (this.length === 1) {
            this.clear();
            return;
        }
        let swapNode = curNode;
        while (swapNode.left || swapNode.right) {
            if (swapNode.right) {
                swapNode = swapNode.right;
                while (swapNode.left)
                    swapNode = swapNode.left;
            }
            else if (swapNode.left) {
                swapNode = swapNode.left;
            }
            [curNode.key, swapNode.key] = [swapNode.key, curNode.key];
            [curNode.value, swapNode.value] = [swapNode.value, curNode.value];
            curNode = swapNode;
        }
        if (this.header.left === swapNode) {
            this.header.left = swapNode.parent;
        }
        else if (this.header.right === swapNode) {
            this.header.right = swapNode.parent;
        }
        this.eraseNodeSelfBalance(swapNode);
        swapNode.remove();
        this.length -= 1;
        this.root.color = TreeNode_1.default.BLACK;
    }
    /**
     * @description Make self balance after insert a node.
     * @param curNode The node want to insert.
     * @protected
     */
    insertNodeSelfBalance(curNode) {
        while (true) {
            const parentNode = curNode.parent;
            if (parentNode.color === TreeNode_1.default.BLACK)
                return;
            const grandParent = parentNode.parent;
            if (parentNode === grandParent.left) {
                const uncle = grandParent.right;
                if (uncle && uncle.color === TreeNode_1.default.RED) {
                    uncle.color = parentNode.color = TreeNode_1.default.BLACK;
                    if (grandParent === this.root)
                        return;
                    grandParent.color = TreeNode_1.default.RED;
                    curNode = grandParent;
                    continue;
                }
                else if (curNode === parentNode.right) {
                    curNode.color = TreeNode_1.default.BLACK;
                    if (curNode.left)
                        curNode.left.parent = parentNode;
                    if (curNode.right)
                        curNode.right.parent = grandParent;
                    parentNode.right = curNode.left;
                    grandParent.left = curNode.right;
                    curNode.left = parentNode;
                    curNode.right = grandParent;
                    if (grandParent === this.root) {
                        this.root = curNode;
                        this.header.parent = curNode;
                    }
                    else {
                        const GP = grandParent.parent;
                        if (GP.left === grandParent) {
                            GP.left = curNode;
                        }
                        else
                            GP.right = curNode;
                    }
                    curNode.parent = grandParent.parent;
                    parentNode.parent = curNode;
                    grandParent.parent = curNode;
                }
                else {
                    parentNode.color = TreeNode_1.default.BLACK;
                    if (grandParent === this.root) {
                        this.root = grandParent.rotateRight();
                    }
                    else
                        grandParent.rotateRight();
                }
                grandParent.color = TreeNode_1.default.RED;
            }
            else {
                const uncle = grandParent.left;
                if (uncle && uncle.color === TreeNode_1.default.RED) {
                    uncle.color = parentNode.color = TreeNode_1.default.BLACK;
                    if (grandParent === this.root)
                        return;
                    grandParent.color = TreeNode_1.default.RED;
                    curNode = grandParent;
                    continue;
                }
                else if (curNode === parentNode.left) {
                    curNode.color = TreeNode_1.default.BLACK;
                    if (curNode.left)
                        curNode.left.parent = grandParent;
                    if (curNode.right)
                        curNode.right.parent = parentNode;
                    grandParent.right = curNode.left;
                    parentNode.left = curNode.right;
                    curNode.left = grandParent;
                    curNode.right = parentNode;
                    if (grandParent === this.root) {
                        this.root = curNode;
                        this.header.parent = curNode;
                    }
                    else {
                        const GP = grandParent.parent;
                        if (GP.left === grandParent) {
                            GP.left = curNode;
                        }
                        else
                            GP.right = curNode;
                    }
                    curNode.parent = grandParent.parent;
                    parentNode.parent = curNode;
                    grandParent.parent = curNode;
                }
                else {
                    parentNode.color = TreeNode_1.default.BLACK;
                    if (grandParent === this.root) {
                        this.root = grandParent.rotateLeft();
                    }
                    else
                        grandParent.rotateLeft();
                }
                grandParent.color = TreeNode_1.default.RED;
            }
            return;
        }
    }
    /**
     * @description Find node which key is equals to the given key.
     * @param curNode The starting node of the search.
     * @param key The key you want to search.
     * @protected
     */
    findElementNode(curNode, key) {
        while (curNode) {
            const cmpResult = this.cmp(curNode.key, key);
            if (cmpResult < 0) {
                curNode = curNode.right;
            }
            else if (cmpResult > 0) {
                curNode = curNode.left;
            }
            else
                return curNode;
        }
        return curNode;
    }
    /**
     * @description Insert a key-value pair or set value by the given key.
     * @param key The key want to insert.
     * @param value The value want to set.
     * @param hint You can give an iterator hint to improve insertion efficiency.
     * @protected
     */
    set(key, value, hint) {
        if (this.root === undefined) {
            this.length += 1;
            this.root = new TreeNode_1.default(key, value);
            this.root.color = TreeNode_1.default.BLACK;
            this.root.parent = this.header;
            this.header.parent = this.root;
            this.header.left = this.root;
            this.header.right = this.root;
            return;
        }
        let curNode;
        const minNode = this.header.left;
        const compareToMin = this.cmp(minNode.key, key);
        if (compareToMin === 0) {
            minNode.value = value;
            return;
        }
        else if (compareToMin > 0) {
            minNode.left = new TreeNode_1.default(key, value);
            minNode.left.parent = minNode;
            curNode = minNode.left;
            this.header.left = curNode;
        }
        else {
            const maxNode = this.header.right;
            const compareToMax = this.cmp(maxNode.key, key);
            if (compareToMax === 0) {
                maxNode.value = value;
                return;
            }
            else if (compareToMax < 0) {
                maxNode.right = new TreeNode_1.default(key, value);
                maxNode.right.parent = maxNode;
                curNode = maxNode.right;
                this.header.right = curNode;
            }
            else {
                if (hint !== undefined) {
                    // @ts-ignore
                    const iterNode = hint.node;
                    if (iterNode !== this.header) {
                        const iterCmpRes = this.cmp(iterNode.key, key);
                        if (iterCmpRes === 0) {
                            iterNode.value = value;
                            return;
                        }
                        else if (iterCmpRes > 0) {
                            const preNode = iterNode.pre();
                            const preCmpRes = this.cmp(preNode.key, key);
                            if (preCmpRes === 0) {
                                preNode.value = value;
                                return;
                            }
                            else if (preCmpRes < 0) {
                                curNode = new TreeNode_1.default(key, value);
                                if (preNode.right === undefined) {
                                    preNode.right = curNode;
                                    curNode.parent = preNode;
                                }
                                else {
                                    iterNode.left = curNode;
                                    curNode.parent = iterNode;
                                }
                            }
                        }
                    }
                }
                if (curNode === undefined) {
                    curNode = this.root;
                    while (true) {
                        const cmpResult = this.cmp(curNode.key, key);
                        if (cmpResult > 0) {
                            if (curNode.left === undefined) {
                                curNode.left = new TreeNode_1.default(key, value);
                                curNode.left.parent = curNode;
                                curNode = curNode.left;
                                break;
                            }
                            curNode = curNode.left;
                        }
                        else if (cmpResult < 0) {
                            if (curNode.right === undefined) {
                                curNode.right = new TreeNode_1.default(key, value);
                                curNode.right.parent = curNode;
                                curNode = curNode.right;
                                break;
                            }
                            curNode = curNode.right;
                        }
                        else {
                            curNode.value = value;
                            return;
                        }
                    }
                }
            }
        }
        this.length += 1;
        this.insertNodeSelfBalance(curNode);
    }
    clear() {
        this.length = 0;
        this.root = undefined;
        this.header.parent = undefined;
        this.header.left = this.header.right = undefined;
    }
    /**
     * @description Update node's key by iterator.
     * @param iter The iterator you want to change.
     * @param key The key you want to update.
     * @return Boolean about if the modification is successful.
     */
    updateKeyByIterator(iter, key) {
        // @ts-ignore
        const node = iter.node;
        if (node === this.header) {
            throw new TypeError('Invalid iterator!');
        }
        if (this.length === 1) {
            node.key = key;
            return true;
        }
        if (node === this.header.left) {
            if (this.cmp(node.next().key, key) > 0) {
                node.key = key;
                return true;
            }
            return false;
        }
        if (node === this.header.right) {
            if (this.cmp(node.pre().key, key) < 0) {
                node.key = key;
                return true;
            }
            return false;
        }
        const preKey = node.pre().key;
        if (this.cmp(preKey, key) >= 0)
            return false;
        const nextKey = node.next().key;
        if (this.cmp(nextKey, key) <= 0)
            return false;
        node.key = key;
        return true;
    }
    eraseElementByPos(pos) {
        (0, checkParams_1.checkWithinAccessParams)(pos, 0, this.length - 1);
        let index = 0;
        this.inOrderTraversal(this.root, curNode => {
            if (pos === index) {
                this.eraseNode(curNode);
                return true;
            }
            index += 1;
            return false;
        });
    }
    /**
     * @description Remove the element of the specified key.
     * @param key The key you want to remove.
     */
    eraseElementByKey(key) {
        if (!this.length)
            return;
        const curNode = this.findElementNode(this.root, key);
        if (curNode === undefined)
            return;
        this.eraseNode(curNode);
    }
    eraseElementByIterator(iter) {
        // @ts-ignore
        const node = iter.node;
        if (node === this.header) {
            throw new RangeError('Invalid iterator');
        }
        if (node.right === undefined) {
            iter = iter.next();
        }
        this.eraseNode(node);
        return iter;
    }
    /**
     * @description Get the height of the tree.
     * @return Number about the height of the RB-tree.
     */
    getHeight() {
        if (!this.length)
            return 0;
        const traversal = (curNode) => {
            if (!curNode)
                return 0;
            return Math.max(traversal(curNode.left), traversal(curNode.right)) + 1;
        };
        return traversal(this.root);
    }
}
exports.default = TreeContainer;