//
//
//
//
// $Revision$
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.TextEditor.Util;
namespace ICSharpCode.TextEditor.Document
{
///
/// Data structure for efficient management of the line segments (most operations are O(lg n)).
/// This implements an augmented red-black tree where each node has fields for the number of
/// nodes in its subtree (like an order statistics tree) for access by index(=line number).
/// Additionally, each node knows the total length of all segments in its subtree.
/// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in
/// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when
/// text is inserted in one line means we just have to increment the totalLength of the affected line and
/// its parent nodes - an O(lg n) operation.
/// However this means getting the line number or offset from a LineSegment is not a constant time
/// operation, but takes O(lg n).
///
/// NOTE: The tree is never empty, Clear() causes it to contain an empty segment.
///
sealed class LineSegmentTree : IList
{
internal struct RBNode
{
internal LineSegment lineSegment;
internal int count;
internal int totalLength;
public RBNode(LineSegment lineSegment)
{
this.lineSegment = lineSegment;
this.count = 1;
this.totalLength = lineSegment.TotalLength;
}
public override string ToString()
{
return "[RBNode count=" + count + " totalLength="+totalLength
+ " lineSegment.LineNumber=" + lineSegment.LineNumber
+ " lineSegment.Offset=" + lineSegment.Offset
+ " lineSegment.TotalLength=" + lineSegment.TotalLength
+ " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]";
}
}
struct MyHost : IRedBlackTreeHost
{
public int Compare(RBNode x, RBNode y)
{
throw new NotImplementedException();
}
public bool Equals(RBNode a, RBNode b)
{
throw new NotImplementedException();
}
public void UpdateAfterChildrenChange(RedBlackTreeNode node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
if (count != node.val.count || totalLength != node.val.totalLength) {
node.val.count = count;
node.val.totalLength = totalLength;
if (node.parent != null) UpdateAfterChildrenChange(node.parent);
}
}
public void UpdateAfterRotateLeft(RedBlackTreeNode node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
public void UpdateAfterRotateRight(RedBlackTreeNode node)
{
UpdateAfterChildrenChange(node);
UpdateAfterChildrenChange(node.parent);
}
}
readonly AugmentableRedBlackTree tree = new AugmentableRedBlackTree(new MyHost());
RedBlackTreeNode GetNode(int index)
{
if (index < 0 || index >= tree.Count)
throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1));
RedBlackTreeNode node = tree.root;
while (true) {
if (node.left != null && index < node.left.val.count) {
node = node.left;
} else {
if (node.left != null) {
index -= node.left.val.count;
}
if (index == 0)
return node;
index--;
node = node.right;
}
}
}
static int GetIndexFromNode(RedBlackTreeNode node)
{
int index = (node.left != null) ? node.left.val.count : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
index += node.parent.left.val.count;
index++;
}
node = node.parent;
}
return index;
}
RedBlackTreeNode GetNodeByOffset(int offset)
{
if (offset < 0 || offset > this.TotalLength)
throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength);
if (offset == this.TotalLength) {
if (tree.root == null)
throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty.");
return tree.root.RightMost;
}
RedBlackTreeNode node = tree.root;
while (true) {
if (node.left != null && offset < node.left.val.totalLength) {
node = node.left;
} else {
if (node.left != null) {
offset -= node.left.val.totalLength;
}
offset -= node.val.lineSegment.TotalLength;
if (offset < 0)
return node;
node = node.right;
}
}
}
static int GetOffsetFromNode(RedBlackTreeNode node)
{
int offset = (node.left != null) ? node.left.val.totalLength : 0;
while (node.parent != null) {
if (node == node.parent.right) {
if (node.parent.left != null)
offset += node.parent.left.val.totalLength;
offset += node.parent.val.lineSegment.TotalLength;
}
node = node.parent;
}
return offset;
}
public LineSegment GetByOffset(int offset)
{
return GetNodeByOffset(offset).val.lineSegment;
}
///
/// Gets the total length of all line segments. Runs in O(1).
///
public int TotalLength {
get {
if (tree.root == null)
return 0;
else
return tree.root.val.totalLength;
}
}
///
/// Updates the length of a line segment. Runs in O(lg n).
///
public void SetSegmentLength(LineSegment segment, int newTotalLength)
{
if (segment == null)
throw new ArgumentNullException("segment");
RedBlackTreeNode node = segment.treeEntry.it.node;
segment.TotalLength = newTotalLength;
default(MyHost).UpdateAfterChildrenChange(node);
#if DEBUG
CheckProperties();
#endif
}
public void RemoveSegment(LineSegment segment)
{
tree.RemoveAt(segment.treeEntry.it);
#if DEBUG
CheckProperties();
#endif
}
public LineSegment InsertSegmentAfter(LineSegment segment, int length)
{
LineSegment newSegment = new LineSegment();
newSegment.TotalLength = length;
newSegment.DelimiterLength = segment.DelimiterLength;
newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment);
return newSegment;
}
Enumerator InsertAfter(RedBlackTreeNode node, LineSegment newSegment)
{
RedBlackTreeNode newNode = new RedBlackTreeNode(new RBNode(newSegment));
if (node.right == null) {
tree.InsertAsRight(node, newNode);
} else {
tree.InsertAsLeft(node.right.LeftMost, newNode);
}
#if DEBUG
CheckProperties();
#endif
return new Enumerator(new RedBlackTreeIterator(newNode));
}
///
/// Gets the number of items in the collections. Runs in O(1).
///
public int Count {
get { return tree.Count; }
}
///
/// Gets or sets an item by index. Runs in O(lg n).
///
public LineSegment this[int index] {
get {
return GetNode(index).val.lineSegment;
}
set {
throw new NotSupportedException();
}
}
bool ICollection.IsReadOnly {
get { return true; }
}
///
/// Gets the index of an item. Runs in O(lg n).
///
public int IndexOf(LineSegment item)
{
int index = item.LineNumber;
if (index < 0 || index >= this.Count)
return -1;
if (item != this[index])
return -1;
return index;
}
void IList.RemoveAt(int index)
{
throw new NotSupportedException();
}
#if DEBUG
[Conditional("DATACONSISTENCYTEST")]
void CheckProperties()
{
if (tree.root == null) {
Debug.Assert(this.Count == 0);
} else {
Debug.Assert(tree.root.val.count == this.Count);
CheckProperties(tree.root);
}
}
void CheckProperties(RedBlackTreeNode node)
{
int count = 1;
int totalLength = node.val.lineSegment.TotalLength;
if (node.left != null) {
CheckProperties(node.left);
count += node.left.val.count;
totalLength += node.left.val.totalLength;
}
if (node.right != null) {
CheckProperties(node.right);
count += node.right.val.count;
totalLength += node.right.val.totalLength;
}
Debug.Assert(node.val.count == count);
Debug.Assert(node.val.totalLength == totalLength);
}
public string GetTreeAsString()
{
return tree.GetTreeAsString();
}
#endif
public LineSegmentTree()
{
Clear();
}
///
/// Clears the list. Runs in O(1).
///
public void Clear()
{
tree.Clear();
LineSegment emptySegment = new LineSegment();
emptySegment.TotalLength = 0;
emptySegment.DelimiterLength = 0;
tree.Add(new RBNode(emptySegment));
emptySegment.treeEntry = GetEnumeratorForIndex(0);
#if DEBUG
CheckProperties();
#endif
}
///
/// Tests whether an item is in the list. Runs in O(n).
///
public bool Contains(LineSegment item)
{
return IndexOf(item) >= 0;
}
///
/// Copies all elements from the list to the array.
///
public void CopyTo(LineSegment[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException("array");
foreach (LineSegment val in this)
array[arrayIndex++] = val;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public Enumerator GetEnumerator()
{
return new Enumerator(tree.GetEnumerator());
}
public Enumerator GetEnumeratorForIndex(int index)
{
return new Enumerator(new RedBlackTreeIterator(GetNode(index)));
}
public Enumerator GetEnumeratorForOffset(int offset)
{
return new Enumerator(new RedBlackTreeIterator(GetNodeByOffset(offset)));
}
public struct Enumerator : IEnumerator
{
///
/// An invalid enumerator value. Calling MoveNext on the invalid enumerator
/// will always return false, accessing Current will throw an exception.
///
public static readonly Enumerator Invalid = default(Enumerator);
internal RedBlackTreeIterator it;
internal Enumerator(RedBlackTreeIterator it)
{
this.it = it;
}
///
/// Gets the current value. Runs in O(1).
///
public LineSegment Current {
get {
return it.Current.lineSegment;
}
}
public bool IsValid {
get {
return it.IsValid;
}
}
///
/// Gets the index of the current value. Runs in O(lg n).
///
public int CurrentIndex {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetIndexFromNode(it.node);
}
}
///
/// Gets the offset of the current value. Runs in O(lg n).
///
public int CurrentOffset {
get {
if (it.node == null)
throw new InvalidOperationException();
return GetOffsetFromNode(it.node);
}
}
object System.Collections.IEnumerator.Current {
get {
return it.Current.lineSegment;
}
}
public void Dispose()
{
}
///
/// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
///
public bool MoveNext()
{
return it.MoveNext();
}
///
/// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n).
///
public bool MoveBack()
{
return it.MoveBack();
}
void System.Collections.IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
void IList.Insert(int index, LineSegment item)
{
throw new NotSupportedException();
}
void ICollection.Add(LineSegment item)
{
throw new NotSupportedException();
}
bool ICollection.Remove(LineSegment item)
{
throw new NotSupportedException();
}
}
}