// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.TextFormatting;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Utils;

namespace ICSharpCode.AvalonEdit.Rendering
{
	/// <summary>
	/// A virtualizing panel producing+showing <see cref="VisualLine"/>s for a <see cref="TextDocument"/>.
	/// 
	/// This is the heart of the text editor, this class controls the text rendering process.
	/// 
	/// Taken as a standalone control, it's a text viewer without any editing capability.
	/// </summary>
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable",
	                                                 Justification = "The user usually doesn't work with TextView but with TextEditor; and nulling the Document property is sufficient to dispose everything.")]
	public class TextView : FrameworkElement, IScrollInfo, IWeakEventListener, ITextEditorComponent, IServiceProvider
	{
		#region Constructor
		static TextView()
		{
			ClipToBoundsProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.True));
			FocusableProperty.OverrideMetadata(typeof(TextView), new FrameworkPropertyMetadata(Boxes.False));
		}
		
		ColumnRulerRenderer columnRulerRenderer;
		
		/// <summary>
		/// Creates a new TextView instance.
		/// </summary>
		public TextView()
		{
			services.AddService(typeof(TextView), this);
			textLayer = new TextLayer(this);
			elementGenerators = new ObserveAddRemoveCollection<VisualLineElementGenerator>(ElementGenerator_Added, ElementGenerator_Removed);
			lineTransformers = new ObserveAddRemoveCollection<IVisualLineTransformer>(LineTransformer_Added, LineTransformer_Removed);
			backgroundRenderers = new ObserveAddRemoveCollection<IBackgroundRenderer>(BackgroundRenderer_Added, BackgroundRenderer_Removed);
			columnRulerRenderer = new ColumnRulerRenderer(this);
			this.Options = new TextEditorOptions();
			
			Debug.Assert(singleCharacterElementGenerator != null); // assert that the option change created the builtin element generators
			
			layers = new LayerCollection(this);
			InsertLayer(textLayer, KnownLayer.Text, LayerInsertionPosition.Replace);
			
			this.hoverLogic = new MouseHoverLogic(this);
			this.hoverLogic.MouseHover += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverEvent, MouseHoverEvent);
			this.hoverLogic.MouseHoverStopped += (sender, e) => RaiseHoverEventPair(e, PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent);
		}

		#endregion
		
		#region Document Property
		/// <summary>
		/// Document property.
		/// </summary>
		public static readonly DependencyProperty DocumentProperty =
			DependencyProperty.Register("Document", typeof(TextDocument), typeof(TextView),
			                            new FrameworkPropertyMetadata(OnDocumentChanged));
		
		TextDocument document;
		HeightTree heightTree;
		
		/// <summary>
		/// Gets/Sets the document displayed by the text editor.
		/// </summary>
		public TextDocument Document {
			get { return (TextDocument)GetValue(DocumentProperty); }
			set { SetValue(DocumentProperty, value); }
		}
		
		static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
		{
			((TextView)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
		}
		
		internal double FontSize {
			get {
				return (double)GetValue(TextBlock.FontSizeProperty);
			}
		}
		
		/// <summary>
		/// Occurs when the document property has changed.
		/// </summary>
		public event EventHandler DocumentChanged;
		
		void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
		{
			if (oldValue != null) {
				heightTree.Dispose();
				heightTree = null;
				formatter.Dispose();
				formatter = null;
				cachedElements.Dispose();
				cachedElements = null;
				TextDocumentWeakEventManager.Changing.RemoveListener(oldValue, this);
			}
			this.document = newValue;
			ClearScrollData();
			ClearVisualLines();
			if (newValue != null) {
				TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
				formatter = TextFormatterFactory.Create(this);
				InvalidateDefaultTextMetrics(); // measuring DefaultLineHeight depends on formatter
				heightTree = new HeightTree(newValue, DefaultLineHeight);
				cachedElements = new TextViewCachedElements();
			}
			InvalidateMeasure(DispatcherPriority.Normal);
			if (DocumentChanged != null)
				DocumentChanged(this, EventArgs.Empty);
		}
		
		/// <summary>
		/// Recreates the text formatter that is used internally
		/// by calling <see cref="TextFormatterFactory.Create"/>.
		/// </summary>
		void RecreateTextFormatter()
		{
			if (formatter != null) {
				formatter.Dispose();
				formatter = TextFormatterFactory.Create(this);
				Redraw();
			}
		}
		
		void RecreateCachedElements()
		{
			if (cachedElements != null) {
				cachedElements.Dispose();
				cachedElements = new TextViewCachedElements();
			}
		}
		
		/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
		protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
		{
			if (managerType == typeof(TextDocumentWeakEventManager.Changing)) {
				// TODO: put redraw into background so that other input events can be handled before the redraw.
				// Unfortunately the "easy" approach (just use DispatcherPriority.Background) here makes the editor twice as slow because
				// the caret position change forces an immediate redraw, and the text input then forces a background redraw.
				// When fixing this, make sure performance on the SharpDevelop "type text in C# comment" stress test doesn't get significantly worse.
				DocumentChangeEventArgs change = (DocumentChangeEventArgs)e;
				Redraw(change.Offset, change.RemovalLength, DispatcherPriority.Normal);
				return true;
			} else if (managerType == typeof(PropertyChangedWeakEventManager)) {
				OnOptionChanged((PropertyChangedEventArgs)e);
				return true;
			}
			return false;
		}
		
		bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
		{
			return ReceiveWeakEvent(managerType, sender, e);
		}
		#endregion
		
		#region Options property
		/// <summary>
		/// Options property.
		/// </summary>
		public static readonly DependencyProperty OptionsProperty =
			DependencyProperty.Register("Options", typeof(TextEditorOptions), typeof(TextView),
			                            new FrameworkPropertyMetadata(OnOptionsChanged));
		
		/// <summary>
		/// Gets/Sets the options used by the text editor.
		/// </summary>
		public TextEditorOptions Options {
			get { return (TextEditorOptions)GetValue(OptionsProperty); }
			set { SetValue(OptionsProperty, value); }
		}
		
		/// <summary>
		/// Occurs when a text editor option has changed.
		/// </summary>
		public event PropertyChangedEventHandler OptionChanged;
		
		/// <summary>
		/// Raises the <see cref="OptionChanged"/> event.
		/// </summary>
		protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
		{
			if (OptionChanged != null) {
				OptionChanged(this, e);
			}
			
			if (Options.ShowColumnRuler)
				columnRulerRenderer.SetRuler(Options.ColumnRulerPosition, ColumnRulerPen);
			else
				columnRulerRenderer.SetRuler(-1, ColumnRulerPen);
			
			UpdateBuiltinElementGeneratorsFromOptions();
			Redraw();
		}
		
		static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
		{
			((TextView)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
		}
		
		void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
		{
			if (oldValue != null) {
				PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
			}
			if (newValue != null) {
				PropertyChangedWeakEventManager.AddListener(newValue, this);
			}
			OnOptionChanged(new PropertyChangedEventArgs(null));
		}
		#endregion
		
		#region ElementGenerators+LineTransformers Properties
		readonly ObserveAddRemoveCollection<VisualLineElementGenerator> elementGenerators;
		
		/// <summary>
		/// Gets a collection where element generators can be registered.
		/// </summary>
		public IList<VisualLineElementGenerator> ElementGenerators {
			get { return elementGenerators; }
		}
		
		void ElementGenerator_Added(VisualLineElementGenerator generator)
		{
			ConnectToTextView(generator);
			Redraw();
		}
		
		void ElementGenerator_Removed(VisualLineElementGenerator generator)
		{
			DisconnectFromTextView(generator);
			Redraw();
		}
		
		readonly ObserveAddRemoveCollection<IVisualLineTransformer> lineTransformers;
		
		/// <summary>
		/// Gets a collection where line transformers can be registered.
		/// </summary>
		public IList<IVisualLineTransformer> LineTransformers {
			get { return lineTransformers; }
		}
		
		void LineTransformer_Added(IVisualLineTransformer lineTransformer)
		{
			ConnectToTextView(lineTransformer);
			Redraw();
		}
		
		void LineTransformer_Removed(IVisualLineTransformer lineTransformer)
		{
			DisconnectFromTextView(lineTransformer);
			Redraw();
		}
		#endregion
		
		#region Builtin ElementGenerators
//		NewLineElementGenerator newLineElementGenerator;
		SingleCharacterElementGenerator singleCharacterElementGenerator;
		LinkElementGenerator linkElementGenerator;
		MailLinkElementGenerator mailLinkElementGenerator;
		
		void UpdateBuiltinElementGeneratorsFromOptions()
		{
			TextEditorOptions options = this.Options;
			
//			AddRemoveDefaultElementGeneratorOnDemand(ref newLineElementGenerator, options.ShowEndOfLine);
			AddRemoveDefaultElementGeneratorOnDemand(ref singleCharacterElementGenerator, options.ShowBoxForControlCharacters || options.ShowSpaces || options.ShowTabs);
			AddRemoveDefaultElementGeneratorOnDemand(ref linkElementGenerator, options.EnableHyperlinks);
			AddRemoveDefaultElementGeneratorOnDemand(ref mailLinkElementGenerator, options.EnableEmailHyperlinks);
		}
		
		void AddRemoveDefaultElementGeneratorOnDemand<T>(ref T generator, bool demand)
			where T : VisualLineElementGenerator, IBuiltinElementGenerator, new()
		{
			bool hasGenerator = generator != null;
			if (hasGenerator != demand) {
				if (demand) {
					generator = new T();
					this.ElementGenerators.Add(generator);
				} else {
					this.ElementGenerators.Remove(generator);
					generator = null;
				}
			}
			if (generator != null)
				generator.FetchOptions(this.Options);
		}
		#endregion
		
		#region Layers
		internal readonly TextLayer textLayer;
		readonly LayerCollection layers;
		
		/// <summary>
		/// Gets the list of layers displayed in the text view.
		/// </summary>
		public UIElementCollection Layers {
			get { return layers; }
		}
		
		sealed class LayerCollection : UIElementCollection
		{
			readonly TextView textView;
			
			public LayerCollection(TextView textView)
				: base(textView, textView)
			{
				this.textView = textView;
			}
			
			public override void Clear()
			{
				base.Clear();
				textView.LayersChanged();
			}
			
			public override int Add(UIElement element)
			{
				int r = base.Add(element);
				textView.LayersChanged();
				return r;
			}
			
			public override void RemoveAt(int index)
			{
				base.RemoveAt(index);
				textView.LayersChanged();
			}
			
			public override void RemoveRange(int index, int count)
			{
				base.RemoveRange(index, count);
				textView.LayersChanged();
			}
		}
		
		void LayersChanged()
		{
			textLayer.index = layers.IndexOf(textLayer);
		}
		
		/// <summary>
		/// Inserts a new layer at a position specified relative to an existing layer.
		/// </summary>
		/// <param name="layer">The new layer to insert.</param>
		/// <param name="referencedLayer">The existing layer</param>
		/// <param name="position">Specifies whether the layer is inserted above,below, or replaces the referenced layer</param>
		public void InsertLayer(UIElement layer, KnownLayer referencedLayer, LayerInsertionPosition position)
		{
			if (layer == null)
				throw new ArgumentNullException("layer");
			if (!Enum.IsDefined(typeof(KnownLayer), referencedLayer))
				throw new InvalidEnumArgumentException("referencedLayer", (int)referencedLayer, typeof(KnownLayer));
			if (!Enum.IsDefined(typeof(LayerInsertionPosition), position))
				throw new InvalidEnumArgumentException("position", (int)position, typeof(LayerInsertionPosition));
			if (referencedLayer == KnownLayer.Background && position != LayerInsertionPosition.Above)
				throw new InvalidOperationException("Cannot replace or insert below the background layer.");
			
			LayerPosition newPosition = new LayerPosition(referencedLayer, position);
			LayerPosition.SetLayerPosition(layer, newPosition);
			for (int i = 0; i < layers.Count; i++) {
				LayerPosition p = LayerPosition.GetLayerPosition(layers[i]);
				if (p != null) {
					if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Replace) {
						// found the referenced layer
						switch (position) {
							case LayerInsertionPosition.Below:
								layers.Insert(i, layer);
								return;
							case LayerInsertionPosition.Above:
								layers.Insert(i + 1, layer);
								return;
							case LayerInsertionPosition.Replace:
								layers[i] = layer;
								return;
						}
					} else if (p.KnownLayer == referencedLayer && p.Position == LayerInsertionPosition.Above
					           || p.KnownLayer > referencedLayer) {
						// we skipped the insertion position (referenced layer does not exist?)
						layers.Insert(i, layer);
						return;
					}
				}
			}
			// inserting after all existing layers:
			layers.Add(layer);
		}
		
		/// <inheritdoc/>
		protected override int VisualChildrenCount {
			get { return layers.Count + inlineObjects.Count; }
		}
		
		/// <inheritdoc/>
		protected override Visual GetVisualChild(int index)
		{
			int cut = textLayer.index + 1;
			if (index < cut)
				return layers[index];
			else if (index < cut + inlineObjects.Count)
				return inlineObjects[index - cut].Element;
			else
				return layers[index - inlineObjects.Count];
		}
		
		/// <inheritdoc/>
		protected override System.Collections.IEnumerator LogicalChildren {
			get {
				return inlineObjects.Select(io => io.Element).Concat(layers.Cast<UIElement>()).GetEnumerator();
			}
		}
		#endregion
		
		#region Inline object handling
		List<InlineObjectRun> inlineObjects = new List<InlineObjectRun>();
		
		/// <summary>
		/// Adds a new inline object.
		/// </summary>
		internal void AddInlineObject(InlineObjectRun inlineObject)
		{
			Debug.Assert(inlineObject.VisualLine != null);
			
			// Remove inline object if its already added, can happen e.g. when recreating textrun for word-wrapping
			bool alreadyAdded = false;
			for (int i = 0; i < inlineObjects.Count; i++) {
				if (inlineObjects[i].Element == inlineObject.Element) {
					RemoveInlineObjectRun(inlineObjects[i], true);
					inlineObjects.RemoveAt(i);
					alreadyAdded = true;
					break;
				}
			}
			
			inlineObjects.Add(inlineObject);
			if (!alreadyAdded) {
				AddVisualChild(inlineObject.Element);
			}
			inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
			inlineObject.desiredSize = inlineObject.Element.DesiredSize;
		}
		
		void MeasureInlineObjects()
		{
			// As part of MeasureOverride(), re-measure the inline objects
			foreach (InlineObjectRun inlineObject in inlineObjects) {
				if (inlineObject.VisualLine.IsDisposed) {
					// Don't re-measure inline objects that are going to be removed anyways.
					// If the inline object will be reused in a different VisualLine, we'll measure it in the AddInlineObject() call.
					continue;
				}
				inlineObject.Element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
				if (!inlineObject.Element.DesiredSize.IsClose(inlineObject.desiredSize)) {
					// the element changed size -> recreate its parent visual line
					inlineObject.desiredSize = inlineObject.Element.DesiredSize;
					if (allVisualLines.Remove(inlineObject.VisualLine)) {
						DisposeVisualLine(inlineObject.VisualLine);
					}
				}
			}
		}
		
		List<VisualLine> visualLinesWithOutstandingInlineObjects = new List<VisualLine>();
		
		void RemoveInlineObjects(VisualLine visualLine)
		{
			// Delay removing inline objects:
			// A document change immediately invalidates affected visual lines, but it does not
			// cause an immediate redraw.
			// To prevent inline objects from flickering when they are recreated, we delay removing
			// inline objects until the next redraw.
			if (visualLine.hasInlineObjects) {
				visualLinesWithOutstandingInlineObjects.Add(visualLine);
			}
		}
		
		/// <summary>
		/// Remove the inline objects that were marked for removal.
		/// </summary>
		void RemoveInlineObjectsNow()
		{
			if (visualLinesWithOutstandingInlineObjects.Count == 0)
				return;
			inlineObjects.RemoveAll(
				ior => {
					if (visualLinesWithOutstandingInlineObjects.Contains(ior.VisualLine)) {
						RemoveInlineObjectRun(ior, false);
						return true;
					}
					return false;
				});
			visualLinesWithOutstandingInlineObjects.Clear();
		}

		// Remove InlineObjectRun.Element from TextLayer.
		// Caller of RemoveInlineObjectRun will remove it from inlineObjects collection.
		void RemoveInlineObjectRun(InlineObjectRun ior, bool keepElement)
		{
			if (!keepElement && ior.Element.IsKeyboardFocusWithin) {
				// When the inline element that has the focus is removed, WPF will reset the
				// focus to the main window without raising appropriate LostKeyboardFocus events.
				// To work around this, we manually set focus to the next focusable parent.
				UIElement element = this;
				while (element != null && !element.Focusable) {
					element = VisualTreeHelper.GetParent(element) as UIElement;
				}
				if (element != null)
					Keyboard.Focus(element);
			}
			ior.VisualLine = null;
			if (!keepElement)
				RemoveVisualChild(ior.Element);
		}
		#endregion
		
		#region Brushes
		/// <summary>
		/// NonPrintableCharacterBrush dependency property.
		/// </summary>
		public static readonly DependencyProperty NonPrintableCharacterBrushProperty =
			DependencyProperty.Register("NonPrintableCharacterBrush", typeof(Brush), typeof(TextView),
			                            new FrameworkPropertyMetadata(Brushes.LightGray));
		
		/// <summary>
		/// Gets/sets the Brush used for displaying non-printable characters.
		/// </summary>
		public Brush NonPrintableCharacterBrush {
			get { return (Brush)GetValue(NonPrintableCharacterBrushProperty); }
			set { SetValue(NonPrintableCharacterBrushProperty, value); }
		}
		
		/// <summary>
		/// LinkTextForegroundBrush dependency property.
		/// </summary>
		public static readonly DependencyProperty LinkTextForegroundBrushProperty =
			DependencyProperty.Register("LinkTextForegroundBrush", typeof(Brush), typeof(TextView),
			                            new FrameworkPropertyMetadata(Brushes.Blue));
		
		/// <summary>
		/// Gets/sets the Brush used for displaying link texts.
		/// </summary>
		public Brush LinkTextForegroundBrush {
			get { return (Brush)GetValue(LinkTextForegroundBrushProperty); }
			set { SetValue(LinkTextForegroundBrushProperty, value); }
		}
		
		/// <summary>
		/// LinkTextBackgroundBrush dependency property.
		/// </summary>
		public static readonly DependencyProperty LinkTextBackgroundBrushProperty =
			DependencyProperty.Register("LinkTextBackgroundBrush", typeof(Brush), typeof(TextView),
			                            new FrameworkPropertyMetadata(Brushes.Transparent));
		
		/// <summary>
		/// Gets/sets the Brush used for the background of link texts.
		/// </summary>
		public Brush LinkTextBackgroundBrush {
			get { return (Brush)GetValue(LinkTextBackgroundBrushProperty); }
			set { SetValue(LinkTextBackgroundBrushProperty, value); }
		}
		#endregion
		
		#region Redraw methods / VisualLine invalidation
		/// <summary>
		/// Causes the text editor to regenerate all visual lines.
		/// </summary>
		public void Redraw()
		{
			Redraw(DispatcherPriority.Normal);
		}
		
		/// <summary>
		/// Causes the text editor to regenerate all visual lines.
		/// </summary>
		public void Redraw(DispatcherPriority redrawPriority)
		{
			VerifyAccess();
			ClearVisualLines();
			InvalidateMeasure(redrawPriority);
		}
		
		/// <summary>
		/// Causes the text editor to regenerate the specified visual line.
		/// </summary>
		public void Redraw(VisualLine visualLine, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
		{
			VerifyAccess();
			if (allVisualLines.Remove(visualLine)) {
				DisposeVisualLine(visualLine);
				InvalidateMeasure(redrawPriority);
			}
		}
		
		/// <summary>
		/// Causes the text editor to redraw all lines overlapping with the specified segment.
		/// </summary>
		public void Redraw(int offset, int length, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
		{
			VerifyAccess();
			bool changedSomethingBeforeOrInLine = false;
			for (int i = 0; i < allVisualLines.Count; i++) {
				VisualLine visualLine = allVisualLines[i];
				int lineStart = visualLine.FirstDocumentLine.Offset;
				int lineEnd = visualLine.LastDocumentLine.Offset + visualLine.LastDocumentLine.TotalLength;
				if (offset <= lineEnd) {
					changedSomethingBeforeOrInLine = true;
					if (offset + length >= lineStart) {
						allVisualLines.RemoveAt(i--);
						DisposeVisualLine(visualLine);
					}
				}
			}
			if (changedSomethingBeforeOrInLine) {
				// Repaint not only when something in visible area was changed, but also when anything in front of it
				// was changed. We might have to redraw the line number margin. Or the highlighting changed.
				// However, we'll try to reuse the existing VisualLines.
				InvalidateMeasure(redrawPriority);
			}
		}
		
		/// <summary>
		/// Causes a known layer to redraw.
		/// This method does not invalidate visual lines;
		/// use the <see cref="Redraw()"/> method to do that.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer",
		                                                 Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")]
		public void InvalidateLayer(KnownLayer knownLayer)
		{
			InvalidateMeasure(DispatcherPriority.Normal);
		}
		
		/// <summary>
		/// Causes a known layer to redraw.
		/// This method does not invalidate visual lines;
		/// use the <see cref="Redraw()"/> method to do that.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "knownLayer",
		                                                 Justification="This method is meant to invalidate only a specific layer - I just haven't figured out how to do that, yet.")]
		public void InvalidateLayer(KnownLayer knownLayer, DispatcherPriority priority)
		{
			InvalidateMeasure(priority);
		}
		
		/// <summary>
		/// Causes the text editor to redraw all lines overlapping with the specified segment.
		/// Does nothing if segment is null.
		/// </summary>
		public void Redraw(ISegment segment, DispatcherPriority redrawPriority = DispatcherPriority.Normal)
		{
			if (segment != null) {
				Redraw(segment.Offset, segment.Length, redrawPriority);
			}
		}
		
		/// <summary>
		/// Invalidates all visual lines.
		/// The caller of ClearVisualLines() must also call InvalidateMeasure() to ensure
		/// that the visual lines will be recreated.
		/// </summary>
		void ClearVisualLines()
		{
			visibleVisualLines = null;
			if (allVisualLines.Count != 0) {
				foreach (VisualLine visualLine in allVisualLines) {
					DisposeVisualLine(visualLine);
				}
				allVisualLines.Clear();
			}
		}
		
		void DisposeVisualLine(VisualLine visualLine)
		{
			if (newVisualLines != null && newVisualLines.Contains(visualLine)) {
				throw new ArgumentException("Cannot dispose visual line because it is in construction!");
			}
			visibleVisualLines = null;
			visualLine.Dispose();
			RemoveInlineObjects(visualLine);
		}
		#endregion
		
		#region InvalidateMeasure(DispatcherPriority)
		DispatcherOperation invalidateMeasureOperation;
		
		void InvalidateMeasure(DispatcherPriority priority)
		{
			if (priority >= DispatcherPriority.Render) {
				if (invalidateMeasureOperation != null) {
					invalidateMeasureOperation.Abort();
					invalidateMeasureOperation = null;
				}
				base.InvalidateMeasure();
			} else {
				if (invalidateMeasureOperation != null) {
					invalidateMeasureOperation.Priority = priority;
				} else {
					invalidateMeasureOperation = Dispatcher.BeginInvoke(
						priority,
						new Action(
							delegate {
								invalidateMeasureOperation = null;
								base.InvalidateMeasure();
							}
						)
					);
				}
			}
		}
		#endregion
		
		#region Get(OrConstruct)VisualLine
		/// <summary>
		/// Gets the visual line that contains the document line with the specified number.
		/// Returns null if the document line is outside the visible range.
		/// </summary>
		public VisualLine GetVisualLine(int documentLineNumber)
		{
			// TODO: EnsureVisualLines() ?
			foreach (VisualLine visualLine in allVisualLines) {
				Debug.Assert(visualLine.IsDisposed == false);
				int start = visualLine.FirstDocumentLine.LineNumber;
				int end = visualLine.LastDocumentLine.LineNumber;
				if (documentLineNumber >= start && documentLineNumber <= end)
					return visualLine;
			}
			return null;
		}
		
		/// <summary>
		/// Gets the visual line that contains the document line with the specified number.
		/// If that line is outside the visible range, a new VisualLine for that document line is constructed.
		/// </summary>
		public VisualLine GetOrConstructVisualLine(DocumentLine documentLine)
		{
			if (documentLine == null)
				throw new ArgumentNullException("documentLine");
			if (!this.Document.Lines.Contains(documentLine))
				throw new InvalidOperationException("Line belongs to wrong document");
			VerifyAccess();
			
			VisualLine l = GetVisualLine(documentLine.LineNumber);
			if (l == null) {
				TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
				VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
				
				while (heightTree.GetIsCollapsed(documentLine.LineNumber)) {
					documentLine = documentLine.PreviousLine;
				}
				
				l = BuildVisualLine(documentLine,
				                    globalTextRunProperties, paragraphProperties,
				                    elementGenerators.ToArray(), lineTransformers.ToArray(),
				                    lastAvailableSize);
				allVisualLines.Add(l);
				// update all visual top values (building the line might have changed visual top of other lines due to word wrapping)
				foreach (var line in allVisualLines) {
					line.VisualTop = heightTree.GetVisualPosition(line.FirstDocumentLine);
				}
			}
			return l;
		}
		#endregion
		
		#region Visual Lines (fields and properties)
		List<VisualLine> allVisualLines = new List<VisualLine>();
		ReadOnlyCollection<VisualLine> visibleVisualLines;
		double clippedPixelsOnTop;
		List<VisualLine> newVisualLines;
		
		/// <summary>
		/// Gets the currently visible visual lines.
		/// </summary>
		/// <exception cref="VisualLinesInvalidException">
		/// Gets thrown if there are invalid visual lines when this property is accessed.
		/// You can use the <see cref="VisualLinesValid"/> property to check for this case,
		/// or use the <see cref="EnsureVisualLines()"/> method to force creating the visual lines
		/// when they are invalid.
		/// </exception>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
		public ReadOnlyCollection<VisualLine> VisualLines {
			get {
				if (visibleVisualLines == null)
					throw new VisualLinesInvalidException();
				return visibleVisualLines;
			}
		}
		
		/// <summary>
		/// Gets whether the visual lines are valid.
		/// Will return false after a call to Redraw().
		/// Accessing the visual lines property will cause a <see cref="VisualLinesInvalidException"/>
		/// if this property is <c>false</c>.
		/// </summary>
		public bool VisualLinesValid {
			get { return visibleVisualLines != null; }
		}
		
		/// <summary>
		/// Occurs when the TextView is about to be measured and will regenerate its visual lines.
		/// This event may be used to mark visual lines as invalid that would otherwise be reused.
		/// </summary>
		public event EventHandler<VisualLineConstructionStartEventArgs> VisualLineConstructionStarting;
		
		/// <summary>
		/// Occurs when the TextView was measured and changed its visual lines.
		/// </summary>
		public event EventHandler VisualLinesChanged;
		
		/// <summary>
		/// If the visual lines are invalid, creates new visual lines for the visible part
		/// of the document.
		/// If all visual lines are valid, this method does nothing.
		/// </summary>
		/// <exception cref="InvalidOperationException">The visual line build process is already running.
		/// It is not allowed to call this method during the construction of a visual line.</exception>
		public void EnsureVisualLines()
		{
			Dispatcher.VerifyAccess();
			if (inMeasure)
				throw new InvalidOperationException("The visual line build process is already running! Cannot EnsureVisualLines() during Measure!");
			if (!VisualLinesValid) {
				// increase priority for re-measure
				InvalidateMeasure(DispatcherPriority.Normal);
				// force immediate re-measure
				UpdateLayout();
			}
			// Sometimes we still have invalid lines after UpdateLayout - work around the problem
			// by calling MeasureOverride directly.
			if (!VisualLinesValid) {
				Debug.WriteLine("UpdateLayout() failed in EnsureVisualLines");
				MeasureOverride(lastAvailableSize);
			}
			if (!VisualLinesValid)
				throw new VisualLinesInvalidException("Internal error: visual lines invalid after EnsureVisualLines call");
		}
		#endregion
		
		#region Measure
		/// <summary>
		/// Additonal amount that allows horizontal scrolling past the end of the longest line.
		/// This is necessary to ensure the caret always is visible, even when it is at the end of the longest line.
		/// </summary>
		const double AdditionalHorizontalScrollAmount = 3;
		
		Size lastAvailableSize;
		bool inMeasure;
		
		/// <inheritdoc/>
		protected override Size MeasureOverride(Size availableSize)
		{
			// We don't support infinite available width, so we'll limit it to 32000 pixels.
			if (availableSize.Width > 32000)
				availableSize.Width = 32000;
			
			if (!canHorizontallyScroll && !availableSize.Width.IsClose(lastAvailableSize.Width))
				ClearVisualLines();
			lastAvailableSize = availableSize;
			
			foreach (UIElement layer in layers) {
				layer.Measure(availableSize);
			}
			MeasureInlineObjects();
			
			InvalidateVisual(); // = InvalidateArrange+InvalidateRender
			
			double maxWidth;
			if (document == null) {
				// no document -> create empty list of lines
				allVisualLines = new List<VisualLine>();
				visibleVisualLines = allVisualLines.AsReadOnly();
				maxWidth = 0;
			} else {
				inMeasure = true;
				try {
					maxWidth = CreateAndMeasureVisualLines(availableSize);
				} finally {
					inMeasure = false;
				}
			}
			
			// remove inline objects only at the end, so that inline objects that were re-used are not removed from the editor
			RemoveInlineObjectsNow();
			
			maxWidth += AdditionalHorizontalScrollAmount;
			double heightTreeHeight = this.DocumentHeight;
			TextEditorOptions options = this.Options;
			if (options.AllowScrollBelowDocument) {
				if (!double.IsInfinity(scrollViewport.Height)) {
					heightTreeHeight = Math.Max(heightTreeHeight, Math.Min(heightTreeHeight - 50, scrollOffset.Y) + scrollViewport.Height);
				}
			}
			
			textLayer.SetVisualLines(visibleVisualLines);
			
			SetScrollData(availableSize,
			              new Size(maxWidth, heightTreeHeight),
			              scrollOffset);
			if (VisualLinesChanged != null)
				VisualLinesChanged(this, EventArgs.Empty);
			
			return new Size(Math.Min(availableSize.Width, maxWidth), Math.Min(availableSize.Height, heightTreeHeight));
		}
		
		/// <summary>
		/// Build all VisualLines in the visible range.
		/// </summary>
		/// <returns>Width the longest line</returns>
		double CreateAndMeasureVisualLines(Size availableSize)
		{
			TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties();
			VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties);
			
			Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset);
			var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y);
			
			// number of pixels clipped from the first visual line(s)
			clippedPixelsOnTop = scrollOffset.Y - heightTree.GetVisualPosition(firstLineInView);
			// clippedPixelsOnTop should be >= 0, except for floating point inaccurracy.
			Debug.Assert(clippedPixelsOnTop >= -ExtensionMethods.Epsilon);
			
			newVisualLines = new List<VisualLine>();
			
			if (VisualLineConstructionStarting != null)
				VisualLineConstructionStarting(this, new VisualLineConstructionStartEventArgs(firstLineInView));
			
			var elementGeneratorsArray = elementGenerators.ToArray();
			var lineTransformersArray = lineTransformers.ToArray();
			var nextLine = firstLineInView;
			double maxWidth = 0;
			double yPos = -clippedPixelsOnTop;
			while (yPos < availableSize.Height && nextLine != null) {
				VisualLine visualLine = GetVisualLine(nextLine.LineNumber);
				if (visualLine == null) {
					visualLine = BuildVisualLine(nextLine,
					                             globalTextRunProperties, paragraphProperties,
					                             elementGeneratorsArray, lineTransformersArray,
					                             availableSize);
				}
				
				visualLine.VisualTop = scrollOffset.Y + yPos;
				
				nextLine = visualLine.LastDocumentLine.NextLine;
				
				yPos += visualLine.Height;
				
				foreach (TextLine textLine in visualLine.TextLines) {
					if (textLine.WidthIncludingTrailingWhitespace > maxWidth)
						maxWidth = textLine.WidthIncludingTrailingWhitespace;
				}
				
				newVisualLines.Add(visualLine);
			}
			
			foreach (VisualLine line in allVisualLines) {
				Debug.Assert(line.IsDisposed == false);
				if (!newVisualLines.Contains(line))
					DisposeVisualLine(line);
			}
			
			allVisualLines = newVisualLines;
			// visibleVisualLines = readonly copy of visual lines
			visibleVisualLines = new ReadOnlyCollection<VisualLine>(newVisualLines.ToArray());
			newVisualLines = null;
			
			if (allVisualLines.Any(line => line.IsDisposed)) {
				throw new InvalidOperationException("A visual line was disposed even though it is still in use.\n" +
				                                    "This can happen when Redraw() is called during measure for lines " +
				                                    "that are already constructed.");
			}
			return maxWidth;
		}
		#endregion
		
		#region BuildVisualLine
		TextFormatter formatter;
		internal TextViewCachedElements cachedElements;
		
		TextRunProperties CreateGlobalTextRunProperties()
		{
			var p = new GlobalTextRunProperties();
			p.typeface = this.CreateTypeface();
			p.fontRenderingEmSize = FontSize;
			p.foregroundBrush = (Brush)GetValue(Control.ForegroundProperty);
			ExtensionMethods.CheckIsFrozen(p.foregroundBrush);
			p.cultureInfo = CultureInfo.CurrentCulture;
			return p;
		}
		
		VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties)
		{
			return new VisualLineTextParagraphProperties {
				defaultTextRunProperties = defaultTextRunProperties,
				textWrapping = canHorizontallyScroll ? TextWrapping.NoWrap : TextWrapping.Wrap,
				tabSize = Options.IndentationSize * WideSpaceWidth
			};
		}
		
		VisualLine BuildVisualLine(DocumentLine documentLine,
		                           TextRunProperties globalTextRunProperties,
		                           VisualLineTextParagraphProperties paragraphProperties,
		                           VisualLineElementGenerator[] elementGeneratorsArray,
		                           IVisualLineTransformer[] lineTransformersArray,
		                           Size availableSize)
		{
			if (heightTree.GetIsCollapsed(documentLine.LineNumber))
				throw new InvalidOperationException("Trying to build visual line from collapsed line");
			
			Debug.WriteLine("Building line " + documentLine.LineNumber);
			
			VisualLine visualLine = new VisualLine(this, documentLine);
			VisualLineTextSource textSource = new VisualLineTextSource(visualLine) {
				Document = document,
				GlobalTextRunProperties = globalTextRunProperties,
				TextView = this
			};
			
			visualLine.ConstructVisualElements(textSource, elementGeneratorsArray);
			
			if (visualLine.FirstDocumentLine != visualLine.LastDocumentLine) {
				// Check whether the lines are collapsed correctly:
				double firstLinePos = heightTree.GetVisualPosition(visualLine.FirstDocumentLine.NextLine);
				double lastLinePos = heightTree.GetVisualPosition(visualLine.LastDocumentLine.NextLine ?? visualLine.LastDocumentLine);
				if (!firstLinePos.IsClose(lastLinePos)) {
					for (int i = visualLine.FirstDocumentLine.LineNumber + 1; i <= visualLine.LastDocumentLine.LineNumber; i++) {
						if (!heightTree.GetIsCollapsed(i))
							throw new InvalidOperationException("Line " + i + " was skipped by a VisualLineElementGenerator, but it is not collapsed.");
					}
					throw new InvalidOperationException("All lines collapsed but visual pos different - height tree inconsistency?");
				}
			}
			
			visualLine.RunTransformers(textSource, lineTransformersArray);
			
			// now construct textLines:
			int textOffset = 0;
			TextLineBreak lastLineBreak = null;
			var textLines = new List<TextLine>();
			paragraphProperties.indent = 0;
			paragraphProperties.firstLineInParagraph = true;
			while (textOffset <= visualLine.VisualLengthWithEndOfLineMarker) {
				TextLine textLine = formatter.FormatLine(
					textSource,
					textOffset,
					availableSize.Width,
					paragraphProperties,
					lastLineBreak
				);
				textLines.Add(textLine);
				textOffset += textLine.Length;
				
				// exit loop so that we don't do the indentation calculation if there's only a single line
				if (textOffset >= visualLine.VisualLengthWithEndOfLineMarker)
					break;
				
				if (paragraphProperties.firstLineInParagraph) {
					paragraphProperties.firstLineInParagraph = false;
					
					TextEditorOptions options = this.Options;
					double indentation = 0;
					if (options.InheritWordWrapIndentation) {
						// determine indentation for next line:
						int indentVisualColumn = GetIndentationVisualColumn(visualLine);
						if (indentVisualColumn > 0 && indentVisualColumn < textOffset) {
							indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn, 0));
						}
					}
					indentation += options.WordWrapIndentation;
					// apply the calculated indentation unless it's more than half of the text editor size:
					if (indentation > 0 && indentation * 2 < availableSize.Width)
						paragraphProperties.indent = indentation;
				}
				lastLineBreak = textLine.GetTextLineBreak();
			}
			visualLine.SetTextLines(textLines);
			heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height);
			return visualLine;
		}
		
		static int GetIndentationVisualColumn(VisualLine visualLine)
		{
			if (visualLine.Elements.Count == 0)
				return 0;
			int column = 0;
			int elementIndex = 0;
			VisualLineElement element = visualLine.Elements[elementIndex];
			while (element.IsWhitespace(column)) {
				column++;
				if (column == element.VisualColumn + element.VisualLength) {
					elementIndex++;
					if (elementIndex == visualLine.Elements.Count)
						break;
					element = visualLine.Elements[elementIndex];
				}
			}
			return column;
		}
		#endregion
		
		#region Arrange
		/// <summary>
		/// Arrange implementation.
		/// </summary>
		protected override Size ArrangeOverride(Size finalSize)
		{
			EnsureVisualLines();
			
			foreach (UIElement layer in layers) {
				layer.Arrange(new Rect(new Point(0, 0), finalSize));
			}
			
			if (document == null || allVisualLines.Count == 0)
				return finalSize;
			
			// validate scroll position
			Vector newScrollOffset = scrollOffset;
			if (scrollOffset.X + finalSize.Width > scrollExtent.Width) {
				newScrollOffset.X = Math.Max(0, scrollExtent.Width - finalSize.Width);
			}
			if (scrollOffset.Y + finalSize.Height > scrollExtent.Height) {
				newScrollOffset.Y = Math.Max(0, scrollExtent.Height - finalSize.Height);
			}
			if (SetScrollData(scrollViewport, scrollExtent, newScrollOffset))
				InvalidateMeasure(DispatcherPriority.Normal);
			
			//Debug.WriteLine("Arrange finalSize=" + finalSize + ", scrollOffset=" + scrollOffset);
			
//			double maxWidth = 0;
			
			if (visibleVisualLines != null) {
				Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop);
				foreach (VisualLine visualLine in visibleVisualLines) {
					int offset = 0;
					foreach (TextLine textLine in visualLine.TextLines) {
						foreach (var span in textLine.GetTextRunSpans()) {
							InlineObjectRun inline = span.Value as InlineObjectRun;
							if (inline != null && inline.VisualLine != null) {
								Debug.Assert(inlineObjects.Contains(inline));
								double distance = textLine.GetDistanceFromCharacterHit(new CharacterHit(offset, 0));
								inline.Element.Arrange(new Rect(new Point(pos.X + distance, pos.Y), inline.Element.DesiredSize));
							}
							offset += span.Length;
						}
						pos.Y += textLine.Height;
					}
				}
			}
			InvalidateCursor();
			
			return finalSize;
		}
		#endregion
		
		#region Render
		readonly ObserveAddRemoveCollection<IBackgroundRenderer> backgroundRenderers;
		
		/// <summary>
		/// Gets the list of background renderers.
		/// </summary>
		public IList<IBackgroundRenderer> BackgroundRenderers {
			get { return backgroundRenderers; }
		}
		
		void BackgroundRenderer_Added(IBackgroundRenderer renderer)
		{
			ConnectToTextView(renderer);
			InvalidateLayer(renderer.Layer);
		}
		
		void BackgroundRenderer_Removed(IBackgroundRenderer renderer)
		{
			DisconnectFromTextView(renderer);
			InvalidateLayer(renderer.Layer);
		}
		
		/// <inheritdoc/>
		protected override void OnRender(DrawingContext drawingContext)
		{
			RenderBackground(drawingContext, KnownLayer.Background);
			foreach (var line in visibleVisualLines) {
				Brush currentBrush = null;
				int startVC = 0;
				int length = 0;
				foreach (var element in line.Elements) {
					if (currentBrush == null || !currentBrush.Equals(element.BackgroundBrush)) {
						if (currentBrush != null) {
							BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder();
							builder.AlignToWholePixels = true;
							builder.CornerRadius = 3;
							foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length))
								builder.AddRectangle(this, rect);
							Geometry geometry = builder.CreateGeometry();
							if (geometry != null) {
								drawingContext.DrawGeometry(currentBrush, null, geometry);
							}
						}
						startVC = element.VisualColumn;
						length = element.DocumentLength;
						currentBrush = element.BackgroundBrush;
					} else {
						length += element.VisualLength;
					}
				}
				if (currentBrush != null) {
					BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder();
					builder.AlignToWholePixels = true;
					builder.CornerRadius = 3;
					foreach (var rect in BackgroundGeometryBuilder.GetRectsFromVisualSegment(this, line, startVC, startVC + length))
						builder.AddRectangle(this, rect);
					Geometry geometry = builder.CreateGeometry();
					if (geometry != null) {
						drawingContext.DrawGeometry(currentBrush, null, geometry);
					}
				}
			}
		}
		
		internal void RenderBackground(DrawingContext drawingContext, KnownLayer layer)
		{
			foreach (IBackgroundRenderer bg in backgroundRenderers) {
				if (bg.Layer == layer) {
					bg.Draw(this, drawingContext);
				}
			}
		}
		
		internal void ArrangeTextLayer(IList<VisualLineDrawingVisual> visuals)
		{
			Point pos = new Point(-scrollOffset.X, -clippedPixelsOnTop);
			foreach (VisualLineDrawingVisual visual in visuals) {
				TranslateTransform t = visual.Transform as TranslateTransform;
				if (t == null || t.X != pos.X || t.Y != pos.Y) {
					visual.Transform = new TranslateTransform(pos.X, pos.Y);
					visual.Transform.Freeze();
				}
				pos.Y += visual.Height;
			}
		}
		#endregion
		
		#region IScrollInfo implementation
		/// <summary>
		/// Size of the document, in pixels.
		/// </summary>
		Size scrollExtent;
		
		/// <summary>
		/// Offset of the scroll position.
		/// </summary>
		Vector scrollOffset;
		
		/// <summary>
		/// Size of the viewport.
		/// </summary>
		Size scrollViewport;
		
		void ClearScrollData()
		{
			SetScrollData(new Size(), new Size(), new Vector());
		}
		
		bool SetScrollData(Size viewport, Size extent, Vector offset)
		{
			if (!(viewport.IsClose(this.scrollViewport)
			      && extent.IsClose(this.scrollExtent)
			      && offset.IsClose(this.scrollOffset)))
			{
				this.scrollViewport = viewport;
				this.scrollExtent = extent;
				SetScrollOffset(offset);
				this.OnScrollChange();
				return true;
			}
			return false;
		}
		
		void OnScrollChange()
		{
			ScrollViewer scrollOwner = ((IScrollInfo)this).ScrollOwner;
			if (scrollOwner != null) {
				scrollOwner.InvalidateScrollInfo();
			}
		}
		
		bool canVerticallyScroll;
		bool IScrollInfo.CanVerticallyScroll {
			get { return canVerticallyScroll; }
			set {
				if (canVerticallyScroll != value) {
					canVerticallyScroll = value;
					InvalidateMeasure(DispatcherPriority.Normal);
				}
			}
		}
		bool canHorizontallyScroll;
		bool IScrollInfo.CanHorizontallyScroll {
			get { return canHorizontallyScroll; }
			set {
				if (canHorizontallyScroll != value) {
					canHorizontallyScroll = value;
					ClearVisualLines();
					InvalidateMeasure(DispatcherPriority.Normal);
				}
			}
		}
		
		double IScrollInfo.ExtentWidth {
			get { return scrollExtent.Width; }
		}
		
		double IScrollInfo.ExtentHeight {
			get { return scrollExtent.Height; }
		}
		
		double IScrollInfo.ViewportWidth {
			get { return scrollViewport.Width; }
		}
		
		double IScrollInfo.ViewportHeight {
			get { return scrollViewport.Height; }
		}
		
		/// <summary>
		/// Gets the horizontal scroll offset.
		/// </summary>
		public double HorizontalOffset {
			get { return scrollOffset.X; }
		}
		
		/// <summary>
		/// Gets the vertical scroll offset.
		/// </summary>
		public double VerticalOffset {
			get { return scrollOffset.Y; }
		}
		
		/// <summary>
		/// Gets the scroll offset;
		/// </summary>
		public Vector ScrollOffset {
			get { return scrollOffset; }
		}
		
		/// <summary>
		/// Occurs when the scroll offset has changed.
		/// </summary>
		public event EventHandler ScrollOffsetChanged;
		
		void SetScrollOffset(Vector vector)
		{
			if (!canHorizontallyScroll)
				vector.X = 0;
			if (!canVerticallyScroll)
				vector.Y = 0;
			
			if (!scrollOffset.IsClose(vector)) {
				scrollOffset = vector;
				if (ScrollOffsetChanged != null)
					ScrollOffsetChanged(this, EventArgs.Empty);
			}
		}
		
		ScrollViewer IScrollInfo.ScrollOwner { get; set; }
		
		void IScrollInfo.LineUp()
		{
			((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - DefaultLineHeight);
		}
		
		void IScrollInfo.LineDown()
		{
			((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + DefaultLineHeight);
		}
		
		void IScrollInfo.LineLeft()
		{
			((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - WideSpaceWidth);
		}
		
		void IScrollInfo.LineRight()
		{
			((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + WideSpaceWidth);
		}
		
		void IScrollInfo.PageUp()
		{
			((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y - scrollViewport.Height);
		}
		
		void IScrollInfo.PageDown()
		{
			((IScrollInfo)this).SetVerticalOffset(scrollOffset.Y + scrollViewport.Height);
		}
		
		void IScrollInfo.PageLeft()
		{
			((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X - scrollViewport.Width);
		}
		
		void IScrollInfo.PageRight()
		{
			((IScrollInfo)this).SetHorizontalOffset(scrollOffset.X + scrollViewport.Width);
		}
		
		void IScrollInfo.MouseWheelUp()
		{
			((IScrollInfo)this).SetVerticalOffset(
				scrollOffset.Y - (SystemParameters.WheelScrollLines * DefaultLineHeight));
			OnScrollChange();
		}
		
		void IScrollInfo.MouseWheelDown()
		{
			((IScrollInfo)this).SetVerticalOffset(
				scrollOffset.Y + (SystemParameters.WheelScrollLines * DefaultLineHeight));
			OnScrollChange();
		}
		
		void IScrollInfo.MouseWheelLeft()
		{
			((IScrollInfo)this).SetHorizontalOffset(
				scrollOffset.X - (SystemParameters.WheelScrollLines * WideSpaceWidth));
			OnScrollChange();
		}
		
		void IScrollInfo.MouseWheelRight()
		{
			((IScrollInfo)this).SetHorizontalOffset(
				scrollOffset.X + (SystemParameters.WheelScrollLines * WideSpaceWidth));
			OnScrollChange();
		}
		
		bool defaultTextMetricsValid;
		double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling.
		double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling.
		double defaultBaseline; // Baseline of a line containing 'x'. Used for TextTop/TextBottom calculation.
		
		/// <summary>
		/// Gets the width of a 'wide space' (the space width used for calculating the tab size).
		/// </summary>
		/// <remarks>
		/// This is the width of an 'x' in the current font.
		/// We do not measure the width of an actual space as that would lead to tiny tabs in
		/// some proportional fonts.
		/// For monospaced fonts, this property will return the expected value, as 'x' and ' ' have the same width.
		/// </remarks>
		public double WideSpaceWidth {
			get {
				CalculateDefaultTextMetrics();
				return wideSpaceWidth;
			}
		}
		
		/// <summary>
		/// Gets the default line height. This is the height of an empty line or a line containing regular text.
		/// Lines that include formatted text or custom UI elements may have a different line height.
		/// </summary>
		public double DefaultLineHeight {
			get {
				CalculateDefaultTextMetrics();
				return defaultLineHeight;
			}
		}
		
		/// <summary>
		/// Gets the default baseline position. This is the difference between <see cref="VisualYPosition.TextTop"/>
		/// and <see cref="VisualYPosition.Baseline"/> for a line containing regular text.
		/// Lines that include formatted text or custom UI elements may have a different baseline.
		/// </summary>
		public double DefaultBaseline {
			get {
				CalculateDefaultTextMetrics();
				return defaultBaseline;
			}
		}
		
		void InvalidateDefaultTextMetrics()
		{
			defaultTextMetricsValid = false;
			if (heightTree != null) {
				// calculate immediately so that height tree gets updated
				CalculateDefaultTextMetrics();
			}
		}
		
		void CalculateDefaultTextMetrics()
		{
			if (defaultTextMetricsValid)
				return;
			defaultTextMetricsValid = true;
			if (formatter != null) {
				var textRunProperties = CreateGlobalTextRunProperties();
				using (var line = formatter.FormatLine(
					new SimpleTextSource("x", textRunProperties),
					0, 32000,
					new VisualLineTextParagraphProperties { defaultTextRunProperties = textRunProperties },
					null))
				{
					wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace);
					defaultBaseline = Math.Max(1, line.Baseline);
					defaultLineHeight = Math.Max(1, line.Height);
				}
			} else {
				wideSpaceWidth = FontSize / 2;
				defaultBaseline = FontSize;
				defaultLineHeight = FontSize + 3;
			}
			// Update heightTree.DefaultLineHeight, if a document is loaded.
			if (heightTree != null)
				heightTree.DefaultLineHeight = defaultLineHeight;
		}
		
		static double ValidateVisualOffset(double offset)
		{
			if (double.IsNaN(offset))
				throw new ArgumentException("offset must not be NaN");
			if (offset < 0)
				return 0;
			else
				return offset;
		}
		
		void IScrollInfo.SetHorizontalOffset(double offset)
		{
			offset = ValidateVisualOffset(offset);
			if (!scrollOffset.X.IsClose(offset)) {
				SetScrollOffset(new Vector(offset, scrollOffset.Y));
				InvalidateVisual();
				textLayer.InvalidateVisual();
			}
		}
		
		void IScrollInfo.SetVerticalOffset(double offset)
		{
			offset = ValidateVisualOffset(offset);
			if (!scrollOffset.Y.IsClose(offset)) {
				SetScrollOffset(new Vector(scrollOffset.X, offset));
				InvalidateMeasure(DispatcherPriority.Normal);
			}
		}
		
		Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle)
		{
			if (rectangle.IsEmpty || visual == null || visual == this || !this.IsAncestorOf(visual)) {
				return Rect.Empty;
			}
			// Convert rectangle into our coordinate space.
			GeneralTransform childTransform = visual.TransformToAncestor(this);
			rectangle = childTransform.TransformBounds(rectangle);
			
			MakeVisible(Rect.Offset(rectangle, scrollOffset));
			
			return rectangle;
		}
		
		/// <summary>
		/// Scrolls the text view so that the specified rectangle gets visible.
		/// </summary>
		public void MakeVisible(Rect rectangle)
		{
			Rect visibleRectangle = new Rect(scrollOffset.X, scrollOffset.Y,
			                                 scrollViewport.Width, scrollViewport.Height);
			Vector newScrollOffset = scrollOffset;
			if (rectangle.Left < visibleRectangle.Left) {
				if (rectangle.Right > visibleRectangle.Right) {
					newScrollOffset.X = rectangle.Left + rectangle.Width / 2;
				} else {
					newScrollOffset.X = rectangle.Left;
				}
			} else if (rectangle.Right > visibleRectangle.Right) {
				newScrollOffset.X = rectangle.Right - scrollViewport.Width;
			}
			if (rectangle.Top < visibleRectangle.Top) {
				if (rectangle.Bottom > visibleRectangle.Bottom) {
					newScrollOffset.Y = rectangle.Top + rectangle.Height / 2;
				} else {
					newScrollOffset.Y = rectangle.Top;
				}
			} else if (rectangle.Bottom > visibleRectangle.Bottom) {
				newScrollOffset.Y = rectangle.Bottom - scrollViewport.Height;
			}
			newScrollOffset.X = ValidateVisualOffset(newScrollOffset.X);
			newScrollOffset.Y = ValidateVisualOffset(newScrollOffset.Y);
			if (!scrollOffset.IsClose(newScrollOffset)) {
				SetScrollOffset(newScrollOffset);
				this.OnScrollChange();
				InvalidateMeasure(DispatcherPriority.Normal);
			}
		}
		#endregion
		
		#region Visual element mouse handling
		/// <inheritdoc/>
		protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
		{
			// accept clicks even where the text area draws no background
			return new PointHitTestResult(this, hitTestParameters.HitPoint);
		}
		
		[ThreadStatic] static bool invalidCursor;
		
		/// <summary>
		/// Updates the mouse cursor by calling <see cref="Mouse.UpdateCursor"/>, but with input priority.
		/// </summary>
		public static void InvalidateCursor()
		{
			if (!invalidCursor) {
				invalidCursor = true;
				Dispatcher.CurrentDispatcher.BeginInvoke(
					DispatcherPriority.Input,
					new Action(
						delegate {
							invalidCursor = false;
							Mouse.UpdateCursor();
						}));
			}
		}
		
		/// <inheritdoc/>
		protected override void OnQueryCursor(QueryCursorEventArgs e)
		{
			VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
			if (element != null) {
				element.OnQueryCursor(e);
			}
		}
		
		/// <inheritdoc/>
		protected override void OnMouseDown(MouseButtonEventArgs e)
		{
			base.OnMouseDown(e);
			if (!e.Handled) {
				EnsureVisualLines();
				VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
				if (element != null) {
					element.OnMouseDown(e);
				}
			}
		}
		
		/// <inheritdoc/>
		protected override void OnMouseUp(MouseButtonEventArgs e)
		{
			base.OnMouseUp(e);
			if (!e.Handled) {
				EnsureVisualLines();
				VisualLineElement element = GetVisualLineElementFromPosition(e.GetPosition(this) + scrollOffset);
				if (element != null) {
					element.OnMouseUp(e);
				}
			}
		}
		#endregion
		
		#region Getting elements from Visual Position
		/// <summary>
		/// Gets the visual line at the specified document position (relative to start of document).
		/// Returns null if there is no visual line for the position (e.g. the position is outside the visible
		/// text area).
		/// </summary>
		public VisualLine GetVisualLineFromVisualTop(double visualTop)
		{
			// TODO: change this method to also work outside the visible range -
			// required to make GetPosition work as expected!
			EnsureVisualLines();
			foreach (VisualLine vl in this.VisualLines) {
				if (visualTop < vl.VisualTop)
					continue;
				if (visualTop < vl.VisualTop + vl.Height)
					return vl;
			}
			return null;
		}
		
		/// <summary>
		/// Gets the visual top position (relative to start of document) from a document line number.
		/// </summary>
		public double GetVisualTopByDocumentLine(int line)
		{
			VerifyAccess();
			if (heightTree == null)
				throw ThrowUtil.NoDocumentAssigned();
			return heightTree.GetVisualPosition(heightTree.GetLineByNumber(line));
		}
		
		VisualLineElement GetVisualLineElementFromPosition(Point visualPosition)
		{
			VisualLine vl = GetVisualLineFromVisualTop(visualPosition.Y);
			if (vl != null) {
				int column = vl.GetVisualColumnFloor(visualPosition);
//				Debug.WriteLine(vl.FirstDocumentLine.LineNumber + " vc " + column);
				foreach (VisualLineElement element in vl.Elements) {
					if (element.VisualColumn + element.VisualLength <= column)
						continue;
					return element;
				}
			}
			return null;
		}
		#endregion
		
		#region Visual Position <-> TextViewPosition
		/// <summary>
		/// Gets the visual position from a text view position.
		/// </summary>
		/// <param name="position">The text view position.</param>
		/// <param name="yPositionMode">The mode how to retrieve the Y position.</param>
		/// <returns>The position in WPF device-independent pixels relative
		/// to the top left corner of the document.</returns>
		public Point GetVisualPosition(TextViewPosition position, VisualYPosition yPositionMode)
		{
			VerifyAccess();
			if (this.Document == null)
				throw ThrowUtil.NoDocumentAssigned();
			DocumentLine documentLine = this.Document.GetLineByNumber(position.Line);
			VisualLine visualLine = GetOrConstructVisualLine(documentLine);
			int visualColumn = position.VisualColumn;
			if (visualColumn < 0) {
				int offset = documentLine.Offset + position.Column - 1;
				visualColumn = visualLine.GetVisualColumn(offset - visualLine.FirstDocumentLine.Offset);
			}
			return visualLine.GetVisualPosition(visualColumn, yPositionMode);
		}
		
		/// <summary>
		/// Gets the text view position from the specified visual position.
		/// If the position is within a character, it is rounded to the next character boundary.
		/// </summary>
		/// <param name="visualPosition">The position in WPF device-independent pixels relative
		/// to the top left corner of the document.</param>
		/// <returns>The logical position, or null if the position is outside the document.</returns>
		public TextViewPosition? GetPosition(Point visualPosition)
		{
			VerifyAccess();
			if (this.Document == null)
				throw ThrowUtil.NoDocumentAssigned();
			VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
			if (line == null)
				return null;
			int visualColumn = line.GetVisualColumn(visualPosition);
			int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
			return new TextViewPosition(document.GetLocation(documentOffset), visualColumn);
		}
		
		/// <summary>
		/// Gets the text view position from the specified visual position.
		/// If the position is inside a character, the position in front of the character is returned.
		/// </summary>
		/// <param name="visualPosition">The position in WPF device-independent pixels relative
		/// to the top left corner of the document.</param>
		/// <returns>The logical position, or null if the position is outside the document.</returns>
		public TextViewPosition? GetPositionFloor(Point visualPosition)
		{
			VerifyAccess();
			if (this.Document == null)
				throw ThrowUtil.NoDocumentAssigned();
			VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
			if (line == null)
				return null;
			int visualColumn = line.GetVisualColumnFloor(visualPosition);
			int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
			return new TextViewPosition(document.GetLocation(documentOffset), visualColumn);
		}
		#endregion
		
		#region Service Provider
		readonly ServiceContainer services = new ServiceContainer();
		
		/// <summary>
		/// Gets a service container used to associate services with the text view.
		/// </summary>
		public ServiceContainer Services {
			get { return services; }
		}
		
		object IServiceProvider.GetService(Type serviceType)
		{
			return services.GetService(serviceType);
		}
		
		void ConnectToTextView(object obj)
		{
			ITextViewConnect c = obj as ITextViewConnect;
			if (c != null)
				c.AddToTextView(this);
		}
		
		void DisconnectFromTextView(object obj)
		{
			ITextViewConnect c = obj as ITextViewConnect;
			if (c != null)
				c.RemoveFromTextView(this);
		}
		#endregion
		
		#region MouseHover
		/// <summary>
		/// The PreviewMouseHover event.
		/// </summary>
		public static readonly RoutedEvent PreviewMouseHoverEvent =
			EventManager.RegisterRoutedEvent("PreviewMouseHover", RoutingStrategy.Tunnel,
			                                 typeof(MouseEventHandler), typeof(TextView));
		/// <summary>
		/// The MouseHover event.
		/// </summary>
		public static readonly RoutedEvent MouseHoverEvent =
			EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble,
			                                 typeof(MouseEventHandler), typeof(TextView));
		
		/// <summary>
		/// The PreviewMouseHoverStopped event.
		/// </summary>
		public static readonly RoutedEvent PreviewMouseHoverStoppedEvent =
			EventManager.RegisterRoutedEvent("PreviewMouseHoverStopped", RoutingStrategy.Tunnel,
			                                 typeof(MouseEventHandler), typeof(TextView));
		/// <summary>
		/// The MouseHoverStopped event.
		/// </summary>
		public static readonly RoutedEvent MouseHoverStoppedEvent =
			EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble,
			                                 typeof(MouseEventHandler), typeof(TextView));
		
		
		/// <summary>
		/// Occurs when the mouse has hovered over a fixed location for some time.
		/// </summary>
		public event MouseEventHandler PreviewMouseHover {
			add { AddHandler(PreviewMouseHoverEvent, value); }
			remove { RemoveHandler(PreviewMouseHoverEvent, value); }
		}
		
		/// <summary>
		/// Occurs when the mouse has hovered over a fixed location for some time.
		/// </summary>
		public event MouseEventHandler MouseHover {
			add { AddHandler(MouseHoverEvent, value); }
			remove { RemoveHandler(MouseHoverEvent, value); }
		}
		
		/// <summary>
		/// Occurs when the mouse had previously hovered but now started moving again.
		/// </summary>
		public event MouseEventHandler PreviewMouseHoverStopped {
			add { AddHandler(PreviewMouseHoverStoppedEvent, value); }
			remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); }
		}
		
		/// <summary>
		/// Occurs when the mouse had previously hovered but now started moving again.
		/// </summary>
		public event MouseEventHandler MouseHoverStopped {
			add { AddHandler(MouseHoverStoppedEvent, value); }
			remove { RemoveHandler(MouseHoverStoppedEvent, value); }
		}
		
		MouseHoverLogic hoverLogic;
		
		void RaiseHoverEventPair(MouseEventArgs e, RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent)
		{
			var mouseDevice = e.MouseDevice;
			var stylusDevice = e.StylusDevice;
			int inputTime = Environment.TickCount;
			var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
				RoutedEvent = tunnelingEvent,
				Source = this
			};
			RaiseEvent(args1);
			var args2 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
				RoutedEvent = bubblingEvent,
				Source = this,
				Handled = args1.Handled
			};
			RaiseEvent(args2);
		}
		#endregion
		
		/// <summary>
		/// Collapses lines for the purpose of scrolling. <see cref="DocumentLine"/>s marked as collapsed will be hidden
		/// and not used to start the generation of a <see cref="VisualLine"/>.
		/// </summary>
		/// <remarks>
		/// This method is meant for <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span
		/// multiple <see cref="DocumentLine"/>s. Do not call it without providing a corresponding
		/// <see cref="VisualLineElementGenerator"/>.
		/// If you want to create collapsible text sections, see <see cref="Folding.FoldingManager"/>.
		/// 
		/// Note that if you want a VisualLineElement to span from line N to line M, then you need to collapse only the lines
		/// N+1 to M. Do not collapse line N itself.
		/// 
		/// When you no longer need the section to be collapsed, call <see cref="CollapsedLineSection.Uncollapse()"/> on the
		/// <see cref="CollapsedLineSection"/> returned from this method.
		/// </remarks>
		public CollapsedLineSection CollapseLines(DocumentLine start, DocumentLine end)
		{
			VerifyAccess();
			if (heightTree == null)
				throw ThrowUtil.NoDocumentAssigned();
			return heightTree.CollapseText(start, end);
		}
		
		/// <summary>
		/// Gets the height of the document.
		/// </summary>
		public double DocumentHeight {
			get {
				// return 0 if there is no document = no heightTree
				return heightTree != null ? heightTree.TotalHeight : 0;
			}
		}
		
		/// <summary>
		/// Gets the document line at the specified visual position.
		/// </summary>
		public DocumentLine GetDocumentLineByVisualTop(double visualTop)
		{
			VerifyAccess();
			if (heightTree == null)
				throw ThrowUtil.NoDocumentAssigned();
			return heightTree.GetLineByVisualPosition(visualTop);
		}
		
		/// <inheritdoc/>
		protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
		{
			base.OnPropertyChanged(e);
			if (TextFormatterFactory.PropertyChangeAffectsTextFormatter(e.Property)) {
				// first, create the new text formatter:
				RecreateTextFormatter();
				// changing text formatter requires recreating the cached elements
				RecreateCachedElements();
				// and we need to re-measure the font metrics:
				InvalidateDefaultTextMetrics();
			} else if (e.Property == Control.ForegroundProperty
			           || e.Property == TextView.NonPrintableCharacterBrushProperty
			           || e.Property == TextView.LinkTextBackgroundBrushProperty
			           || e.Property == TextView.LinkTextForegroundBrushProperty)
			{
				// changing brushes requires recreating the cached elements
				RecreateCachedElements();
				Redraw();
			}
			if (e.Property == Control.FontFamilyProperty
			    || e.Property == Control.FontSizeProperty
			    || e.Property == Control.FontStretchProperty
			    || e.Property == Control.FontStyleProperty
			    || e.Property == Control.FontWeightProperty)
			{
				// changing font properties requires recreating cached elements
				RecreateCachedElements();
				// and we need to re-measure the font metrics:
				InvalidateDefaultTextMetrics();
				Redraw();
			}
			if (e.Property == ColumnRulerPenProperty) {
				columnRulerRenderer.SetRuler(this.Options.ColumnRulerPosition, this.ColumnRulerPen);
			}
		}
		
		/// <summary>
		/// The pen used to draw the column ruler.
		/// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
		/// </summary>
		public static readonly DependencyProperty ColumnRulerPenProperty =
			DependencyProperty.Register("ColumnRulerBrush", typeof(Pen), typeof(TextView),
			                            new FrameworkPropertyMetadata(CreateFrozenPen(Brushes.LightGray)));
		
		static Pen CreateFrozenPen(SolidColorBrush brush)
		{
			Pen pen = new Pen(brush, 1);
			pen.Freeze();
			return pen;
		}
		
		/// <summary>
		/// Gets/Sets the pen used to draw the column ruler.
		/// <seealso cref="TextEditorOptions.ShowColumnRuler"/>
		/// </summary>
		public Pen ColumnRulerPen {
			get { return (Pen)GetValue(ColumnRulerPenProperty); }
			set { SetValue(ColumnRulerPenProperty, value); }
		}
	}
}
