void RemoveNode(HeightTreeNode removedNode)
		{
			if (removedNode.left != null && removedNode.right != null) {
				// replace removedNode with it's in-order successor
				
				HeightTreeNode leftMost = removedNode.right.LeftMost;
				HeightTreeNode parentOfLeftMost = leftMost.parent;
				RemoveNode(leftMost); // remove leftMost from its current location
				
				BeforeNodeReplace(removedNode, leftMost, parentOfLeftMost);
				// 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;
				
				UpdateAfterChildrenChange(leftMost);
				if (leftMost.parent != null) UpdateAfterChildrenChange(leftMost.parent);
				return;
			}
			
			// now either removedNode.left or removedNode.right is null
			// get the remaining child
			HeightTreeNode parentNode = removedNode.parent;
			HeightTreeNode childNode = removedNode.left ?? removedNode.right;
			BeforeNodeRemove(removedNode);
			ReplaceNode(removedNode, childNode);
			if (parentNode != null) UpdateAfterChildrenChange(parentNode);
			if (removedNode.color == BLACK) {
				if (childNode != null && childNode.color == RED) {
					childNode.color = BLACK;
				} else {
					FixTreeOnDelete(childNode, parentNode);
				}
			}
		}
		void MergeCollapsedSectionsIfPossible(HeightTreeNode node)
		{
			Debug.Assert(node != null);
			if (inRemoval) {
				nodesToCheckForMerging.Add(node);
				return;
			}
			// now check if we need to merge collapsedSections together
			bool merged = false;
			var collapsedL = node.lineNode.collapsedSections;
			if (collapsedL != null) {
				for (int i = collapsedL.Count - 1; i >= 0; i--) {
					CollapsedLineSection cs = collapsedL[i];
					if (cs.Start == node.documentLine || cs.End == node.documentLine)
						continue;
					if (node.left == null
					    || (node.left.collapsedSections != null && node.left.collapsedSections.Contains(cs)))
					{
						if (node.right == null
						    || (node.right.collapsedSections != null && node.right.collapsedSections.Contains(cs)))
						{
							// all children of node contain cs: -> merge!
							if (node.left != null) node.left.RemoveDirectlyCollapsed(cs);
							if (node.right != null) node.right.RemoveDirectlyCollapsed(cs);
							collapsedL.RemoveAt(i);
							node.AddDirectlyCollapsed(cs);
							merged = true;
						}
					}
				}
				if (collapsedL.Count == 0)
					node.lineNode.collapsedSections = null;
			}
			if (merged && node.parent != null) {
				MergeCollapsedSectionsIfPossible(node.parent);
			}
		}
		static void UpdateAugmentedData(HeightTreeNode node, UpdateAfterChildrenChangeRecursionMode mode)
		{
			int totalCount = 1;
			double totalHeight = node.lineNode.TotalHeight;
			if (node.left != null) {
				totalCount += node.left.totalCount;
				totalHeight += node.left.totalHeight;
			}
			if (node.right != null) {
				totalCount += node.right.totalCount;
				totalHeight += node.right.totalHeight;
			}
			if (node.IsDirectlyCollapsed)
				totalHeight = 0;
			if (totalCount != node.totalCount
			    || !totalHeight.IsClose(node.totalHeight)
			    || mode == UpdateAfterChildrenChangeRecursionMode.WholeBranch)
			{
				node.totalCount = totalCount;
				node.totalHeight = totalHeight;
				if (node.parent != null && mode != UpdateAfterChildrenChangeRecursionMode.None)
					UpdateAugmentedData(node.parent, mode);
			}
		}
		static bool GetColor(HeightTreeNode node)
		{
			return node != null ? node.color : BLACK;
		}
		void FixTreeOnDelete(HeightTreeNode node, HeightTreeNode parentNode)
		{
			Debug.Assert(node == null || node.parent == parentNode);
			if (parentNode == null)
				return;
			
			// warning: node may be null
			HeightTreeNode 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);
			}
		}
		static void UpdateAfterChildrenChange(HeightTreeNode node)
		{
			UpdateAugmentedData(node, UpdateAfterChildrenChangeRecursionMode.IfRequired);
		}