private IEnumerable <T> EnumerateRecords(BTree2Node <T> node, ushort nodeLevel)
        {
            // This method could be rearranged to accept a BTree2NodePointer (instead of the root node).
            // In that case it would be possible to simplify the double check for internal/leaf node.

            // internal node
            var internalNode = node as BTree2InternalNode <T>;

            if (internalNode is not null)
            {
                var records = node.Records
                              .Cast <T>()
                              .ToList();

                var nodePointers = internalNode.NodePointers;

                for (int i = 0; i < nodePointers.Length; i++)
                {
                    // there is one more node pointer than records
                    if (i < records.Count)
                    {
                        yield return(records[i]);
                    }

                    var nodePointer = nodePointers[i];
                    this.Reader.Seek((long)nodePointer.Address, SeekOrigin.Begin);
                    var childNodeLevel = (ushort)(nodeLevel - 1);

                    IEnumerable <T> childRecords;

                    // internal node
                    if (childNodeLevel > 0)
                    {
                        var childNode = new BTree2InternalNode <T>(this.Reader, _superblock, this, nodePointer.RecordCount, childNodeLevel, _decodeKey);
                        childRecords = this.EnumerateRecords(childNode, childNodeLevel);
                    }
                    // leaf node
                    else
                    {
                        var childNode = new BTree2LeafNode <T>(this.Reader, this, nodePointer.RecordCount, _decodeKey);
                        childRecords = childNode.Records;
                    }

                    foreach (var record in childRecords)
                    {
                        yield return(record);
                    }
                }
            }
            // leaf node
            else
            {
                foreach (var record in node.Records)
                {
                    yield return(record);
                }
            }
        }
        public bool TryFindRecord(out T result, Func <T, int> compare)
        {
            /* H5B2.c (H5B2_find) */
            int  cmp;
            uint index = 0;
            BTree2NodePosition curr_pos;

            result = default;

            /* Make copy of the root node pointer to start search with */
            var currentNodePointer = this.RootNodePointer;

            /* Check for empty tree */
            if (currentNodePointer.RecordCount == 0)
            {
                return(false);
            }

#warning Optimizations missing.

            /* Current depth of the tree */
            var depth = this.Depth;

            /* Walk down B-tree to find record or leaf node where record is located */
            cmp      = -1;
            curr_pos = BTree2NodePosition.Root;

            while (depth > 0)
            {
                this.Reader.Seek((long)currentNodePointer.Address, SeekOrigin.Begin);
                var internalNode = new BTree2InternalNode <T>(this.Reader, _superblock, this, currentNodePointer.RecordCount, depth, _decodeKey);

                if (internalNode is null)
                {
                    throw new Exception("Unable to load B-tree internal node.");
                }

                /* Locate node pointer for child */
                (index, cmp) = this.LocateRecord(internalNode.Records, compare);

                if (cmp > 0)
                {
                    index++;
                }

                if (cmp != 0)
                {
                    /* Get node pointer for next node to search */
                    var nextNodePointer = internalNode.NodePointers[index];

                    /* Set the position of the next node */
                    if (curr_pos != BTree2NodePosition.Middle)
                    {
                        if (index == 0)
                        {
                            if (curr_pos == BTree2NodePosition.Left || curr_pos == BTree2NodePosition.Root)
                            {
                                curr_pos = BTree2NodePosition.Left;
                            }
                            else
                            {
                                curr_pos = BTree2NodePosition.Middle;
                            }
                        }
                        else if (index == internalNode.Records.Length)
                        {
                            if (curr_pos == BTree2NodePosition.Right || curr_pos == BTree2NodePosition.Root)
                            {
                                curr_pos = BTree2NodePosition.Right;
                            }
                            else
                            {
                                curr_pos = BTree2NodePosition.Middle;
                            }
                        }
                        else
                        {
                            curr_pos = BTree2NodePosition.Middle;
                        }
                    }

                    currentNodePointer = nextNodePointer;
                }
                else
                {
                    result = internalNode.Records[index];
                    return(true);
                }

                /* Decrement depth we're at in B-tree */
                depth--;
            }

            {
                this.Reader.Seek((long)currentNodePointer.Address, SeekOrigin.Begin);
                var leafNode = new BTree2LeafNode <T>(this.Reader, this, currentNodePointer.RecordCount, _decodeKey);

                /* Locate record */
                (index, cmp) = this.LocateRecord(leafNode.Records, compare);

                if (cmp == 0)
                {
                    result = leafNode.Records[index];
                    return(true);

#warning Optimizations missing.
                }
            }

            return(false);
        }