// insertNode adds the given key and value into the node
        private rbNode insertNode(rbNode node, RbKey key, object value)
        {
            if (node == null)
            {
                this.count++;
                return(newRbNode(key, value));
            }

            var cmp = key.ComparedTo(node.key);

            switch (cmp)
            {
            case KeyComparison.KeyIsLess:
                node.left = this.insertNode(node.left, key, value);
                break;

            case KeyComparison.KeyIsGreater:
                node.right = this.insertNode(node.right, key, value);
                break;

            default:
                if (this.onInsert == null)
                {
                    node.value = value;
                }
                else
                {
                    node.value = this.onInsert(key, value);
                }
                break;
            }
            return(balance(node));
        }
        // ceilig returns the smallest key node in the subtree rooted at x greater than or equal to the given key
        private rbNode ceiling(rbNode node, RbKey key)
        {
            if (node == null)
            {
                return(null);
            }

            var cmp = key.ComparedTo(node.key);

            switch (cmp)
            {
            case KeyComparison.KeysAreEqual:
                return(node);

            case KeyComparison.KeyIsGreater:
                return(ceiling(node.right, key));

            default:
                var cn = ceiling(node.left, key);
                if (cn != null)
                {
                    return(cn);
                }
                return(node);
            }
        }
        // floor returns the largest key node in the subtree rooted at x less than or equal to the given key
        private rbNode floor(rbNode node, RbKey key)
        {
            if (node == null)
            {
                return(null);
            }

            var cmp = key.ComparedTo(node.key);

            switch (cmp)
            {
            case KeyComparison.KeysAreEqual:
                return(node);

            case KeyComparison.KeyIsLess:
                return(floor(node.left, key));

            default:
                var fn = floor(node.right, key);
                if (fn != null)
                {
                    return(fn);
                }
                return(node);
            }
        }
        public void Between(RbKey loKey, RbKey hiKey, out int count, out error err)
        {
            count = 0;
            err   = null;
            if (loKey == null)
            {
                err = ErrorDef.ArgumentNilError("loKey");
                return;
            }
            if (hiKey == null)
            {
                err = ErrorDef.ArgumentNilError("hiKey");
                return;
            }

            RbTree tree;

            this.checkStateAndGetTree(out tree, out err);
            if (err != null)
            {
                return;
            }

            try
            {
                var cmp = loKey.ComparedTo(hiKey);
                switch (cmp)
                {
                case KeyComparison.KeysAreEqual:
                    var node = tree.find(loKey);
                    if (node != null)
                    {
                        count = 1;
                        this.callback(this, node.key, node.value);
                        return;
                    }
                    return;

                case KeyComparison.KeyIsGreater:
                    var tmp = loKey;
                    loKey = hiKey;
                    hiKey = tmp;
                    break;
                }

                this.walkBetween(tree.root, loKey, hiKey);
                count = this.CurrentCount();
            }
            finally
            {
                Interlocked.CompareExchange(ref this.state, (long)IterationState.iteratorReady,
                                            (long)IterationState.iteratorWalking);
            }
        }
        public void walkBetween(rbNode node, RbKey loKey, RbKey hiKey)
        {
            if (node == null || !this.inWalk())
            {
                return;
            }

            var cmpLo = (int)loKey.ComparedTo(node.key);

            if (cmpLo < RbTree.zeroOrEqual)
            {
                if (node.left != null)
                {
                    this.walkBetween(node.left, loKey, hiKey);
                    if (!this.inWalk())
                    {
                        return;
                    }
                }
            }

            var cmpHi = (int)hiKey.ComparedTo(node.key);

            if (cmpLo <= RbTree.zeroOrEqual && cmpHi >= RbTree.zeroOrEqual)
            {
                this.incrementCount();
                this.callback(this, node.key, node.value);
                if (!this.inWalk())
                {
                    return;
                }
            }

            if (cmpHi > RbTree.zeroOrEqual)
            {
                if (node.right != null)
                {
                    this.walkBetween(node.right, loKey, hiKey);
                }
            }
        }
        // find returns the node if (key found, otherwise returns null
        internal rbNode find(RbKey key)
        {
            for (var node = this.root; node != null;)
            {
                var cmp = key.ComparedTo(node.key);
                switch (cmp)
                {
                case KeyComparison.KeyIsLess:
                    node = node.left;
                    break;

                case KeyComparison.KeyIsGreater:
                    node = node.right;
                    break;

                default:
                    return(node);
                }
            }
            return(null);
        }
        // deleteNode deletes the given key from the node
        private rbNode deleteNode(rbNode node, RbKey key)
        {
            if (node == null)
            {
                return(null);
            }

            var cmp = key.ComparedTo(node.key);

            if (cmp == KeyComparison.KeyIsLess)
            {
                if (isBlack(node.left) && !isRed(node.left.left))
                {
                    node = moveRedLeft(node);
                }
                node.left = this.deleteNode(node.left, key);
            }
            else
            {
                if (cmp == KeyComparison.KeysAreEqual && this.onDelete != null)
                {
                    var value = this.onInsert(key, node.value);
                    if (value != null)
                    {
                        node.value = value;
                        return(node);
                    }
                }

                if (isRed(node.left))
                {
                    node = rotateRight(node);
                }

                if (isBlack(node.right) && !isRed(node.right.left))
                {
                    node = moveRedRight(node);
                }

                if (key.ComparedTo(node.key) != KeyComparison.KeysAreEqual)
                {
                    node.right = this.deleteNode(node.right, key);
                }
                else
                {
                    if (node.right == null)
                    {
                        return(null);
                    }

                    var rm = min(node.right);
                    node.key   = rm.key;
                    node.value = rm.value;
                    node.right = deleteMin(node.right);

                    rm.left  = null;
                    rm.right = null;

                    this.count--;
                }
            }
            return(balance(node));
        }