/// <summary>
        /// Used by add(). Chooses a leaf to add the rectangle to.
        /// </summary>
        private Node <T> chooseNode(Rectangle r, int level)
        {
            // CL1 [Initialize] Set N to be the root node
            Node <T> n = getNode(rootNodeId);

            parents.Clear();
            parentsEntry.Clear();

            // CL2 [Leaf check] If N is a leaf, return N
            while (true)
            {
                if (n == null)
                {
                    Debug.WriteLine($"Could not get root Node<T> ({rootNodeId})");
                }

                if (n.level == level)
                {
                    return(n);
                }

                // CL3 [Choose subtree] If N is not at the desired level, let F be the entry in N
                // whose rectangle FI needs least enlargement to include EI. Resolve
                // ties by choosing the entry with the rectangle of smaller area.
                float leastEnlargement = n.getEntry(0).enlargement(r);
                int   index            = 0; // index of rectangle in subtree
                for (int i = 1; i < n.entryCount; i++)
                {
                    Rectangle tempRectangle   = n.getEntry(i);
                    float     tempEnlargement = tempRectangle.enlargement(r);
                    if ((tempEnlargement < leastEnlargement) ||
                        ((tempEnlargement == leastEnlargement) &&
                         (tempRectangle.area() < n.getEntry(index).area())))
                    {
                        index            = i;
                        leastEnlargement = tempEnlargement;
                    }
                }

                parents.Push(n.nodeId);
                parentsEntry.Push(index);

                // CL4 [Descend until a leaf is reached] Set N to be the child Node&lt;T&gt;
                // pointed to by Fp and repeat from CL2
                n = getNode(n.ids[index]);
            }
        }
        /// <summary>
        /// Split a node. Algorithm is taken pretty much verbatim from
        /// Guttman's original paper.
        /// </summary>
        /// <param name="n"></param>
        /// <param name="newRect"></param>
        /// <param name="newId"></param>
        /// <returns>return new Node&lt;T&gt; object.</returns>
        private Node <T> splitNode(Node <T> n, Rectangle newRect, int newId)
        {
            // [Pick first entry for each group] Apply algorithm pickSeeds to
            // choose two entries to be the first elements of the groups. Assign
            // each to a group.

            // debug code
#if DEBUG
            float     initialArea = 0;
            Rectangle union       = n.mbr.union(newRect);
            initialArea = union.area();
#endif

            System.Array.Copy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries);

            Node <T> newNode = null;
            newNode = new Node <T>(getNextNodeId(), n.level, maxNodeEntries);
            nodeMap.Add(newNode.nodeId, newNode);

            pickSeeds(n, newRect, newId, newNode); // this also sets the entryCount to 1

            // [Check if done] If all entries have been assigned, stop. If one
            // group has so few entries that all the rest must be assigned to it in
            // order for it to have the minimum number m, assign them and stop.
            while (n.entryCount + newNode.entryCount < maxNodeEntries + 1)
            {
                if (maxNodeEntries + 1 - newNode.entryCount == minNodeEntries)
                {
                    // assign all remaining entries to original node
                    for (int i = 0; i < maxNodeEntries; i++)
                    {
                        if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED)
                        {
                            entryStatus[i] = ENTRY_STATUS_ASSIGNED;
                            n.mbr.add(n.entries[i]);
                            n.entryCount++;
                        }
                    }
                    break;
                }
                if (maxNodeEntries + 1 - n.entryCount == minNodeEntries)
                {
                    // assign all remaining entries to new node
                    for (int i = 0; i < maxNodeEntries; i++)
                    {
                        if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED)
                        {
                            entryStatus[i] = ENTRY_STATUS_ASSIGNED;
                            newNode.addEntryNoCopy(n.entries[i], n.ids[i]);
                            n.entries[i] = null;
                        }
                    }
                    break;
                }

                // [Select entry to assign] Invoke algorithm pickNext to choose the
                // next entry to assign. Add it to the group whose covering rectangle
                // will have to be enlarged least to accommodate it. Resolve ties
                // by adding the entry to the group with smaller area, then to the
                // the one with fewer entries, then to either. Repeat from S2
                pickNext(n, newNode);
            }

            n.reorganize(this);

            // check that the MBR stored for each Node&lt;T&gt; is correct.
            if (INTERNAL_CONSISTENCY_CHECKING)
            {
                if (!n.mbr.Equals(calculateMBR(n)))
                {
                    Debug.WriteLine("Error: splitNode old Node<T> MBR wrong");
                }

                if (!newNode.mbr.Equals(calculateMBR(newNode)))
                {
                    Debug.WriteLine("Error: splitNode new Node<T> MBR wrong");
                }
            }

            // debug code
#if DEBUG
            float newArea            = n.mbr.area() + newNode.mbr.area();
            float percentageIncrease = (100 * (newArea - initialArea)) / initialArea;
            Debug.WriteLine($"Node { n.nodeId} split. New area increased by {percentageIncrease}%");
#endif

            return(newNode);
        }