void InsertAsRight(TextAnchorNode parentNode, TextAnchorNode newNode)
 {
     Debug.Assert(parentNode.right == null);
     parentNode.right = newNode;
     newNode.parent   = parentNode;
     newNode.color    = RED;
     UpdateAugmentedData(parentNode);
     FixTreeOnInsert(newNode);
 }
 static TextAnchorNode Sibling(TextAnchorNode node)
 {
     if (node == node.parent.left)
     {
         return(node.parent.right);
     }
     else
     {
         return(node.parent.left);
     }
 }
 void InsertBefore(TextAnchorNode node, TextAnchorNode newNode)
 {
     if (node.left == null)
     {
         InsertAsLeft(node, newNode);
     }
     else
     {
         InsertAsRight(node.left.RightMost, newNode);
     }
 }
 static TextAnchorNode Sibling(TextAnchorNode node, TextAnchorNode parentNode)
 {
     Debug.Assert(node == null || node.parent == parentNode);
     if (node == parentNode.left)
     {
         return(parentNode.right);
     }
     else
     {
         return(parentNode.left);
     }
 }
        void RemoveNode(TextAnchorNode removedNode)
        {
            if (removedNode.left != null && removedNode.right != null)
            {
                // replace removedNode with it's in-order successor

                TextAnchorNode leftMost = removedNode.right.LeftMost;
                RemoveNode(leftMost);                 // remove leftMost from its current location

                // and overwrite the removedNode with it
                ReplaceNode(removedNode, leftMost);
                leftMost.left = removedNode.left;
                if (leftMost.left != null)
                {
                    leftMost.left.parent = leftMost;
                }
                leftMost.right = removedNode.right;
                if (leftMost.right != null)
                {
                    leftMost.right.parent = leftMost;
                }
                leftMost.color = removedNode.color;

                UpdateAugmentedData(leftMost);
                if (leftMost.parent != null)
                {
                    UpdateAugmentedData(leftMost.parent);
                }
                return;
            }

            // now either removedNode.left or removedNode.right is null
            // get the remaining child
            TextAnchorNode parentNode = removedNode.parent;
            TextAnchorNode childNode  = removedNode.left ?? removedNode.right;

            ReplaceNode(removedNode, childNode);
            if (parentNode != null)
            {
                UpdateAugmentedData(parentNode);
            }
            if (removedNode.color == BLACK)
            {
                if (childNode != null && childNode.color == RED)
                {
                    childNode.color = BLACK;
                }
                else
                {
                    FixTreeOnDelete(childNode, parentNode);
                }
            }
        }
 TextAnchorNode FindActualBeginNode(TextAnchorNode node)
 {
     // now find the actual beginNode
     while (node != null && node.length == 0)
     {
         node = node.Predecessor;
     }
     if (node == null)
     {
         // no predecessor = beginNode is first node in tree
         node = root.LeftMost;
     }
     return(node);
 }
        // Sorts the nodes in the range [beginNode, endNode) by MovementType
        // and inserts the length between the BeforeInsertion and the AfterInsertion nodes.
        void PerformInsertText(TextAnchorNode beginNode, TextAnchorNode endNode, int length)
        {
            Debug.Assert(beginNode != null);
            // endNode may be null at the end of the anchor tree

            // now we need to sort the nodes in the range [beginNode, endNode); putting those with
            // MovementType.BeforeInsertion in front of those with MovementType.AfterInsertion
            List <TextAnchorNode> beforeInsert = new List <TextAnchorNode>();
            //List<TextAnchorNode> afterInsert = new List<TextAnchorNode>();
            TextAnchorNode temp = beginNode;

            while (temp != endNode)
            {
                TextAnchor anchor = (TextAnchor)temp.Target;
                if (anchor == null)
                {
                    // afterInsert.Add(temp);
                    MarkNodeForDelete(temp);
                }
                else if (anchor.MovementType == AnchorMovementType.AfterInsertion)
                {
                    // afterInsert.Add(temp);
                }
                else
                {
                    beforeInsert.Add(temp);
                }
                temp = temp.Successor;
            }
            // now again go through the range and swap the nodes with those in the beforeInsert list
            temp = beginNode;
            foreach (TextAnchorNode node in beforeInsert)
            {
                SwapAnchors(node, temp);
                temp = temp.Successor;
            }
            // now temp is pointing to the first node that is afterInsert,
            // or to endNode, if there is no afterInsert node at the offset
            // So add the length to temp
            if (temp == null)
            {
                // temp might be null if endNode==null and no afterInserts
                Debug.Assert(endNode == null);
            }
            else
            {
                temp.length += length;
                UpdateAugmentedData(temp);
            }
        }
        void CheckProperties(TextAnchorNode node)
        {
            int totalLength = node.length;

            if (node.left != null)
            {
                CheckProperties(node.left);
                totalLength += node.left.totalLength;
            }
            if (node.right != null)
            {
                CheckProperties(node.right);
                totalLength += node.right.totalLength;
            }
            Debug.Assert(node.totalLength == totalLength);
        }
        /// <summary>
        /// Finds the node at the specified offset.
        /// After the method has run, offset is relative to the beginning of the returned node.
        /// </summary>
        TextAnchorNode FindNode(ref int offset)
        {
            TextAnchorNode n = root;

            while (true)
            {
                if (n.left != null)
                {
                    if (offset < n.left.totalLength)
                    {
                        n = n.left;                         // descend into left subtree
                        continue;
                    }
                    else
                    {
                        offset -= n.left.totalLength;                         // skip left subtree
                    }
                }
                if (!n.IsAlive)
                {
                    MarkNodeForDelete(n);
                }
                if (offset < n.length)
                {
                    return(n);                    // found correct node
                }
                else
                {
                    offset -= n.length;                     // skip this node
                }
                if (n.right != null)
                {
                    n = n.right;                     // descend into right subtree
                }
                else
                {
                    // didn't find any node containing the offset
                    return(null);
                }
            }
        }
        void RotateRight(TextAnchorNode p)
        {
            // let q be p's left child
            TextAnchorNode q = p.left;

            Debug.Assert(q != null);
            Debug.Assert(q.parent == p);
            // set q to be the new root
            ReplaceNode(p, q);

            // set p's left child to be q's right child
            p.left = q.right;
            if (p.left != null)
            {
                p.left.parent = p;
            }
            // set q's right child to be p
            q.right  = p;
            p.parent = q;
            UpdateAugmentedData(p);
            UpdateAugmentedData(q);
        }
 void DeleteMarkedNodes()
 {
     CheckProperties();
     while (nodesToDelete.Count > 0)
     {
         int            pos = nodesToDelete.Count - 1;
         TextAnchorNode n   = nodesToDelete[pos];
         // combine section of n with the following section
         TextAnchorNode s = n.Successor;
         if (s != null)
         {
             s.length += n.length;
         }
         RemoveNode(n);
         if (s != null)
         {
             UpdateAugmentedData(s);
         }
         nodesToDelete.RemoveAt(pos);
         CheckProperties();
     }
     CheckProperties();
 }
 void ReplaceNode(TextAnchorNode replacedNode, TextAnchorNode newNode)
 {
     if (replacedNode.parent == null)
     {
         Debug.Assert(replacedNode == root);
         root = newNode;
     }
     else
     {
         if (replacedNode.parent.left == replacedNode)
         {
             replacedNode.parent.left = newNode;
         }
         else
         {
             replacedNode.parent.right = newNode;
         }
     }
     if (newNode != null)
     {
         newNode.parent = replacedNode.parent;
     }
     replacedNode.parent = null;
 }
 internal void OnDeleted(DelayedEvents delayedEvents)
 {
     node = null;
     delayedEvents.DelayedRaise(Deleted, this, EventArgs.Empty);
 }
 static bool GetColor(TextAnchorNode node)
 {
     return(node != null ? node.color : BLACK);
 }
        public void HandleTextChange(OffsetChangeMapEntry entry, DelayedEvents delayedEvents)
        {
            //Log("HandleTextChange(" + entry + ")");
            if (entry.RemovalLength == 0)
            {
                // This is a pure insertion.
                // Unlike a replace with removal, a pure insertion can result in nodes at the same location
                // to split depending on their MovementType.
                // Thus, we handle this case on a separate code path
                // (the code below looks like it does something similar, but it can only split
                // the set of deletion survivors, not all nodes at an offset)
                InsertText(entry.Offset, entry.InsertionLength);
                return;
            }
            // When handling a replacing text change, we need to:
            // - find all anchors in the deleted segment and delete them / move them to the appropriate
            //   surviving side.
            // - adjust the segment size between the left and right side

            int offset = entry.Offset;
            int remainingRemovalLength = entry.RemovalLength;

            // if the text change is happening after the last anchor, we don't have to do anything
            if (root == null || offset >= root.totalLength)
            {
                return;
            }
            TextAnchorNode node = FindNode(ref offset);
            TextAnchorNode firstDeletionSurvivor = null;

            // go forward through the tree and delete all nodes in the removal segment
            while (node != null && offset + remainingRemovalLength > node.length)
            {
                TextAnchor anchor = (TextAnchor)node.Target;
                if (anchor != null && (anchor.SurviveDeletion || entry.RemovalNeverCausesAnchorDeletion))
                {
                    if (firstDeletionSurvivor == null)
                    {
                        firstDeletionSurvivor = node;
                    }
                    // This node should be deleted, but it wants to survive.
                    // We'll just remove the deleted length segment, so the node will be positioned
                    // in front of the removed segment.
                    remainingRemovalLength -= node.length - offset;
                    node.length             = offset;
                    offset = 0;
                    UpdateAugmentedData(node);
                    node = node.Successor;
                }
                else
                {
                    // delete node
                    TextAnchorNode s = node.Successor;
                    remainingRemovalLength -= node.length;
                    RemoveNode(node);
                    // we already deleted the node, don't delete it twice
                    nodesToDelete.Remove(node);
                    if (anchor != null)
                    {
                        anchor.OnDeleted(delayedEvents);
                    }
                    node = s;
                }
            }
            // 'node' now is the first anchor after the deleted segment.
            // If there are no anchors after the deleted segment, 'node' is null.

            // firstDeletionSurvivor was set to the first node surviving deletion.
            // Because all non-surviving nodes up to 'node' were deleted, the node range
            // [firstDeletionSurvivor, node) now refers to the set of all deletion survivors.

            // do the remaining job of the removal
            if (node != null)
            {
                node.length -= remainingRemovalLength;
                Debug.Assert(node.length >= 0);
            }
            if (entry.InsertionLength > 0)
            {
                // we are performing a replacement
                if (firstDeletionSurvivor != null)
                {
                    // We got deletion survivors which need to be split into BeforeInsertion
                    // and AfterInsertion groups.
                    // Take care that we don't regroup everything at offset, but only the deletion
                    // survivors - from firstDeletionSurvivor (inclusive) to node (exclusive).
                    // This ensures that nodes immediately before or after the replaced segment
                    // stay where they are (independent from their MovementType)
                    PerformInsertText(firstDeletionSurvivor, node, entry.InsertionLength);
                }
                else if (node != null)
                {
                    // No deletion survivors:
                    // just perform the insertion
                    node.length += entry.InsertionLength;
                }
            }
            if (node != null)
            {
                UpdateAugmentedData(node);
            }
            DeleteMarkedNodes();
        }
        void FixTreeOnDelete(TextAnchorNode node, TextAnchorNode parentNode)
        {
            Debug.Assert(node == null || node.parent == parentNode);
            if (parentNode == null)
            {
                return;
            }

            // warning: node may be null
            TextAnchorNode sibling = Sibling(node, parentNode);

            if (sibling.color == RED)
            {
                parentNode.color = RED;
                sibling.color    = BLACK;
                if (node == parentNode.left)
                {
                    RotateLeft(parentNode);
                }
                else
                {
                    RotateRight(parentNode);
                }

                sibling = Sibling(node, parentNode);                 // update value of sibling after rotation
            }

            if (parentNode.color == BLACK &&
                sibling.color == BLACK &&
                GetColor(sibling.left) == BLACK &&
                GetColor(sibling.right) == BLACK)
            {
                sibling.color = RED;
                FixTreeOnDelete(parentNode, parentNode.parent);
                return;
            }

            if (parentNode.color == RED &&
                sibling.color == BLACK &&
                GetColor(sibling.left) == BLACK &&
                GetColor(sibling.right) == BLACK)
            {
                sibling.color    = RED;
                parentNode.color = BLACK;
                return;
            }

            if (node == parentNode.left &&
                sibling.color == BLACK &&
                GetColor(sibling.left) == RED &&
                GetColor(sibling.right) == BLACK)
            {
                sibling.color      = RED;
                sibling.left.color = BLACK;
                RotateRight(sibling);
            }
            else if (node == parentNode.right &&
                     sibling.color == BLACK &&
                     GetColor(sibling.right) == RED &&
                     GetColor(sibling.left) == BLACK)
            {
                sibling.color       = RED;
                sibling.right.color = BLACK;
                RotateLeft(sibling);
            }
            sibling = Sibling(node, parentNode);             // update value of sibling after rotation

            sibling.color    = parentNode.color;
            parentNode.color = BLACK;
            if (node == parentNode.left)
            {
                if (sibling.right != null)
                {
                    Debug.Assert(sibling.right.color == RED);
                    sibling.right.color = BLACK;
                }
                RotateLeft(parentNode);
            }
            else
            {
                if (sibling.left != null)
                {
                    Debug.Assert(sibling.left.color == RED);
                    sibling.left.color = BLACK;
                }
                RotateRight(parentNode);
            }
        }
        void FixTreeOnInsert(TextAnchorNode node)
        {
            Debug.Assert(node != null);
            Debug.Assert(node.color == RED);
            Debug.Assert(node.left == null || node.left.color == BLACK);
            Debug.Assert(node.right == null || node.right.color == BLACK);

            TextAnchorNode parentNode = node.parent;

            if (parentNode == null)
            {
                // we inserted in the root -> the node must be black
                // since this is a root node, making the node black increments the number of black nodes
                // on all paths by one, so it is still the same for all paths.
                node.color = BLACK;
                return;
            }
            if (parentNode.color == BLACK)
            {
                // if the parent node where we inserted was black, our red node is placed correctly.
                // since we inserted a red node, the number of black nodes on each path is unchanged
                // -> the tree is still balanced
                return;
            }
            // parentNode is red, so there is a conflict here!

            // because the root is black, parentNode is not the root -> there is a grandparent node
            TextAnchorNode grandparentNode = parentNode.parent;
            TextAnchorNode uncleNode       = Sibling(parentNode);

            if (uncleNode != null && uncleNode.color == RED)
            {
                parentNode.color      = BLACK;
                uncleNode.color       = BLACK;
                grandparentNode.color = RED;
                FixTreeOnInsert(grandparentNode);
                return;
            }
            // now we know: parent is red but uncle is black
            // First rotation:
            if (node == parentNode.right && parentNode == grandparentNode.left)
            {
                RotateLeft(parentNode);
                node = node.left;
            }
            else if (node == parentNode.left && parentNode == grandparentNode.right)
            {
                RotateRight(parentNode);
                node = node.right;
            }
            // because node might have changed, reassign variables:
            parentNode      = node.parent;
            grandparentNode = parentNode.parent;

            // Now recolor a bit:
            parentNode.color      = BLACK;
            grandparentNode.color = RED;
            // Second rotation:
            if (node == parentNode.left && parentNode == grandparentNode.left)
            {
                RotateRight(grandparentNode);
            }
            else
            {
                // because of the first rotation, this is guaranteed:
                Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
                RotateLeft(grandparentNode);
            }
        }