// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.ComponentModel;
using System.Linq;
using Unity.CodeEditor.Document;
using Unity.CodeEditor.Utils;
using UnityEngine;
using UnityEngine.UIElements;

namespace Unity.CodeEditor.Editing
{
    /// <summary>
    /// Handles selection of text using the mouse.
    /// </summary>
    internal sealed class SelectionMouseHandler //: ITextAreaInputHandler
    {
        #region enum SelectionMode

        private enum SelectionMode
        {
            /// <summary>
            /// no selection (no mouse button down)
            /// </summary>
            None,
            /// <summary>
            /// left mouse button down on selection, might be normal click
            /// or might be drag'n'drop
            /// </summary>
            PossibleDragStart,
            /// <summary>
            /// dragging text
            /// </summary>
            Drag,
            /// <summary>
            /// normal selection (click+drag)
            /// </summary>
            Normal,
            /// <summary>
            /// whole-word selection (double click+drag or ctrl+click+drag)
            /// </summary>
            WholeWord,
            /// <summary>
            /// whole-line selection (triple click+drag)
            /// </summary>
            WholeLine,
            /// <summary>
            /// rectangular selection (alt+click+drag)
            /// </summary>
            Rectangular
        }
        #endregion

        private SelectionMode _mode;
        private AnchorSegment _startWord;
        private Vector2 _possibleDragStartMousePos;
        private int _consecutiveClicks;
        private float _lastClickTime;
        private Vector2 _lastClickPosition;

        #region Constructor + Attach + Detach
        internal SelectionMouseHandler(TextArea textArea)
        {
            TextArea = textArea ?? throw new ArgumentNullException(nameof(textArea));
        }

        internal TextArea TextArea { get; }

        internal void Attach()
        {
            //TextArea.PointerPressed += TextArea_MouseLeftButtonDown;
            //TextArea.PointerMoved += TextArea_MouseMove;
            //TextArea.PointerReleased += TextArea_MouseLeftButtonUp;
            //textArea.QueryCursor += textArea_QueryCursor;
            TextArea.OptionChanged += TextArea_OptionChanged;

            _enableTextDragDrop = TextArea.Options.EnableTextDragDrop;
            if (_enableTextDragDrop)
            {
                AttachDragDrop();
            }
        }

        internal void Detach()
        {
            _mode = SelectionMode.None;
            //TextArea.PointerPressed -= TextArea_MouseLeftButtonDown;
            //TextArea.PointerMoved -= TextArea_MouseMove;
            //TextArea.PointerReleased -= TextArea_MouseLeftButtonUp;
            //textArea.QueryCursor -= textArea_QueryCursor;
            TextArea.OptionChanged -= TextArea_OptionChanged;
            if (_enableTextDragDrop)
            {
                DetachDragDrop();
            }
        }

        private void AttachDragDrop()
        {
            //textArea.AllowDrop = true;
            //textArea.GiveFeedback += textArea_GiveFeedback;
            //textArea.QueryContinueDrag += textArea_QueryContinueDrag;
            //textArea.DragEnter += textArea_DragEnter;
            //textArea.DragOver += textArea_DragOver;
            //textArea.DragLeave += textArea_DragLeave;
            //textArea.Drop += textArea_Drop;
        }

        private void DetachDragDrop()
        {
            //textArea.AllowDrop = false;
            //textArea.GiveFeedback -= textArea_GiveFeedback;
            //textArea.QueryContinueDrag -= textArea_QueryContinueDrag;
            //textArea.DragEnter -= textArea_DragEnter;
            //textArea.DragOver -= textArea_DragOver;
            //textArea.DragLeave -= textArea_DragLeave;
            //textArea.Drop -= textArea_Drop;
        }

        private bool _enableTextDragDrop;

        private void TextArea_OptionChanged(object sender, PropertyChangedEventArgs e)
        {
            var newEnableTextDragDrop = TextArea.Options.EnableTextDragDrop;
            if (newEnableTextDragDrop != _enableTextDragDrop)
            {
                _enableTextDragDrop = newEnableTextDragDrop;
                if (newEnableTextDragDrop)
                    AttachDragDrop();
                else
                    DetachDragDrop();
            }
        }
        #endregion

        #region Dropping text
        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_DragEnter(object sender, DragEventArgs e)
        //{
        //	try {
        //		e.Effects = GetEffect(e);
        //		textArea.Caret.Show();
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}

        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_DragOver(object sender, DragEventArgs e)
        //{
        //	try {
        //		e.Effects = GetEffect(e);
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}

        //DragDropEffects GetEffect(DragEventArgs e)
        //{
        //	if (e.Data.GetDataPresent(DataFormats.UnicodeText, true)) {
        //		e.Handled = true;
        //		int visualColumn;
        //		bool isAtEndOfLine;
        //		int offset = GetOffsetFromMousePosition(e.GetPosition(textArea.TextView), out visualColumn, out isAtEndOfLine);
        //		if (offset >= 0) {
        //			textArea.Caret.Position = new TextViewPosition(textArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
        //			textArea.Caret.DesiredXPos = double.NaN;
        //			if (textArea.ReadOnlySectionProvider.CanInsert(offset)) {
        //				if ((e.AllowedEffects & DragDropEffects.Move) == DragDropEffects.Move
        //				    && (e.KeyStates & DragDropKeyStates.ControlKey) != DragDropKeyStates.ControlKey)
        //				{
        //					return DragDropEffects.Move;
        //				} else {
        //					return e.AllowedEffects & DragDropEffects.Copy;
        //				}
        //			}
        //		}
        //	}
        //	return DragDropEffects.None;
        //}

        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_DragLeave(object sender, DragEventArgs e)
        //{
        //	try {
        //		e.Handled = true;
        //		if (!textArea.IsKeyboardFocusWithin)
        //			textArea.Caret.Hide();
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}

        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_Drop(object sender, DragEventArgs e)
        //{
        //	try {
        //		DragDropEffects effect = GetEffect(e);
        //		e.Effects = effect;
        //		if (effect != DragDropEffects.None) {
        //			int start = textArea.Caret.Offset;
        //			if (mode == SelectionMode.Drag && textArea.Selection.Contains(start)) {
        //				Debug.WriteLine("Drop: did not drop: drop target is inside selection");
        //				e.Effects = DragDropEffects.None;
        //			} else {
        //				Debug.WriteLine("Drop: insert at " + start);

        //				var pastingEventArgs = new DataObjectPastingEventArgs(e.Data, true, DataFormats.UnicodeText);
        //				textArea.RaiseEvent(pastingEventArgs);
        //				if (pastingEventArgs.CommandCancelled)
        //					return;

        //				string text = EditingCommandHandler.GetTextToPaste(pastingEventArgs, textArea);
        //				if (text == null)
        //					return;
        //				bool rectangular = pastingEventArgs.DataObject.GetDataPresent(RectangleSelection.RectangularSelectionDataType);

        //				// Mark the undo group with the currentDragDescriptor, if the drag
        //				// is originating from the same control. This allows combining
        //				// the undo groups when text is moved.
        //				textArea.Document.UndoStack.StartUndoGroup(this.currentDragDescriptor);
        //				try {
        //					if (rectangular && RectangleSelection.PerformRectangularPaste(textArea, textArea.Caret.Position, text, true)) {

        //					} else {
        //						textArea.Document.Insert(start, text);
        //						textArea.Selection = Selection.Create(textArea, start, start + text.Length);
        //					}
        //				} finally {
        //					textArea.Document.UndoStack.EndUndoGroup();
        //				}
        //			}
        //			e.Handled = true;
        //		}
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}

        //void OnDragException(Exception ex)
        //{
        //	// swallows exceptions during drag'n'drop or reports them incorrectly, so
        //	// we re-throw them later to allow the application's unhandled exception handler
        //	// to catch them
        //	textArea.Dispatcher.BeginInvoke(
        //		DispatcherPriority.Send,
        //		new Action(delegate {
        //		           	throw new DragDropException("Exception during drag'n'drop", ex);
        //		           }));
        //}

        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        //{
        //	try {
        //		e.UseDefaultCursors = true;
        //		e.Handled = true;
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}

        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        //void textArea_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        //{
        //	try {
        //		if (e.EscapePressed) {
        //			e.Action = DragAction.Cancel;
        //		} else if ((e.KeyStates & DragDropKeyStates.LeftMouseButton) != DragDropKeyStates.LeftMouseButton) {
        //			e.Action = DragAction.Drop;
        //		} else {
        //			e.Action = DragAction.Continue;
        //		}
        //		e.Handled = true;
        //	} catch (Exception ex) {
        //		OnDragException(ex);
        //	}
        //}
        #endregion

        #region Start Drag
        //object currentDragDescriptor;

        //void StartDrag()
        //{
        //	// prevent nested StartDrag calls
        //	mode = SelectionMode.Drag;

        //	// mouse capture and Drag'n'Drop doesn't mix
        //	textArea.ReleaseMouseCapture();

        //	DataObject dataObject = textArea.Selection.CreateDataObject(textArea);

        //	DragDropEffects allowedEffects = DragDropEffects.All;
        //	var deleteOnMove = textArea.Selection.Segments.Select(s => new AnchorSegment(textArea.Document, s)).ToList();
        //	foreach (ISegment s in deleteOnMove) {
        //		ISegment[] result = textArea.GetDeletableSegments(s);
        //		if (result.Length != 1 || result[0].Offset != s.Offset || result[0].EndOffset != s.EndOffset) {
        //			allowedEffects &= ~DragDropEffects.Move;
        //		}
        //	}

        //	var copyingEventArgs = new DataObjectCopyingEventArgs(dataObject, true);
        //	textArea.RaiseEvent(copyingEventArgs);
        //	if (copyingEventArgs.CommandCancelled)
        //		return;

        //	object dragDescriptor = new object();
        //	this.currentDragDescriptor = dragDescriptor;

        //	DragDropEffects resultEffect;
        //	using (textArea.AllowCaretOutsideSelection()) {
        //		var oldCaretPosition = textArea.Caret.Position;
        //		try {
        //			Debug.WriteLine("DoDragDrop with allowedEffects=" + allowedEffects);
        //			resultEffect = DragDrop.DoDragDrop(textArea, dataObject, allowedEffects);
        //			Debug.WriteLine("DoDragDrop done, resultEffect=" + resultEffect);
        //		} catch (COMException ex) {
        //			// ignore COM errors - don't crash on badly implemented drop targets
        //			Debug.WriteLine("DoDragDrop failed: " + ex.ToString());
        //			return;
        //		}
        //		if (resultEffect == DragDropEffects.None) {
        //			// reset caret if drag was aborted
        //			textArea.Caret.Position = oldCaretPosition;
        //		}
        //	}

        //	this.currentDragDescriptor = null;

        //	if (deleteOnMove != null && resultEffect == DragDropEffects.Move && (allowedEffects & DragDropEffects.Move) == DragDropEffects.Move) {
        //		bool draggedInsideSingleDocument = (dragDescriptor == textArea.Document.UndoStack.LastGroupDescriptor);
        //		if (draggedInsideSingleDocument)
        //			textArea.Document.UndoStack.StartContinuedUndoGroup(null);
        //		textArea.Document.BeginUpdate();
        //		try {
        //			foreach (ISegment s in deleteOnMove) {
        //				textArea.Document.Remove(s.Offset, s.Length);
        //			}
        //		} finally {
        //			textArea.Document.EndUpdate();
        //			if (draggedInsideSingleDocument)
        //				textArea.Document.UndoStack.EndUndoGroup();
        //		}
        //	}
        //}
        #endregion

        #region QueryCursor
        // provide the IBeam Cursor for the text area
        //void textArea_QueryCursor(object sender, QueryCursorEventArgs e)
        //{
        //	if (!e.Handled) {
        //		if (mode != SelectionMode.None) {
        //			// during selection, use IBeam cursor even outside the text area
        //			e.Cursor = Cursors.IBeam;
        //			e.Handled = true;
        //		} else if (textArea.TextView.VisualLinesValid) {
        //			// Only query the cursor if the visual lines are valid.
        //			// If they are invalid, the cursor will get re-queried when the visual lines
        //			// get refreshed.
        //			Point p = e.GetPosition(textArea.TextView);
        //			if (p.X >= 0 && p.Y >= 0 && p.X <= textArea.TextView.ActualWidth && p.Y <= textArea.TextView.ActualHeight) {
        //				int visualColumn;
        //				bool isAtEndOfLine;
        //				int offset = GetOffsetFromMousePosition(e, out visualColumn, out isAtEndOfLine);
        //				if (enableTextDragDrop && textArea.Selection.Contains(offset))
        //					e.Cursor = Cursors.Arrow;
        //				else
        //					e.Cursor = Cursors.IBeam;
        //				e.Handled = true;
        //			}
        //		}
        //	}
        //}
        #endregion

        #region LeftButtonDown

        internal void OnPointerPressed(PointerDownEvent e)
        {
            Vector2 mousePoint = GetTextViewRelativePosition(e);

            if (e.button != 0)
            {
                if (TextArea.RightClickMovesCaret == true)
                {
                    SetCaretOffsetToMousePosition(mousePoint);
                }
            }
            else
            {
                int clickCount = ComputeClickCount(e);
                _mode = SelectionMode.None;
                bool isShift = (e.modifiers & EventModifiers.Shift) != 0;
                bool isAlt = (e.modifiers & EventModifiers.Alt) != 0;
                bool isCtrl = (e.modifiers & EventModifiers.Control) != 0
                           || (e.modifiers & EventModifiers.Command) != 0;

                if (_enableTextDragDrop && clickCount == 1 && !isShift)
                {
                    var offset = GetOffsetFromMousePosition(mousePoint, out _, out _);
                    if (TextArea.Selection.Contains(offset))
                    {
                        _mode = SelectionMode.PossibleDragStart;
                        _possibleDragStartMousePos = mousePoint;
                        return;
                    }
                }

                var oldPosition = TextArea.Caret.Position;
                SetCaretOffsetToMousePosition(mousePoint);

                if (!isShift)
                {
                    TextArea.ClearSelection();
                }

                if (isAlt && TextArea.Options.EnableRectangularSelection)
                {
                    _mode = SelectionMode.Rectangular;
                    if (isShift && TextArea.Selection is RectangleSelection)
                    {
                        TextArea.Selection = TextArea.Selection.StartSelectionOrSetEndpoint(oldPosition, TextArea.Caret.Position);
                    }
                }
                else if (isCtrl && clickCount == 1)
                {
                    _mode = SelectionMode.WholeWord;
                    if (isShift && !(TextArea.Selection is RectangleSelection))
                    {
                        TextArea.Selection = TextArea.Selection.StartSelectionOrSetEndpoint(oldPosition, TextArea.Caret.Position);
                    }
                }
                else if (e.button == 0 && clickCount == 1)
                {
                    _mode = SelectionMode.Normal;
                    if (isShift && !(TextArea.Selection is RectangleSelection))
                    {
                        TextArea.Selection = TextArea.Selection.StartSelectionOrSetEndpoint(oldPosition, TextArea.Caret.Position);
                    }
                }
                else
                {
                    SimpleSegment startWord;
                    if (clickCount == 3)
                    {
                        _mode = SelectionMode.WholeLine;
                        startWord = GetLineAtMousePosition(mousePoint);
                    }
                    else
                    {
                        _mode = SelectionMode.WholeWord;
                        startWord = GetWordAtMousePosition(mousePoint);
                    }

                    if (startWord == SimpleSegment.Invalid)
                    {
                        _mode = SelectionMode.None;
                        return;
                    }
                    if (isShift && !TextArea.Selection.IsEmpty)
                    {
                        if (startWord.Offset < TextArea.Selection.SurroundingSegment.Offset)
                        {
                            TextArea.Selection = TextArea.Selection.SetEndpoint(new TextViewPosition(TextArea.Document.GetLocation(startWord.Offset)));
                        }
                        else if (startWord.EndOffset > TextArea.Selection.SurroundingSegment.EndOffset)
                        {
                            TextArea.Selection = TextArea.Selection.SetEndpoint(new TextViewPosition(TextArea.Document.GetLocation(startWord.EndOffset)));
                        }
                        _startWord = new AnchorSegment(TextArea.Document, TextArea.Selection.SurroundingSegment);
                    }
                    else
                    {
                        TextArea.Selection = Selection.Create(TextArea, startWord.Offset, startWord.EndOffset);
                        _startWord = new AnchorSegment(TextArea.Document, startWord.Offset, startWord.Length);
                    }
                }
            }
        }

        private int ComputeClickCount(PointerDownEvent e)
        {
            float currentTime = Time.realtimeSinceStartup;
            Vector2 currentPosition = e.position;

            if (currentTime - _lastClickTime < TripleClickTimeSeconds
                && Vector2.Distance(currentPosition, _lastClickPosition) < TripleClickMaxDistance)
            {
                _consecutiveClicks++;
            }
            else
            {
                _consecutiveClicks = 1;
            }

            _lastClickTime = currentTime;
            _lastClickPosition = currentPosition;

            return _consecutiveClicks;
        }

        #endregion

        #region LeftButtonClick

        #endregion

        #region LeftButtonDoubleTap

        #endregion

        #region Mouse Position <-> Text coordinates

        private Vector2 GetTextViewRelativePosition(IPointerEvent e)
        {
            return TextArea.TextView.WorldToLocal(e.position);
        }

        private SimpleSegment GetWordAtMousePosition(Vector2 posRelativeToTextView)
        {
            var textView = TextArea.TextView;
            if (textView == null) return SimpleSegment.Invalid;
            var pos = posRelativeToTextView;
            if (pos.y < 0)
                pos.y = 0;
            if (pos.y > textView.Bounds.height)
                pos.y = textView.Bounds.height;
            pos += textView.ScrollOffset;
            var line = textView.GetVisualLineFromVisualTop(pos.y);
            if (line != null && line.TextLines != null)
            {
                var visualColumn = line.GetVisualColumn(pos, TextArea.Selection.EnableVirtualSpace);
                var wordStartVc = line.GetNextCaretPosition(visualColumn + 1, LogicalDirection.Backward, CaretPositioningMode.WordStartOrSymbol, TextArea.Selection.EnableVirtualSpace);
                if (wordStartVc == -1)
                    wordStartVc = 0;
                var wordEndVc = line.GetNextCaretPosition(wordStartVc, LogicalDirection.Forward, CaretPositioningMode.WordBorderOrSymbol, TextArea.Selection.EnableVirtualSpace);
                if (wordEndVc == -1)
                    wordEndVc = line.VisualLength;
                var relOffset = line.FirstDocumentLine.Offset;
                var wordStartOffset = line.GetRelativeOffset(wordStartVc) + relOffset;
                var wordEndOffset = line.GetRelativeOffset(wordEndVc) + relOffset;
                return new SimpleSegment(wordStartOffset, wordEndOffset - wordStartOffset);
            }
            else
            {
                return SimpleSegment.Invalid;
            }
        }

        private SimpleSegment GetLineAtMousePosition(Vector2 posRelativeToTextView)
        {
            var textView = TextArea.TextView;
            if (textView == null) return SimpleSegment.Invalid;
            var pos = posRelativeToTextView;
            if (pos.y < 0)
                pos.y = 0;
            if (pos.y > textView.Bounds.height)
                pos.y = textView.Bounds.height;
            pos += textView.ScrollOffset;
            var line = textView.GetVisualLineFromVisualTop(pos.y);
            return line != null && line.TextLines != null
                ? new SimpleSegment(line.StartOffset, line.LastDocumentLine.EndOffset - line.StartOffset)
                : SimpleSegment.Invalid;
        }

        private int GetOffsetFromMousePosition(Vector2 mousePosition, out int visualColumn, out bool isAtEndOfLine)
        {
            visualColumn = 0;
            var textView = TextArea.TextView;
            var pos = mousePosition;
            if (pos.y < 0)
                pos.y = 0;
            if (pos.y > textView.Bounds.height)
                pos.y = textView.Bounds.height;
            pos += textView.ScrollOffset;
            if (pos.y >= textView.DocumentHeight)
                pos.y = textView.DocumentHeight - ExtensionMethods.Epsilon;
            var line = textView.GetVisualLineFromVisualTop(pos.y);
            if (line != null && line.TextLines != null)
            {
                visualColumn = line.GetVisualColumn(pos, TextArea.Selection.EnableVirtualSpace, out isAtEndOfLine);
                return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
            }
            isAtEndOfLine = false;
            return -1;
        }

        private int GetOffsetFromMousePositionFirstTextLineOnly(Vector2 positionRelativeToTextView, out int visualColumn)
        {
            visualColumn = 0;
            var textView = TextArea.TextView;
            var pos = positionRelativeToTextView;
            if (pos.y < 0)
                pos.y = 0;
            if (pos.y > textView.Bounds.height)
                pos.y = textView.Bounds.height;
            pos += textView.ScrollOffset;
            if (pos.y >= textView.DocumentHeight)
                pos.y = textView.DocumentHeight - ExtensionMethods.Epsilon;
            var line = textView.GetVisualLineFromVisualTop(pos.y);
            if (line != null && line.TextLines != null)
            {
                visualColumn = line.GetVisualColumn(line.TextLines.First(), pos.x, TextArea.Selection.EnableVirtualSpace);
                return line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
            }
            return -1;
        }
        #endregion

        private const int MinimumHorizontalDragDistance = 2;
        private const int MinimumVerticalDragDistance = 2;
        private const float TripleClickTimeSeconds = 0.5f;
        private const float TripleClickMaxDistance = 5f;

        #region MouseMove

        internal void OnPointerMoved(PointerMoveEvent e)
        {
            Vector2 mousePoint = GetTextViewRelativePosition(e);

            if (_mode == SelectionMode.Normal || _mode == SelectionMode.WholeWord || _mode == SelectionMode.WholeLine || _mode == SelectionMode.Rectangular)
            {
                if (TextArea.TextView.VisualLinesValid)
                {
                    ExtendSelectionToMouse(mousePoint);
                }
            }
            else if (_mode == SelectionMode.PossibleDragStart)
            {
                Vector2 mouseMovement = mousePoint - _possibleDragStartMousePos;
                if (Math.Abs(mouseMovement.x) > MinimumHorizontalDragDistance
                    || Math.Abs(mouseMovement.y) > MinimumVerticalDragDistance)
                {
                    // TODO: drag
                    //StartDrag();
                }
            }
        }
        #endregion

        #region ExtendSelection

        private void SetCaretOffsetToMousePosition(Vector2 posRelativeToTextView)
        {
            int visualColumn;
            bool isAtEndOfLine;
            int offset;
            if (_mode == SelectionMode.Rectangular)
            {
                offset = GetOffsetFromMousePositionFirstTextLineOnly(posRelativeToTextView, out visualColumn);
                isAtEndOfLine = true;
            }
            else
            {
                offset = GetOffsetFromMousePosition(posRelativeToTextView, out visualColumn, out isAtEndOfLine);
            }

            if (offset >= 0)
            {
                TextArea.Caret.Position = new TextViewPosition(TextArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
                TextArea.Caret.DesiredXPos = float.NaN;
            }
        }

        private void ExtendSelectionToMouse(Vector2 posRelativeToTextView)
        {
            var oldPosition = TextArea.Caret.Position;
            if (_mode == SelectionMode.Normal || _mode == SelectionMode.Rectangular)
            {
                SetCaretOffsetToMousePosition(posRelativeToTextView);
                if (_mode == SelectionMode.Normal && TextArea.Selection is RectangleSelection)
                    TextArea.Selection = new SimpleSelection(TextArea, oldPosition, TextArea.Caret.Position);
                else if (_mode == SelectionMode.Rectangular && !(TextArea.Selection is RectangleSelection))
                    TextArea.Selection = new RectangleSelection(TextArea, oldPosition, TextArea.Caret.Position);
                else
                    TextArea.Selection = TextArea.Selection.StartSelectionOrSetEndpoint(oldPosition, TextArea.Caret.Position);
            }
            else if (_mode == SelectionMode.WholeWord || _mode == SelectionMode.WholeLine)
            {
                var newWord = (_mode == SelectionMode.WholeLine) ? GetLineAtMousePosition(posRelativeToTextView) : GetWordAtMousePosition(posRelativeToTextView);
                if (newWord != SimpleSegment.Invalid && _startWord != null)
                {
                    TextArea.Selection = Selection.Create(TextArea,
                                                          Math.Min(newWord.Offset, _startWord.Offset),
                                                          Math.Max(newWord.EndOffset, _startWord.EndOffset));
                    TextArea.Caret.Offset = newWord.Offset < _startWord.Offset ? newWord.Offset : Math.Max(newWord.EndOffset, _startWord.EndOffset);
                }
            }
            TextArea.Caret.BringCaretToView(5.0f);
        }
        #endregion

        #region MouseLeftButtonUp

        internal void OnPointerReleased(PointerUpEvent e)
        {
            if (_mode == SelectionMode.None)
                return;
            Vector2 mousePoint = GetTextViewRelativePosition(e);
            switch (_mode)
            {
                case SelectionMode.PossibleDragStart:
                    SetCaretOffsetToMousePosition(mousePoint);
                    TextArea.ClearSelection();
                    break;
                case SelectionMode.Normal:
                case SelectionMode.WholeWord:
                case SelectionMode.WholeLine:
                case SelectionMode.Rectangular:
                    if (TextArea.Options.ExtendSelectionOnMouseUp)
                        ExtendSelectionToMouse(mousePoint);
                    break;
            }
            _mode = SelectionMode.None;
        }
        #endregion
    }
}