protected override void movingTo(long newLocation)
 {
     // if this node has been written before, our left sibling has a pointer to this node on disk.
     // update that sibling to point at the new location.
     if (this.Location != 0)
     {
         BPlusTreeLeafNode <TKey, TValue> left = GetLeftSibling(false) as BPlusTreeLeafNode <TKey, TValue>;
         if (left != null)
         {
             Tree.Lock(delegate() {
                 left.RightSiblingLocation = newLocation;
                 left.Write();
             });
         }
     }
 }
        private static BPlusTreeNode <TKey, TValue> processRead(BPlusTree <TKey, TValue> tree, BPlusTreeIndexNode <TKey, TValue> parent, BinaryReader rdr, object[] args)
        {
            // stream has been positioned appropriately.
            // just read from it.

            BPlusTreeNode <TKey, TValue> node = null;

            tree.Lock(delegate() {
                long startLocation = rdr.BaseStream.Position;
                int slotSize       = rdr.ReadInt32();
                bool leaf          = rdr.ReadBoolean();
                short keywordCount = rdr.ReadInt16();

                if (leaf)
                {
                    node = new BPlusTreeLeafNode <TKey, TValue>(tree, parent, rdr);
                }
                else
                {
                    node = new BPlusTreeIndexNode <TKey, TValue>(tree, parent, rdr);
                }
                node.Location = startLocation;

                // now we must read in all the keywords (they're always at the end of the stream)
                for (short i = 0; i < keywordCount; i++)
                {
                    TKey keyManager = new TKey();
                    keyManager.Read(rdr);
                    node.Keywords.Add(keyManager);
                }

                node.SlotSize = slotSize;

                // orient the stream pointer to the end of the node (which may be past the current data in it)
                rdr.BaseStream.Position = startLocation + node.SlotSize;
            });

            return(node);
        }
        internal BPlusTreeNode <TKey, TValue> SplitChild(BPlusTreeNode <TKey, TValue> originalChild, out int keywordIndex)
        {
            // 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 (since we're splitting it, it's guaranteed to get smaller)
            //            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.



            // first, determine the offset of the current originalchild in this (the parent)
            keywordIndex = 0;

            try {
                BPlusTree <TKey, TValue> .LogInfo("Begin splitting node=" + this.ToString() + ", child=" + originalChild.ToString());

                if (ChildLocations.Count > 0)
                {
                    // TODO: troubleshoot this -- after several thousand splits, originalChild.Location is no longer found in the ChildLocations array!!!
                    //if (LastChildLocation == originalChild.Location) {
                    //    // rightmost child, on a full node.
                    //} else {
                    while (keywordIndex < ChildLocations.Count && ChildLocations[keywordIndex] != originalChild.Location)
                    {
                        keywordIndex++;
                    }
                    //}
                }

                // assign the median keyword to the parent's Keywords list (inserts just before the current one)
                //if (keywordIndex > this.Keywords.Count) {
                //    // rightmost keyword, add to the end of the list
                //    this.Keywords.Add(originalChild.MedianKeyword);
                //} else {
                // not rightmost keyword, insert it at the proper position
                this.Keywords.Insert(keywordIndex, originalChild.MedianKeyword);
                //}

                if (originalChild.IsLeaf)
                {
                    // since this is a leaf, we will copy up the median keyword to the parent (as opposed to push up for index nodes)

                    // create a leaf node
                    BPlusTreeLeafNode <TKey, TValue> origNode = (BPlusTreeLeafNode <TKey, TValue>)originalChild;
                    BPlusTreeLeafNode <TKey, TValue> newNode  = new BPlusTreeLeafNode <TKey, TValue>(this.Tree, (BPlusTreeIndexNode <TKey, TValue>) this, null);

                    // copy right half of keywords + values from originalChild to newChild
                    newNode.Keywords.AddRange(origNode.Keywords.Skip(this.Tree.Median - 1));
                    newNode.Values.AddRange(origNode.Values.Skip(this.Tree.Median - 1));

                    // remove right half of keywords + values from originalChild
                    origNode.Keywords = origNode.Keywords.Take(this.Tree.Median - 1).ToList();
                    origNode.Values   = origNode.Values.Take(this.Tree.Median - 1).ToList();

                    var nextKeywordIndex = keywordIndex + 1;
                    Tree.Lock(delegate() {
                        // write out nodes so sibling locations can be set properly

                        // note: orig node should never relocate on write since we're making it smaller (moving 1/2 of its keywords to a new node)
                        if (origNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("orig leaf node relocated during split child: " + origNode.ToString());
                        }
                        if (newNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("new leaf node relocated during split child: " + newNode.ToString());
                        }

                        // adjust sibling locations (we always add the new child to the right of the original one)
                        newNode.RightSiblingLocation = origNode.RightSiblingLocation;
                        //				newNode.LeftSiblingLocation = origNode.Location;

                        origNode.RightSiblingLocation = newNode.Location;

                        ChildLocations.Insert(nextKeywordIndex, newNode.Location);

                        // write them out again so we save sibling locations
                        if (origNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("orig leaf node relocated on 2nd write during split child: " + origNode.ToString());
                        }
                        if (newNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("new leaf node relocated on 2nd write during split child: " + newNode.ToString());
                        }

                        // and write out the parent since it changed as well
                        if (Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("parent of leaf node relocated during split child: " + this.ToString());
                        }
                    });

                    return(newNode);
                }
                else
                {
                    // since this is an index node, we push up the keyword to the parent (as opposed to copy up for leaves)
                    // this means the median keyword will be removed from the original child but not moved to the new child

                    // create an index node
                    BPlusTreeIndexNode <TKey, TValue> origNode = (BPlusTreeIndexNode <TKey, TValue>)originalChild;
                    BPlusTreeIndexNode <TKey, TValue> newNode  = new BPlusTreeIndexNode <TKey, TValue>(this.Tree, (BPlusTreeIndexNode <TKey, TValue>) this, null);

                    // copy right half of keywords + childlocations from originalChild to newChild
                    // (since this is an index node, skip the median keyword
                    newNode.Keywords.AddRange(origNode.Keywords.Skip(this.Tree.Median));
                    newNode.ChildLocations.AddRange(origNode.ChildLocations.Skip(this.Tree.Median));

                    // remove right half of keywords + childlocations from originalChild
                    // (note we stop one shy of the median keyword -- this is because the median keyword needs to be "pushed up"
                    //  on an index node split.  However, we don't drop the corresponding ChildLocation)
                    origNode.Keywords       = origNode.Keywords.Take(this.Tree.Median - 1).ToList();
                    origNode.ChildLocations = origNode.ChildLocations.Take(this.Tree.Median).ToList();

                    var ki = keywordIndex;
                    Tree.Lock(delegate() {
                        if (origNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("orig index node relocated during split child: " + origNode.ToString());
                        }
                        if (newNode.Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("new index node relocated during split child: " + newNode.ToString());
                        }

                        // original node may have relocated on write...
                        ChildLocations[ki] = origNode.Location;

                        ChildLocations.Insert(ki + 1, newNode.Location);

                        // and write out the parent since it changed as well
                        if (Write())
                        {
                            BPlusTree <TKey, TValue> .LogInfo("parent of index node relocated during split child: " + this.ToString());
                        }
                    });

                    return(newNode);
                }
            } finally {
                BPlusTree <TKey, TValue> .LogInfo("End splitting node=" + this.ToString() + ", child=" + originalChild.ToString());
            }
        }
        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;
            }
        }
        protected internal override void performSearch(TKey keyword, KeywordMatchMode matchMode, bool ignoreCase, bool returnLeafIfNotFound, List <KeywordMatch <TKey, TValue> > matches)
        {
            // given a keyword, spin until the keyword is found (return null when not found)
            short lastMatchIndex = -2;

            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.
                    // means we're done looking in this leaf.
                    // if we didn't find anything and the caller said to return a leaf even if nothing was found, append this leaf
                    if (returnLeafIfNotFound && (matches.Count == 0 || matches[matches.Count - 1].Node != this))
                    {
                        // add this node as containing the hit even though it doesn't (so the value can be inserted)
                        // add this keyword as matching -- so they know where at in the node to insert their new item
                        matches.Add(new KeywordMatch <TKey, TValue> {
                            Index = i, Keyword = Keywords[i], Value = default(TValue), Node = this
                        });
                    }

                    return;
                }
                else if (compared == 0)
                {
                    // found keyword!
                    // add this node if it's not already in the list
                    // we may have multiple hits, so don't return yet.
                    // just add it to the list of matches.
                    matches.Add(new KeywordMatch <TKey, TValue> {
                        Index = i, Keyword = Keywords[i], Value = Values[i], Node = this
                    });
                    lastMatchIndex = i;
                }
                else
                {
                    // compared > 0
                    // given keyword is larger than this entry.
                    // keep checking.
                }
            }



            var inspectRightLeaf = false;

            switch (matchMode)
            {
            //case KeywordMatchMode.Contains:
            //case KeywordMatchMode.EndsWith:
            //    // data isn't stored in this order, so we always check the node (assuming there is one)
            //    inspectRightLeaf = true;
            //    break;
            case KeywordMatchMode.ExactMatch:
                // nothing more to do -- if the exact match occurred in this node, it won't occur in another node.
                break;

            case KeywordMatchMode.StartsWith:
            case KeywordMatchMode.Contains:
            case KeywordMatchMode.EndsWith:
                if (lastMatchIndex == Keywords.Count - 1)
                {
                    // last keyword matched, next leaf might have info in it.
                    inspectRightLeaf = true;
                }
                break;
            }



            if (inspectRightLeaf)
            {
                var nextLeaf = BPlusTreeLeafNode <TKey, TValue> .Read(this.Tree, null, this.RightSiblingLocation) as BPlusTreeLeafNode <TKey, TValue>;

                if (nextLeaf != null)
                {
                    // we're not on the rightmost node, inspect the next one
                    nextLeaf.performSearch(keyword, matchMode, ignoreCase, returnLeafIfNotFound, matches);
                }
                else
                {
                    // we get here, no more leaves exist.
                    // just return, the matches collection is filled properly already.
                }
            }
            else
            {
                // we get here, it was bigger than our biggest keyword
                // Since we're a leaf, it doesn't exist.
                if (returnLeafIfNotFound)
                {
                    // add this keyword as matching -- so they know where at in the node to insert their new item (note Index = Keywords.Count, so it'll be tacked on the end)
                    matches.Add(new KeywordMatch <TKey, TValue> {
                        Index = Keywords.Count, Keyword = default(TKey), Value = default(TValue), Node = this
                    });
                }
            }
        }