protected internal override void performSearch(TKey keyword, KeywordMatchMode matchMode, bool ignoreCase, bool returnLeafIfNotFound, List <KeywordMatch <TKey, TValue> > matches)
        {
            try {
                BPlusTree <TKey, TValue> .LogInfo("Begin performSearch node=" + this.ToString() + " keyword=" + keyword.ToString() + ", returnLeafIfNotFound=" + returnLeafIfNotFound);

                int childIndex = ChildLocations.Count - 1;

                // given a keyword, drill until the containing leaf node is found (return null when not found)
                if (matchMode == KeywordMatchMode.Contains || matchMode == KeywordMatchMode.EndsWith)
                {
                    // Contains and EndsWith must check all leaf nodes since the data is ordered alphabetically
                    // left-to-right.  So we can skip checking keywords because we know we need to end up at the leftmost leaf node
                    childIndex = 0;
                }
                else
                {
                    for (short i = 0; i < Keywords.Count; i++)
                    {
                        int compared = keyword.CompareTo(Keywords[i], matchMode, ignoreCase);
                        if (compared < 0)
                        {
                            // given keyword is smaller than this entry.
                            // follow the left child.
                            childIndex = i;
                            break;
                        }
                        else if (compared == 0)
                        {
                            if (matchMode == KeywordMatchMode.StartsWith)
                            {
                                // special case!  startswith doesn't mean there aren't more values to the left.
                                // so, we go left instead of right.
                                childIndex = i;
                            }
                            else
                            {
                                // given keyword is == this entry.
                                // follow right child.
                                childIndex = i + 1;
                            }
                            break;
                        }
                        else
                        {
                            // keep checking...
                        }
                    }
                }



                // either we found the proper index, or we ran out of keywords and the rightmost index is the proper index.
                // read in that child, split it if needed (if we're inserting), and search down that child
                BPlusTreeNode <TKey, TValue> child = BPlusTreeNode <TKey, TValue> .Read(this.Tree, this, ChildLocations[childIndex]);

                if (!returnLeafIfNotFound)
                {
                    // just continue the search down the child. if the node is full or not makes no difference -- they don't want us
                    // to return the leaf if it's not found anyway.
                    child.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches);
                }
                else
                {
                    if (!child.IsFull)
                    {
                        // just continue the search down the child. we might have to perform an insert somewhere down the line,
                        // but there's still room in this particular node so we shouldn't split it.
                        child.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches);
                    }
                    else
                    {
                        // child node is full and they want us to return a node even if not found -- they're inserting.
                        // split the child, then continue the search down the appropriate one

                        // NOTE: this does pre-emptive node splits -- meaning we split on the way down instead of
                        //       splitting on the way back up.  this is common practice to help increase concurrency.
                        int newKeywordIndex = 0;
                        BPlusTreeNode <TKey, TValue> newChild = this.SplitChild(child, out newKeywordIndex);

                        int compared = keyword.CompareTo(Keywords[newKeywordIndex], matchMode, ignoreCase);
                        if (compared < 0)
                        {
                            // new keyword is less than the keyword that was just copied/pushed into the parent (current) node.
                            // that new keyword belongs in the original child (left child)
                            child.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches);
                        }
                        else
                        {
                            // keep checking...
                            newChild.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches);
                        }
                    }
                }
            } finally {
                BPlusTree <TKey, TValue> .LogInfo("End performSearch node=" + this.ToString() + " keyword=" + keyword.ToString() + ", returnLeafIfNotFound=" + returnLeafIfNotFound);
            }
        }
        protected BPlusTreeNode <TKey, TValue> GetLeftSibling(bool mustHaveSameParent)
        {
            // look in parent for our smallest keyword, grab childlocation to the left of it

            int parentChildCount;
            int childLocationIndex = GetIndexInParent(out parentChildCount);

            if (childLocationIndex == -1)
            {
                // never found the index, or no parent (aka root). No sibling to return.
                return(null);
            }
            else
            {
                if (childLocationIndex == 0)
                {
                    if (mustHaveSameParent)
                    {
                        // caller wants the sibling only if it's from the same parent, and it's not.
                        return(null);
                    }
                    else
                    {
                        // we have to find the parent of our left sibling.
                        // spin up until we find the first node whose child index is > 0
                        // then drill down the rightmost path until we're at the same depth as we started at
                        int i = 0;
                        int nodeChildLocationIndex        = -1;
                        BPlusTreeNode <TKey, TValue> node = this;
                        while (node.Parent != null)
                        {
                            i++;

                            // keep bubbling up until we're at a node whose parent has at least one child to our left.
                            nodeChildLocationIndex = node.GetIndexInParent(out parentChildCount);
                            if (nodeChildLocationIndex > 0)
                            {
                                // this node is not the leftmost child of its parent.
                                // we're done bubbling (node contains the ancestor node, nodeChildLocationIndex can tell us the subtree to search down)
                                break;
                            }
                            else
                            {
                                node = node.Parent;
                            }
                        }

                        if (nodeChildLocationIndex == 0)
                        {
                            // the only common ancestor has our node being the leftmost node in the tree (at this depth).
                            return(null);
                        }

                        // ok, node contains the ancestor somehwere up the tree that has the node we're looking for as its
                        // rightmost descendent at the same depth.
                        // So we just keep drilling down until we're at the same depth.
                        node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, node as BPlusTreeIndexNode <TKey, TValue>, nodeChildLocationIndex - 1);

                        while (i > 0)
                        {
                            BPlusTreeIndexNode <TKey, TValue> indexNode = node as BPlusTreeIndexNode <TKey, TValue>;
                            node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, indexNode, indexNode.LastChildLocation);

                            i--;
                        }

                        // node now contains the left sibling!
                        return(node);
                    }
                }
                else
                {
                    // we know the left sibling is attached to the same parent.
                    BPlusTreeNode <TKey, TValue> node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, this.Parent, Parent.ChildLocations[childLocationIndex - 1]);

                    return(node);
                }
            }
        }
        protected BPlusTreeNode <TKey, TValue> GetRightSibling(bool mustHaveSameParent)
        {
            if (this.IsLeaf)
            {
                // use the rightsibling pointer
                BPlusTreeLeafNode <TKey, TValue> leaf = this as BPlusTreeLeafNode <TKey, TValue>;
                return(BPlusTreeNode <TKey, TValue> .Read(this.Tree, this.Parent, leaf.RightSiblingLocation));
            }
            else
            {
                // look in parent for our smallest keyword, grab childlocation to the left of it
                int parentChildCount;
                int childLocationIndex = GetIndexInParent(out parentChildCount);

                if (childLocationIndex == -1)
                {
                    // never found the index, or no parent (aka root). No sibling to return.
                    return(null);
                }
                else
                {
                    if (childLocationIndex < parentChildCount - 1)
                    {
                        // we know the right sibling is attached to the same parent.
                        BPlusTreeNode <TKey, TValue> node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, this.Parent, Parent.ChildLocations[childLocationIndex + 1]);

                        return(node);
                    }
                    else
                    {
                        // we are the rightmost child, as we're at the location that's 1 past the fanout size
                        if (mustHaveSameParent)
                        {
                            // caller wants the sibling only if it's from the same parent, and it's not.
                            return(null);
                        }
                        else
                        {
                            // we have to find the common ancestor between this and its right sibling.
                            // spin up until we find the first node whose child index is <= Fanoutsize
                            // then drill down the leftmost path until we're at the same depth as we started at
                            int i = 0;
                            int nodeChildLocationIndex        = -1;
                            BPlusTreeNode <TKey, TValue> node = this;
                            while (node.Parent != null)
                            {
                                i++;

                                // keep bubbling up until we're at a node whose parent has at least one child to our right.
                                nodeChildLocationIndex = node.GetIndexInParent(out parentChildCount);
                                if (nodeChildLocationIndex < parentChildCount - 1)
                                {
                                    // this node is not the rightmost child of its parent.
                                    // we're done bubbling (node contains the ancestor node, nodeChildLocationIndex can tell us the subtree to search down)
                                    break;
                                }
                                else
                                {
                                    node = node.Parent;
                                }
                            }

                            if (nodeChildLocationIndex == parentChildCount - 1)
                            {
                                // the only common ancestor has our node being the rightmost node in the tree (at this depth).
                                return(null);
                            }


                            // ok, node contains the ancestor somehwere up the tree that has the node we're looking for as its
                            // leftmost descendent at the same depth.
                            // So we just keep drilling down the leftmost path until we're at the same depth.
                            node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, node as BPlusTreeIndexNode <TKey, TValue>, nodeChildLocationIndex + 1);

                            while (i > 0)
                            {
                                BPlusTreeIndexNode <TKey, TValue> indexNode = node as BPlusTreeIndexNode <TKey, TValue>;
                                node = BPlusTreeNode <TKey, TValue> .Read(this.Tree, indexNode, indexNode.ChildLocations[0]);

                                i--;
                            }

                            // node now contains the right sibling!
                            return(node);
                        }
                    }
                }



                //// bounce up to the parent
                //if (Parent != null) {
                //    TKey ourKey = FirstKeyword;
                //    for (int i=0; i < Parent.Keywords.Count; i++) {
                //        TKey parentKeyword = Parent.Keywords[i];
                //        if (parentKeyword.CompareTo(ourKey, KeywordMatchMode.ExactMatch, false) == 0) {
                //            // follow right child
                //            BPlusTreeNode<TKey, TValue> node = BPlusTreeNode<TKey, TValue>.Read(this.Tree, this.Parent, Parent.ChildLocations[i+1]);
                //            return node;
                //        }
                //    }
                //}
                //return null;
            }
        }
        /// <summary>
        /// Performs a search for the given keyword using the given matchMode/ignoreCase.  For each item that matches, the callback is called, passing the current node, keyword offset, and the given additionalData with it.  This allows us to reuse the somewhat complex search code for searching and updating.
        /// </summary>
        /// <param name="keyword"></param>
        /// <param name="matchMode"></param>
        /// <param name="ignoreCase"></param>
        /// <param name="callback"></param>
        /// <param name="additionalData"></param>
        private void performSearch(string keyword, KeywordMatchMode matchMode, bool ignoreCase, SearchCallback callback, object additionalData)
        {
            int i        = 0;
            int compared = -1;

            // NOTE: We have the switch and as many of the if statements outside the loops as we can so we minimize branching within the loops.
            //       We'll be spinning through possibly millions of times so we're sacrificing cleaner, more intuitive code for performance here

            switch (matchMode)
            {
            case KeywordMatchMode.ExactMatch:
                // exact match.  using String.Compare instead of == allows us to jump out as soon as
                // we find a string "greater than" the search keyword.  If we just used ==, we'd have to always compare the entire
                // set of _keywords in this node if none was ever found.  Since once we find a match we stop, it's okay to put the IsLeaf check within the loop.

                // ExactMatch, regardless of Leafiness, regardless of case sensitivity
                while (i < KeywordCount && compared < 1)
                {
                    compared = String.Compare(Keywords[i], keyword, ignoreCase, CultureInfo.CurrentUICulture);
                    if (compared == 0)
                    {
                        // found our exact match
                        if (IsLeaf)
                        {
                            callback(this, i, additionalData);

                            // there may be multiple entries for a given keyword (unlikely, but possible)
                            // so do NOT jump out.
                        }
                        else
                        {
                            // continue searching the associated child node...
                            BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                            node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                            // since this node is not a leaf, this means a given keyword will not appear more than once.
                            // so once we drill down, we're done.
                            return;
                        }
                    }
                    else if (compared > 0)
                    {
                        // this keyword is bigger than ours -- i.e. we know it's not in this node.
                        // if we're not a leaf, drill down...
                        if (!IsLeaf)
                        {
                            // continue searching the associated child node...
                            BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                            node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                        }
                        // we've already passed where it could be an exact match in this node. jump out.
                        return;
                    }
                    else
                    {
                        // this keyword is "less than" our search keyword.  keep looking in this node.
                    }
                    i++;
                }
                if (!IsLeaf && i == KeywordCount)
                {
                    // we ran out of keywords to compare against -- means
                    // all keywords in this node are "less than" than our search keyword.
                    // if there's a right child, inspect it.
                    BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                    node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                }
                break;

            case KeywordMatchMode.StartsWith:

                if (IsLeaf)
                {
                    // StartsWith, IsLeaf, regardless of case sensitivity (case sensitivity is handled by comparisonType variable)

                    while (i < KeywordCount)
                    {
                        if (Keywords[i].StartsWith(keyword, ignoreCase, CultureInfo.CurrentUICulture))
                        {
                            callback(this, i, additionalData);
                            // since this is a startswith (i.e. fuzzy on right)
                            // we should just inspect the entire node (do not jump out)
                        }
                        i++;
                    }
                }
                else
                {
                    // StartsWith, Is NOT Leaf, regardless of case sensitivity (case sensitivity is handled by comparisonType variable)

                    while (i < KeywordCount)
                    {
                        if (Keywords[i].StartsWith(keyword, ignoreCase, CultureInfo.CurrentUICulture))
                        {
                            BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                            node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                            // since this is a startswith (i.e. fuzzy on right)
                            // we should just inspect the entire node (do not jump out)
                        }
                        else if (String.Compare(Keywords[i], keyword, ignoreCase, CultureInfo.CurrentUICulture) > 0)
                        {
                            // this keyword doesn't start with our search keyword, but it is "greater than" it.
                            // continue searching children
                            BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                            node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                            return;
                        }
                        i++;
                    }
                    if (i == KeywordCount && ChildrenByteOffsets[i] > 0)
                    {
                        // we ran off the end. inspect rightmost child.
                        BPlusTreeNode node = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);
                        node.performSearch(keyword, matchMode, ignoreCase, callback, additionalData);
                        return;
                    }
                }
                break;

            case KeywordMatchMode.EndsWith:

                // Since our index is built by the beginning of the word, we essentially have to do an index scan.
                // we'll use the tree's TraverseLeaves() method to do this efficiently.

                foreach (BPlusTreeNode node in _tree.TraverseLeaves())
                {
                    for (int j = 0; j < node.KeywordCount; j++)
                    {
                        if (node.Keywords[j].EndsWith(keyword, ignoreCase, CultureInfo.CurrentUICulture))
                        {
                            // this does end with what we're looking for...
                            callback(node, j, additionalData);

                            // since this is a endswith (i.e. fuzzy on left)
                            // we should just inspect the entire node (do not jump out)
                        }
                    }
                }


                break;

            case KeywordMatchMode.Contains:

                // Since our index is built by the beginning of the word, we essentially have to do an index scan.
                // we'll use the tree's TraverseLeaves() method to do this efficiently.

                if (ignoreCase)
                {
                    // Contains, Case Insensitive, regardless of leafiness

                    keyword = keyword.ToLower();
                    foreach (BPlusTreeNode node in _tree.TraverseLeaves())
                    {
                        for (int j = 0; j < node.KeywordCount; j++)
                        {
                            if (node.Keywords[j].ToLower().Contains(keyword))
                            {
                                // this does contain what we're looking for...
                                callback(node, j, additionalData);
                                // since this is a endswith (i.e. fuzzy on left)
                                // we should just inspect the entire node (do not jump out)
                            }
                        }
                    }
                }
                else
                {
                    // Contains, Case Sensitive, regardless of leafiness

                    foreach (BPlusTreeNode node in _tree.TraverseLeaves())
                    {
                        for (int j = 0; j < node.KeywordCount; j++)
                        {
                            if (node.Keywords[j].Contains(keyword))
                            {
                                // this does contain what we're looking for...
                                callback(node, j, additionalData);
                                // since this is a endswith (i.e. fuzzy on left)
                                // we should just inspect the entire node (do not jump out)
                            }
                        }
                    }
                }
                break;
            }
        }
        internal BPlusTreeNode SplitChild(int childOffset, BPlusTreeNode originalChild, BPlusTreeNode grandParent)
        {
            // Split the current node into two nodes:
            //   a. Left node contains all pointers/keywords to the left of the median
            //   b. Right node contains all pointers/keywords to the right of the median
            //   c. Parent node gets median keyword pushed into it
            //
            // Disk-wise: the originalChild is always written back to its original position.
            //            the newChild is placed into either an available abandoned node or tacked on the end of the file.
            //            the parent node is written back to its original position IF IT FITS.  Since we're coyping a keyword
            //               up into the parent node, it may cause it to outgrow its allocated size in the file.  If this occurs,
            //               the call to this.Write() implicitly calls relocate().  relocate() will then either write the parent to
            //               an abandoned node or tack it onto the end of the file.

            _tree.Log("------------ BEGIN Splitting node ------------");
            _tree.Log("BEGIN Splitting node parent -> ".PadRight(50) + ToString());
            _tree.Log("BEGIN Splitting node child -> ".PadRight(50) + originalChild.ToString());

            BPlusTreeNode newChild = BPlusTreeNode.CreateNode(_tree);

            newChild.IsLeaf = originalChild.IsLeaf;

            newChild.KeywordCount = _tree.Fanout - _tree.MinimumChildren;

            // assign items left of median
            for (int i = 0; i < newChild.KeywordCount; i++)
            {
                // Left half of original child already has appropriate values
                // However, left half of new child needs data from right half of original child
                newChild.ChildrenByteOffsets[i] = originalChild.ChildrenByteOffsets[i + _tree.Median + 1];
                newChild.Keywords[i]            = originalChild.Keywords[i + _tree.Median + 1];
            }
            newChild.ChildrenByteOffsets[newChild.KeywordCount] = originalChild.ChildrenByteOffsets[_tree.Fanout];

            // adjust the new child's relative info as needed (we always add to the right)
            if (originalChild.IsLeaf)
            {
                // only leaves should track siblings
                newChild.LeftSiblingFileOffset  = originalChild.FileOffset;
                newChild.RightSiblingFileOffset = originalChild.RightSiblingFileOffset;
            }
            else
            {
                newChild.LeftSiblingFileOffset  = -1;
                newChild.RightSiblingFileOffset = -1;
            }


            // make room in parent for the median values
            // (copy all those above the childOffset over one)

            for (int i = KeywordCount; i > childOffset; i--)
            {
                ChildrenByteOffsets[i + 1] = ChildrenByteOffsets[i];
                Keywords[i] = Keywords[i - 1];
            }
            KeywordCount++;

            Keywords[childOffset] = originalChild.Keywords[_tree.Median];
            // just set byterange that points at our new child to the default for now.  we'll assign it the proper value later (once we know the true file offsets)
            ChildrenByteOffsets[childOffset + 1] = 0;

            // if we're splitting a leaf node, we must copy up the median value.
            // if we're splitting an index node, we must simply push it up (i.e. do not leave a copy in the original child)
            int startErasingAt = _tree.MinimumChildren;

            if (!originalChild.IsLeaf)
            {
                // index node, erase the keyword (as we essentially want to push it up)
                startErasingAt--;
            }

            // assign items right of median (note this effectively means we copy up the median value, not move it up)
            for (int i = startErasingAt; i < _tree.Fanout; i++)
            {
                // Left half of newChild already has proper values (nulls/defaults)
                // However, right half of originalChild still has the values we just copied to the left half of newChild.
                // null/default those values.
                if (originalChild.IsLeaf || i > startErasingAt)
                {
                    originalChild.ChildrenByteOffsets[i] = 0;
                }
                originalChild.Keywords[i] = String.Empty;
            }
            // there's always one more child than keyword...
            originalChild.ChildrenByteOffsets[_tree.Fanout] = 0;
            originalChild.KeywordCount = startErasingAt;

            // determine where to put this new child (in an abandoned node or tack onto end of file)
            BPlusAbandonedNode abandonedNode = _tree.GetNextAvailableNodeLocation(newChild);

            //newChild._chunkSize = abandonedNode.ByteCount;
            newChild.FileOffset = abandonedNode.FileOffset;

            newChild.Write(this);


            // the original child's right sibling must be updated to point at the new child instead
            if (originalChild.RightSiblingFileOffset > 0 && originalChild.IsLeaf)
            {
                BPlusTreeNode originalRightSibling = BPlusTreeNode.Read(_tree, originalChild.RightSiblingFileOffset, true);
                originalRightSibling.LeftSiblingFileOffset = newChild.FileOffset;
                originalRightSibling.Write(null);
            }



            // now that we've written the new child out,
            // we know what to set the originalChild's right sibling to...
            if (originalChild.IsLeaf)
            {
                originalChild.RightSiblingFileOffset = newChild.FileOffset;
            }
            originalChild.Write(this);


            // we now know all the offsets we need to update in the parent...
            ChildrenByteOffsets[childOffset]     = originalChild.FileOffset;
            ChildrenByteOffsets[childOffset + 1] = newChild.FileOffset;

            // this node may relocate because we're adding a keyword to it.
            // if it does, we have to be sure our direct parent knows about it (so it can update its child file offset that points to us)
            // hence the "grandParentNode".  Write() handles updating things correctly if we give it our parent (which is our children's grandparent) :)
            if (Write(grandParent))
            {
                //Debug.WriteLine("would have missed this before");
            }

            _tree.Log("END Splitting node parent -> ".PadRight(50) + ToString());
            _tree.Log("END Splitting node. original child -> ".PadRight(50) + originalChild.ToString());
            _tree.Log("END Splitting node. new child -> ".PadRight(50) + newChild.ToString());
            _tree.Log("------------ END Splitting node ------------");

            return(newChild);



            /*
             * newChild <- Allocate-Node()
             * leaf[newChild] <- leaf[parent]
             * n[newChild] <- t - 1
             * for j <- 1 to t - 1
             *       do keyj[newChild] <- keyj+t[parent]
             * if not leaf[parent]
             *       then for j <- 1 to t
             *                do cj[newChild] <- cj+t[parent]
             * n[parent] <- t - 1
             * for j <- n[originalChild] + 1 downto i + 1
             *       do cj+1[originalChild] <- cj[originalChild]
             * ci+1 <- newChild
             * for j <- n[originalChild] downto i
             *       do keyj+1[originalChild] <- keyj[originalChild]
             * keyi[originalChild] <- keyt[parent]
             * n[originalChild] <- n[originalChild] + 1
             * Disk-Write(parent)
             * Disk-Write(newChild)
             * Disk-Write(originalChild)
             *
             */
        }
        internal bool Insert(string keyword, long dataByteOffset, BPlusTreeNode parentNode)
        {
            //i <- n[x]
            //if leaf[x]
            //     then while i >= 1 and k < keyi[x]
            //            do keyi+1[x] <- keyi[x]
            //               i <- i - 1
            //          keyi+1[x] <- k
            //          n[x] <- n[x] + 1
            //          Disk-Write(x)
            //     else while i >= and k < keyi[x]
            //            do i <- i - 1
            //          i <- i + 1
            //          Disk-Read(ci[x])
            //          if n[ci[x]] = 2t - 1
            //               then B-Tree-Split-Child(x, i, ci[x])
            //                    if k > keyi[x]
            //                       then i <- i + 1
            //          B-Tree-Insert-Nonfull(ci[x], k)

            int i = KeywordCount - 1;

            if (IsLeaf)
            {
                while (i > -1 && String.Compare(keyword, Keywords[i], false) < 0)
                {
                    Keywords[i + 1]            = Keywords[i];
                    ChildrenByteOffsets[i + 1] = ChildrenByteOffsets[i];
                    i--;
                }
                Keywords[i + 1]            = keyword;
                ChildrenByteOffsets[i + 1] = dataByteOffset;
                KeywordCount++;
                return(Write(parentNode));
            }
            else
            {
                while (i > -1 && String.Compare(keyword, Keywords[i], false) < 0)
                {
                    i--;
                }
                i++;
                BPlusTreeNode child = BPlusTreeNode.Read(_tree, ChildrenByteOffsets[i], true);

                _tree.Log(("Inspecting node @ " + child.FileOffset + " -> ").PadRight(50) + child.ToString());
                if (!child.IsFull)
                {
                    return(child.Insert(keyword, dataByteOffset, this));
                }
                else
                {
                    // must first split the child
                    BPlusTreeNode newChild = SplitChild(i, child, parentNode);
                    if (String.Compare(keyword, newChild.Keywords[0], false) < 0)
                    {
                        // new keyword belongs somewhere below the original child
                        return(child.Insert(keyword, dataByteOffset, this));
                    }
                    else
                    {
                        // new keyword belongs somewhere below the new child
                        return(newChild.Insert(keyword, dataByteOffset, this));
                    }
                }
            }
        }
        private void relocate(BPlusTreeNode parentNode)
        {
            // this node has outgrown its allotted space.
            // we need to move it to the end of the file and update everybody who points to it

            // so have to update the following:
            // 1. Parent node's pointer
            // 2. Left sibling's pointer
            // 3. Right sibling's pointer
            // 4. All children's parent pointer (which points at this node)

            _tree.Log("------------ BEGIN Relocating node ------------");
            _tree.Log("BEGIN Relocating node -> ".PadRight(50) + ToString());

            // get an abandoned node (or the end of the file, whichever comes first)
            BPlusAbandonedNode abandonedNode = _tree.GetNextAvailableNodeLocation(this);

            // mark this node as abandoned in the file (and remove from cache)
            _tree.AbandonNode(this);

            //if (this.IsRoot) {
            //    // we're relocating the root.
            //    _tree.Log("TODO: what to do here, if anything?");
            //}

            if (!IsLeaf)
            {
                // only leaf nodes should track left/right siblings.
                LeftSiblingFileOffset  = -1;
                RightSiblingFileOffset = -1;
            }

            if (LeftSiblingFileOffset > -1)
            {
                BPlusTreeNode originalLeftSibling = BPlusTreeNode.Read(_tree, this.LeftSiblingFileOffset, true);
                originalLeftSibling.RightSiblingFileOffset = originalLeftSibling.IsLeaf ? abandonedNode.FileOffset : -1;
                _tree.Log(("Updating left sibling node @ " + originalLeftSibling.FileOffset + " -> ").PadRight(50) + originalLeftSibling.ToString());
                originalLeftSibling.Write(null);
            }
            if (RightSiblingFileOffset > -1)
            {
                BPlusTreeNode originalRightSibling = BPlusTreeNode.Read(_tree, this.RightSiblingFileOffset, true);
                originalRightSibling.LeftSiblingFileOffset = originalRightSibling.IsLeaf ? abandonedNode.FileOffset : -1;
                _tree.Log(("Updating right sibling node @ " + originalRightSibling.FileOffset + " -> ").PadRight(50) + originalRightSibling.ToString());
                originalRightSibling.Write(null);
            }

            if (this.IsRoot)
            {
                // special case!  no parent means this is the root node.
                // update the int at the very beginning of the file to point at the new root node
                _tree.WriteRootNodeOffset(abandonedNode.FileOffset);
            }
            else
            {
                if (parentNode != null)
                {
                    for (int i = 0; i < parentNode.KeywordCount + 1; i++)
                    {
                        if (parentNode.ChildrenByteOffsets[i] == FileOffset)
                        {
                            parentNode.ChildrenByteOffsets[i] = abandonedNode.FileOffset;
                            _tree.Log(("Updating parent node @ " + parentNode.FileOffset + " -> ").PadRight(50) + parentNode.ToString());
                            parentNode.Write(null);
                            // the Root node is cached in the tree object -- udpate it if need be
                            if (parentNode.IsRoot)
                            {
                                _tree.Root = parentNode;
                            }
                            break;
                        }
                    }
                }
            }


            _tree.Writer.BaseStream.Position = abandonedNode.FileOffset;
            FileOffset = abandonedNode.FileOffset;
            //_chunkSize = abandonedNode.ByteCount;

            _tree.Log("END Relocating node (not written yet) -> ".PadRight(50) + ToString());
            _tree.Log("------------ END Relocating node ------------");
        }
        // here's the vernacular:
        // The _keywords array represents all the keywords in this node.
        // The _byteRanges array represents all the file offsets in this node for either (a) a keyword or (b) the data itself.
        //   a. If it is located in an index node (i.e. non-Leaf), the byte range represents the start/end offsets in the index file (used for traversing to other nodes)
        //   b. If it is located in a Leaf node, the byte range represents the start/end offsets in the data file (for looking up associated hit(s))


        // Think ByteRange = pointer to next node or data, Keyword = item we're searching by
        // So we always have 1 more pointer (or byterange, BR) than search item (or keyword, K):
        //
        // |-------- Node ---------|
        // |      go       pi      |
        // |  /   |    |   |    \  |
        // | BR1  K1  BR2  K2  BR3 |
        // |-----------------------|


        /// <summary>
        /// Creates a new root node for the tree and makes the original root the first child of the new root.
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        internal static BPlusTreeNode ReplaceRoot(BPlusTree tree, BPlusTreeNode originalRoot, string keyword, long dataByteOffset)
        {
            // NOTE: This is a very special case.
            // replacing the root involves creating 2 new nodes:
            //  1. the new root node
            //  2. splitting the existing root node into 2 nodes, one of which is a new one

            BPlusTreeNode newRoot = null;

            // create a new root node, mark it as not a leaf, put it in exact same spot as original root
            newRoot = new BPlusTreeNode {
                IsLeaf                 = false,
                _tree                  = tree,
                Keywords               = new string[tree.Fanout],
                ChildrenByteOffsets    = new long[tree.Fanout + 1],
                KeywordCount           = 0,
                FileOffset             = -1,
                LeftSiblingFileOffset  = -1,
                RightSiblingFileOffset = -1,
            };

            tree.Log("------------ BEGIN Replacing root ------------");
            tree.Log("BEGIN Replacing root. original node -> ".PadRight(50) + originalRoot.ToString());
            tree.Log("BEGIN Replacing root. new node -> ".PadRight(50) + newRoot.ToString());


            // add our new root to file, update the root node pointer
            BPlusAbandonedNode abandoned = tree.GetNextAvailableNodeLocation(originalRoot);

            newRoot.FileOffset = abandoned.FileOffset;
            newRoot.Write(null);
            tree.WriteRootNodeOffset(newRoot.FileOffset);


            // trickle-down insert the new keyword/data
            if (!String.IsNullOrEmpty(keyword))
            {
                // split the original
                BPlusTreeNode newChild = newRoot.SplitChild(0, originalRoot, null);

                tree.Log("CONTINUE Replacing root. new child node -> ".PadRight(50) + originalRoot.ToString());


                if (newRoot.Insert(keyword, dataByteOffset, null))
                {
                    // the new root was relocated during insertion (outgrew its allotted space)
                    // the insert should take care of it

                    // re-read it in just in case
                    newRoot = BPlusTreeNode.Read(tree, newRoot.FileOffset, true);
                }
            }
            else
            {
                newRoot = BPlusTreeNode.Read(tree, newRoot.FileOffset, true);
            }


            tree.Log("END Replacing root. original node -> ".PadRight(50) + originalRoot.ToString());
            //			_tree.Log("END Replacing root. new child node -> ".PadRight(50) + newChild.ToString());
            tree.Log("END Replacing root. new root -> ".PadRight(50) + newRoot.ToString());
            tree.Log("------------ END Replacing root ------------");

            return(newRoot);
        }