/// <summary>
 /// Initialize a new instance of the <see cref="MarginRedrawEventArgs"/> class.
 /// </summary>
 /// <param name="diffLines">Differences between the current document and the version in TFS.</param>
 /// <param name="reason">The reason of redrawing the margin.</param>
 public MarginRedrawEventArgs(DiffLinesCollection diffLines, MarginDrawReason reason)
 {
     _diffLines = diffLines;
     _reason = reason;
 }
        /// <summary>
        /// Draw margins for each diff line.
        /// </summary>
        /// <param name="diffLines">Differences between the current document and the version in TFS.</param>
        private void DrawMargins(DiffLinesCollection diffLines)
        {
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(() => DrawMargins(diffLines));
                return;
            }

            Children.Clear();
            
            foreach (DiffLineEntry diffLine in diffLines)
            {
                double top, bottom;
                try
                {
                    MapLineToPixels(diffLine.Line, out top, out bottom);
                }
                catch (ArgumentException)
                {
                    // The supplied line is on an incorrect snapshot (old version).
                    return;
                }

                var rect = new Rectangle
                {
                    Height = bottom - top,
                    Width = Width - MarginElementOffset,
                    Focusable = false,
                    IsHitTestVisible = false
                };
                SetLeft(rect, MarginElementOffset);
                SetTop(rect, top);

                switch (diffLine.ChangeType)
                {
                    case DiffChangeType.Insert:
                        rect.Fill = _marginCore.MarginSettings.AddedLineMarginBrush;
                        break;
                    case DiffChangeType.Change:
                        rect.Fill = _marginCore.MarginSettings.ModifiedLineMarginBrush;
                        break;
                    case DiffChangeType.Delete:
                        rect.Fill = _marginCore.MarginSettings.RemovedLineMarginBrush;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                Children.Add(rect);
            }
        }
        /// <summary>
        /// Update difference between lines asynchronously, if needed, and redraw the margin.
        /// </summary>
        /// <param name="useCache">Use cached differences.</param>
        /// <param name="reason">The reason of redrawing.</param>
        private void Redraw(bool useCache, MarginDrawReason reason)
        {
            try
            {
                if (_isDisposed || _textView.IsClosed)
                    return;

                if (!_isActivated)
                {
                    _cachedChangedLines.Clear();
                    RaiseMarginRedraw(reason);
                    return;
                }

                if (useCache)
                {
                    RaiseMarginRedraw(reason);
                    return;
                }

                var task = new Task(() =>
                {
                    lock (_drawLockObject)
                    {
                        try
                        {
                            _cachedChangedLines = GetChangedLineNumbers();
                        }
                        catch (Exception ex)
                        {
                            RaiseExceptionThrown(ex);
                            return;
                        }

                        Redraw(true, reason);
                    }
                });

                task.Start();
            }
            catch (Exception ex)
            {
                RaiseExceptionThrown(ex);
            }
        }
        /// <summary>
        /// Get differences between the document's local file and his source control version.
        /// </summary>
        /// <returns>
        /// Collection that contains the result of comparing the document's local file with his source control version.
        /// <para/>Each element is a pair of key and value: the key is a line of text, the value is a type of difference.
        /// </returns>
        private DiffLinesCollection GetChangedLineNumbers()
        {
            Debug.Assert(_textDoc != null, "_textDoc is null.");
            Debug.Assert(_versionControl != null, "_versionControl is null.");

            var textSnapshot = _textView.TextSnapshot;

            Stream sourceStream = new MemoryStream();
            Encoding sourceStreamEncoding = _textDoc.Encoding;
            byte[] textBytes = sourceStreamEncoding.GetBytes(textSnapshot.GetText());
            sourceStream.Write(textBytes, 0, textBytes.Length);

            DiffSummary diffSummary;
            lock (_versionControlItemStreamLockObject)
            {
                diffSummary = GetDifference(
                    _versionControlItemStream,
                    Encoding.GetEncoding(_versionControlItem.Encoding),
                    sourceStream,
                    sourceStreamEncoding);
            }

            var dict = new DiffLinesCollection();
            for (int i = 0; i < diffSummary.Changes.Length; i++)
            {
                var diff = new DiffChange(diffSummary.Changes[i]);

                if (diff.ChangeType == DiffChangeType.Change)
                {
                    if (diff.OriginalLength >= diff.ModifiedLength)
                    {
                        int linesModified = diff.ModifiedLength;
                        if (linesModified == 0)
                        {
                            diff.ChangeType = DiffChangeType.Delete;
                            int linesDeleted = diff.OriginalLength - diff.ModifiedLength;
                            Debug.Assert(linesDeleted > 0, "linesDeleted must be greater than zero.");
                        }
                    }
                    else
                    {
                        int linesModified = diff.OriginalLength;
                        if (linesModified == 0)
                        {
                            diff.ChangeType = DiffChangeType.Insert;
                            int linesAdded = diff.ModifiedLength - diff.OriginalLength;
                            Debug.Assert(linesAdded > 0, "linesAdded must be greater than zero.");
                        }
                    }
                }

                if (diff.ChangeType != DiffChangeType.Delete)
                {
                    for (int k = diff.ModifiedStart; k <= diff.ModifiedEnd; k++)
                        AddLineToDiffLinesCollection(dict, textSnapshot, k, diff);
                }
                else
                {
                    AddLineToDiffLinesCollection(dict, textSnapshot, diff.ModifiedStart, diff);
                }
            }

            return dict;
        }
        /// <summary>
        /// Adds a line of text from an <see cref="ITextSnapshot"/> to <see cref="DiffLinesCollection"/>.
        /// </summary>
        /// <param name="collection">Collection of differences between the current document and the version in TFS.</param>
        /// <param name="textSnapshot">The text snapshot.</param>
        /// <param name="lineNumber">The line number.</param>
        /// <param name="diffChangeInfo">Represents information about a specific difference between two sequences.</param>
        private static void AddLineToDiffLinesCollection(DiffLinesCollection collection, ITextSnapshot textSnapshot, int lineNumber, IDiffChange diffChangeInfo)
        {
            ITextSnapshotLine line;
            try
            {
                line = textSnapshot.GetLineFromLineNumber(lineNumber);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                string msg = string.Format("Line number {0} is out of range [0..{1}].", lineNumber, textSnapshot.LineCount);
                throw new ArgumentOutOfRangeException(msg, ex);
            }

            collection.Add(line, diffChangeInfo);
        }
        /// <summary>
        /// Draw margins for each diff line.
        /// </summary>
        /// <param name="diffLines">Differences between the current document and the version in TFS.</param>
        private void DrawMargins(DiffLinesCollection diffLines)
        {
            if (!Dispatcher.CheckAccess())
            {
                Dispatcher.Invoke(() => DrawMargins(diffLines));
                return;
            }

            Children.Clear();

            foreach (ITextViewLine viewLine in _textView.TextViewLines)
            {
                DiffChangeType diffType;
                ITextSnapshotLine line;
                
                try
                {
                    if (ContainsChanges(viewLine, diffLines[DiffChangeType.Change], out line))
                    {
                        diffType = DiffChangeType.Change;
                    }
                    else if (ContainsChanges(viewLine, diffLines[DiffChangeType.Insert], out line))
                    {
                        diffType = DiffChangeType.Insert;

                        ITextSnapshotLine tmp;
                        if (ContainsChanges(viewLine, diffLines[DiffChangeType.Delete], out tmp))
                        {
                            diffType = DiffChangeType.Change;
                            line = tmp;
                        }
                    }
                    else if (ContainsChanges(viewLine, diffLines[DiffChangeType.Delete], out line))
                    {
                        diffType = DiffChangeType.Delete;
                    }
                    else
                    {
                        continue;
                    }
                }
                catch (ArgumentException)
                {
                    // The supplied line is on an incorrect snapshot (old version).
                    return;
                }

                IDiffChange diffChangeInfo = diffLines[line];

                var rect = new Rectangle
                {
                    Height = viewLine.Height, 
                    Width = MarginElementWidth,
                    Cursor = Cursors.Hand,
                    ContextMenu = _contextMenu,
                    Tag = new { DiffChangeInfo = diffChangeInfo, Line = line }
                };
                SetLeft(rect, MarginElementLeft);
                SetTop(rect, viewLine.Top - _textView.ViewportTop);

                switch (diffType)
                {
                    case DiffChangeType.Insert:
                        rect.Fill = _marginCore.MarginSettings.AddedLineMarginBrush;
                        break;
                    case DiffChangeType.Change:
                        rect.Fill = _marginCore.MarginSettings.ModifiedLineMarginBrush;
                        break;
                    case DiffChangeType.Delete:
                        rect.Fill = _marginCore.MarginSettings.RemovedLineMarginBrush;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

                if (diffType != DiffChangeType.Insert)
                    rect.MouseEnter += OnMarginElementMouseEnter;

                rect.MouseLeftButtonDown += OnMarginElementMouseLeftButtonDown;
                rect.MouseLeftButtonUp += OnMarginElementMouseLeftButtonUp;

                Children.Add(rect);
            }
        }