﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Windows;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense
{
    internal partial class DebuggerTextView : IWpfTextView, IDebuggerTextView2, ITextView2
    {
        /// <summary>
        /// The actual debugger view of the watch or immediate window that we're wrapping
        /// </summary>
        private readonly IWpfTextView _innerTextView;
        private readonly IVsTextLines _debuggerTextLinesOpt;

        private IMultiSelectionBroker _multiSelectionBroker;

        // This name "CompletionRoot" is specified on the Editor side.
        // Roslyn must match the name.
        // The const should be removed with resolution of https://github.com/dotnet/roslyn/issues/31189
        public const string CompletionRoot = nameof(CompletionRoot);

        public DebuggerTextView(
            IWpfTextView innerTextView,
            IBufferGraph bufferGraph,
            IVsTextLines debuggerTextLinesOpt,
            bool isImmediateWindow)
        {
            _innerTextView = innerTextView;
            _debuggerTextLinesOpt = debuggerTextLinesOpt;
            BufferGraph = bufferGraph;
            IsImmediateWindow = isImmediateWindow;

            // The editor requires the current top buffer.
            // TODO it seems to be a hack. It should be removed.
            // Here is an issue to track: https://github.com/dotnet/roslyn/issues/31189
            // Starting debugging for the second time, we already have the property set.
            _innerTextView.Properties[CompletionRoot] = bufferGraph.TopBuffer;
        }

        /// <summary>
        /// We basically replace the innerTextView's BufferGraph with our own custom projection graph
        /// that projects the immediate window contents into a context buffer:
        /// 
        ///             (1)
        ///         (2)     (5)
        ///         (3)     (6)
        ///         (4)
        /// (1) Top level projection buffer - the subject buffer used by intellisense
        /// (2/3) Currently a double projection buffer combo that elides away the ? in the immediate window, and may add some 
        ///       boilerplate code to force an expression context.
        /// (4) innerTextView.TextBuffer, what the user actually sees in the watch/immediate windows
        /// (5) A read-only projection of (6)
        /// (6) The context buffer which is typically a source file
        /// 
        /// </summary>
        public IBufferGraph BufferGraph
        {
            get;
        }

        public bool IsImmediateWindow
        {
            get;
        }

        public uint StartBufferUpdate()
        {
            // null in unit tests
            if (_debuggerTextLinesOpt == null)
            {
                return 0;
            }

            _debuggerTextLinesOpt.GetStateFlags(out var bufferFlags);
            _debuggerTextLinesOpt.SetStateFlags((uint)((BUFFERSTATEFLAGS)bufferFlags & ~BUFFERSTATEFLAGS.BSF_USER_READONLY));
            return bufferFlags;
        }

        public void EndBufferUpdate(uint cookie)
        {
            // null in unit tests
            _debuggerTextLinesOpt?.SetStateFlags(cookie);
        }

        public ITextCaret Caret
        {
            get { return _innerTextView.Caret; }
        }

        public bool HasAggregateFocus
        {
            get { return _innerTextView.HasAggregateFocus; }
        }

        public bool InLayout
        {
            get { return _innerTextView.InLayout; }
        }

        public bool IsClosed
        {
            get { return _innerTextView.IsClosed; }
        }

        public bool IsMouseOverViewOrAdornments
        {
            get { return _innerTextView.IsMouseOverViewOrAdornments; }
        }

        public double LineHeight
        {
            get { return _innerTextView.LineHeight; }
        }

        public double MaxTextRightCoordinate
        {
            get { return _innerTextView.MaxTextRightCoordinate; }
        }

        public IEditorOptions Options
        {
            get { return _innerTextView.Options; }
        }

        public PropertyCollection Properties
        {
            get { return _innerTextView.Properties; }
        }

        public ITrackingSpan ProvisionalTextHighlight
        {
            get
            {
                return _innerTextView.ProvisionalTextHighlight;
            }

            set
            {
                _innerTextView.ProvisionalTextHighlight = value;
            }
        }

        public ITextViewRoleSet Roles
        {
            get { return _innerTextView.Roles; }
        }

        public ITextSelection Selection
        {
            get { return _innerTextView.Selection; }
        }

        public ITextViewLineCollection TextViewLines
        {
            get { return _innerTextView.TextViewLines; }
        }

        public ITextViewModel TextViewModel
        {
            get { return _innerTextView.TextViewModel; }
        }

        public IViewScroller ViewScroller
        {
            get { return _innerTextView.ViewScroller; }
        }

        public double ViewportBottom
        {
            get { return _innerTextView.ViewportBottom; }
        }

        public double ViewportHeight
        {
            get { return _innerTextView.ViewportHeight; }
        }

        public double ViewportLeft
        {
            get
            {
                return _innerTextView.ViewportLeft;
            }

            set
            {
                _innerTextView.ViewportLeft = value;
            }
        }

        public double ViewportRight
        {
            get { return _innerTextView.ViewportRight; }
        }

        public double ViewportTop
        {
            get { return _innerTextView.ViewportTop; }
        }

        public double ViewportWidth
        {
            get { return _innerTextView.ViewportWidth; }
        }

        public ITextSnapshot VisualSnapshot
        {
            get { return _innerTextView.VisualSnapshot; }
        }

        public ITextDataModel TextDataModel
        {
            get { return _innerTextView.TextDataModel; }
        }

        public ITextBuffer TextBuffer
        {
            get
            {
                return _innerTextView.TextBuffer;
            }
        }

        public ITextSnapshot TextSnapshot
        {
            get
            {
                return _innerTextView.TextSnapshot;
            }
        }

        public FrameworkElement VisualElement
        {
            get
            {
                return _innerTextView.VisualElement;
            }
        }

        public Brush Background
        {
            get
            {
                return _innerTextView.Background;
            }

            set
            {
                _innerTextView.Background = value;
            }
        }

        IWpfTextViewLineCollection IWpfTextView.TextViewLines
        {
            get
            {
                return _innerTextView.TextViewLines;
            }
        }

        public IFormattedLineSource FormattedLineSource
        {
            get
            {
                return _innerTextView.FormattedLineSource;
            }
        }

        public ILineTransformSource LineTransformSource
        {
            get
            {
                return _innerTextView.LineTransformSource;
            }
        }

        public double ZoomLevel
        {
            get
            {
                return _innerTextView.ZoomLevel;
            }

            set
            {
                _innerTextView.ZoomLevel = value;
            }
        }

        public bool InOuterLayout => throw new NotImplementedException();

        public IMultiSelectionBroker MultiSelectionBroker
        {
            get
            {
                _multiSelectionBroker ??= _innerTextView.GetMultiSelectionBroker();

                return _multiSelectionBroker;
            }
        }

        public void Close()
            => throw new NotSupportedException();

        public void DisplayTextLineContainingBufferPosition(SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo, double? viewportWidthOverride, double? viewportHeightOverride)
            => throw new NotSupportedException();

        public void DisplayTextLineContainingBufferPosition(SnapshotPoint bufferPosition, double verticalDistance, ViewRelativePosition relativeTo)
            => throw new NotSupportedException();

        public SnapshotSpan GetTextElementSpan(SnapshotPoint point)
            => throw new NotSupportedException();

        public ITextViewLine GetTextViewLineContainingBufferPosition(SnapshotPoint bufferPosition)
            => _innerTextView.GetTextViewLineContainingBufferPosition(bufferPosition);

        public void QueueSpaceReservationStackRefresh()
            => throw new NotSupportedException();

        public IAdornmentLayer GetAdornmentLayer(string name)
            => _innerTextView.GetAdornmentLayer(name);

        public ISpaceReservationManager GetSpaceReservationManager(string name)
            => _innerTextView.GetSpaceReservationManager(name);

        IWpfTextViewLine IWpfTextView.GetTextViewLineContainingBufferPosition(SnapshotPoint bufferPosition)
            => _innerTextView.GetTextViewLineContainingBufferPosition(bufferPosition);

        public void Cleanup()
        {
            // The innerTextView of the immediate window never closes, but we want
            // our completion subscribers to unsubscribe from events when this
            // DebuggerTextView is no longer in use.
            if (this.IsImmediateWindow)
            {
                this.ClosedInternal?.Invoke(this, EventArgs.Empty);
            }

            _innerTextView.Properties.RemoveProperty(CompletionRoot);
        }

        public void QueuePostLayoutAction(Action action) => _innerTextView.QueuePostLayoutAction(action);

        public bool TryGetTextViewLines(out ITextViewLineCollection textViewLines) => _innerTextView.TryGetTextViewLines(out textViewLines);

        public bool TryGetTextViewLineContainingBufferPosition(SnapshotPoint bufferPosition, out ITextViewLine textViewLine)
            => _innerTextView.TryGetTextViewLineContainingBufferPosition(bufferPosition, out textViewLine);

        private event EventHandler ClosedInternal;

#pragma warning disable 67
        public event EventHandler MaxTextRightCoordinateChanged;
#pragma warning restore 67

        public event EventHandler Closed
        {
            add
            {
                if (this.IsImmediateWindow)
                {
                    this.ClosedInternal += value;
                }
                else
                {
                    _innerTextView.Closed += value;
                }
            }

            remove
            {
                if (this.IsImmediateWindow)
                {
                    this.ClosedInternal -= value;
                }
                else
                {
                    _innerTextView.Closed -= value;
                }
            }
        }

        public event EventHandler GotAggregateFocus
        {
            add { _innerTextView.GotAggregateFocus += value; }
            remove { _innerTextView.GotAggregateFocus -= value; }
        }

        public event EventHandler<TextViewLayoutChangedEventArgs> LayoutChanged
        {
            add { _innerTextView.LayoutChanged += value; }
            remove { _innerTextView.LayoutChanged -= value; }
        }

        public event EventHandler LostAggregateFocus
        {
            add { _innerTextView.LostAggregateFocus += value; }
            remove { _innerTextView.LostAggregateFocus -= value; }
        }

        public event EventHandler<MouseHoverEventArgs> MouseHover
        {
            add { _innerTextView.MouseHover += value; }
            remove { _innerTextView.MouseHover -= value; }
        }

        public event EventHandler ViewportHeightChanged
        {
            add { _innerTextView.ViewportHeightChanged += value; }
            remove { _innerTextView.ViewportHeightChanged -= value; }
        }

        public event EventHandler ViewportLeftChanged
        {
            add { _innerTextView.ViewportLeftChanged += value; }
            remove { _innerTextView.ViewportLeftChanged -= value; }
        }

        public event EventHandler ViewportWidthChanged
        {
            add { _innerTextView.ViewportWidthChanged += value; }
            remove { _innerTextView.ViewportWidthChanged -= value; }
        }

        public event EventHandler<BackgroundBrushChangedEventArgs> BackgroundBrushChanged
        {
            add { _innerTextView.BackgroundBrushChanged += value; }
            remove { _innerTextView.BackgroundBrushChanged -= value; }
        }

        public event EventHandler<ZoomLevelChangedEventArgs> ZoomLevelChanged
        {
            add { _innerTextView.ZoomLevelChanged += value; }
            remove { _innerTextView.ZoomLevelChanged -= value; }
        }
    }
}
