﻿// 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.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.DebuggerIntelliSense
{
    internal partial class DebuggerTextView
    {
        // HACK HACK HACK HACK HACK: We'll use this fake ICompletionSession to trick them into
        // routing commands to us for both completion and sighelp
        private readonly HACK_CompletionSession _hackCompletionSession = new();

        public void HACK_StartCompletionSession(IIntellisenseSession editorSessionOpt)
        {
            HACK_SetShimCompletionSession();
            editorSessionOpt.Dismissed += CompletionOrSignatureHelpSession_Dismissed;
        }

        private void HACK_SetShimCompletionSession()
        {
            // We could choose to use reflection to add a session only when there isn't one set, but
            // this way we'll re-set the field if they accidentally null it out somehow.
            HACK_SetShimCompletionSessionWorker(_hackCompletionSession);

            _hackCompletionSession.Count++;
        }

        private void HACK_RemoveShimCompletionSession()
        {
            _hackCompletionSession.Count--;
            if (_hackCompletionSession.Count == 0)
            {
                HACK_SetShimCompletionSessionWorker(null);
            }
        }

        private void HACK_SetShimCompletionSessionWorker(ICompletionSession completionSession)
        {
            var propertyList = _innerTextView.Properties.PropertyList;
            var shimController = propertyList.Single(x => x.Value != null && x.Value.GetType().Name == "ShimCompletionController").Value;
            var shimControllerType = shimController.GetType();
            var sessionFieldInfo = shimControllerType.GetField("_session", BindingFlags.NonPublic | BindingFlags.Instance);
            sessionFieldInfo.SetValue(shimController, completionSession);
        }

        private void CompletionOrSignatureHelpSession_Dismissed(object sender, EventArgs e)
            => HACK_RemoveShimCompletionSession();

        /// <remarks>
        /// Dev11's debugger intellisense uses the old completion shims and routes commands through
        /// them. Since we use the new editor completion and sighelp brokers for our sessions, the shims
        /// are unaware of any sessions and don't pass us any commands other than typechar. To determine
        /// whether to pass commands or non, the shims simply verify that they have a pointer to an
        /// ICompletionSession. We will use reflection to place an ICompletionSession in the field.
        /// 
        /// Furthermore, Dev11's debugger intellisense does not pass commands on to SignatureHelp at
        /// all. It's therefore impossible to use the arrow keys to navigate overloads, etc. If we give
        /// the CompletionSessionShim an ICompletionSession, though, we still get the commands and our
        /// command handlers can deal with them appropriately. To get commands when only our
        /// SignatureHelp is up, we still must provide an ICompletionSession, which this class provides. 
        /// Note: Any calls to methods in this class will throw, since the completion shims should not
        /// be doing anything.
        /// 
        /// We also include a counter so that we can null out the field when all of our sessions have
        /// actually ended.
        /// 
        /// See CEditCtlStatementCompletion::HandleKeyDown for more information
        /// </remarks>
        internal class HACK_CompletionSession : ICompletionSession
        {
            public int Count = 0;

            public void Commit()
                => throw new NotImplementedException();

            // We've got a bunch of unused events, so disable the unused event warning.
#pragma warning disable 67
            public event EventHandler Committed;

            public ReadOnlyObservableCollection<CompletionSet> CompletionSets
            {
                get { throw new NotImplementedException(); }
            }

            public void Filter()
                => throw new NotImplementedException();

            public bool IsStarted
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            public CompletionSet SelectedCompletionSet
            {
                // To prevent them trying to commit, we need to pretend there's nothing actually
                // selected.
                get { return null; }
                set { throw new NotImplementedException(); }
            }

            public event EventHandler<ValueChangedEventArgs<CompletionSet>> SelectedCompletionSetChanged;

            public void Collapse()
                => throw new NotImplementedException();

            public void Dismiss()
            {
                return;
            }

            public event EventHandler Dismissed;

            public SnapshotPoint? GetTriggerPoint(ITextSnapshot textSnapshot)
                => throw new NotImplementedException();

            public ITrackingPoint GetTriggerPoint(ITextBuffer textBuffer)
                => throw new NotImplementedException();

            // The shim controller actually does check IsDismissed immediately after checking for a
            // session, so this implementation can't throw. 
            public bool IsDismissed
            {
                get { return false; }
            }

            public bool Match()
                => throw new NotImplementedException();

            public IIntellisensePresenter Presenter
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

            public event EventHandler PresenterChanged;

            public void Recalculate()
                => throw new NotImplementedException();

            public event EventHandler Recalculated;

            public void Start()
                => throw new NotImplementedException();

            public ITextView TextView
            {
                get { throw new NotImplementedException(); }
            }

            public PropertyCollection Properties
            {
                get { throw new NotImplementedException(); }
            }
        }
    }
}
