﻿// 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.Linq;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
using TextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.Venus
{
    internal partial class ContainedLanguage : IVsContainedLanguageCodeSupport
    {
        public int CreateUniqueEventName(string pszClassName, string pszObjectName, string pszNameOfEvent, out string pbstrEventHandlerName)
        {
            string result = null;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c =>
                    result = ContainedLanguageCodeSupport.CreateUniqueEventName(GetThisDocument(), GlobalOptions, pszClassName, pszObjectName, pszNameOfEvent, c.UserCancellationToken));

            pbstrEventHandlerName = result;
            return VSConstants.S_OK;
        }

        public int EnsureEventHandler(
            string pszClassName,
            string pszObjectTypeName,
            string pszNameOfEvent,
            string pszEventHandlerName,
            uint itemidInsertionPoint,
            out string pbstrUniqueMemberID,
            out string pbstrEventBody,
            TextSpan[] pSpanInsertionPoint)
        {
            var thisDocument = GetThisDocument();
            var targetDocumentId = this.ContainedDocument.FindProjectDocumentIdWithItemId(itemidInsertionPoint);
            var targetDocument = thisDocument.Project.Solution.GetDocument(targetDocumentId);
            if (targetDocument == null)
            {
                // Can't generate into this itemid
                pbstrUniqueMemberID = null;
                pbstrEventBody = null;
                return VSConstants.E_FAIL;
            }

            Tuple<string, string, TextSpan> idBodyAndInsertionPoint = null;
            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c => idBodyAndInsertionPoint = ContainedLanguageCodeSupport.EnsureEventHandler(
                    thisDocument,
                    targetDocument,
                    pszClassName,
                    null /*objectName*/,
                    pszObjectTypeName,
                    pszNameOfEvent,
                    pszEventHandlerName,
                    itemidInsertionPoint,
                    useHandlesClause: false,
                    additionalFormattingRule: targetDocument.Project.Services.GetService<IAdditionalFormattingRuleLanguageService>().GetAdditionalCodeGenerationRule(),
                    GlobalOptions,
                    cancellationToken: c.UserCancellationToken));

            pbstrUniqueMemberID = idBodyAndInsertionPoint.Item1;
            pbstrEventBody = idBodyAndInsertionPoint.Item2;
            pSpanInsertionPoint[0] = idBodyAndInsertionPoint.Item3;
            return VSConstants.S_OK;
        }

        public int GetBaseClassName(string pszClassName, out string pbstrBaseClassName)
        {
            var result = false;
            string baseClassName = null;
            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c => result = ContainedLanguageCodeSupport.TryGetBaseClassName(GetThisDocument(), pszClassName, c.UserCancellationToken, out baseClassName));

            pbstrBaseClassName = baseClassName;
            return result ? VSConstants.S_OK : VSConstants.E_FAIL;
        }

        public int GetCompatibleEventHandlers(
            string pszClassName,
            string pszObjectTypeName,
            string pszNameOfEvent,
            out int pcMembers,
            IntPtr ppbstrEventHandlerNames,
            IntPtr ppbstrMemberIDs)
        {
            IEnumerable<Tuple<string, string>> membersAndIds = null;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c => membersAndIds = ContainedLanguageCodeSupport.GetCompatibleEventHandlers(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, c.UserCancellationToken));

            pcMembers = membersAndIds.Count();
            CreateBSTRArray(ppbstrEventHandlerNames, membersAndIds.Select(t => t.Item1));
            CreateBSTRArray(ppbstrMemberIDs, membersAndIds.Select(t => t.Item2));

            return VSConstants.S_OK;
        }

        public int GetEventHandlerMemberID(string pszClassName, string pszObjectTypeName, string pszNameOfEvent, string pszEventHandlerName, out string pbstrUniqueMemberID)
        {
            string memberId = null;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c => memberId = ContainedLanguageCodeSupport.GetEventHandlerMemberId(GetThisDocument(), pszClassName, pszObjectTypeName, pszNameOfEvent, pszEventHandlerName, c.UserCancellationToken));

            pbstrUniqueMemberID = memberId;
            return pbstrUniqueMemberID == null ? VSConstants.S_FALSE : VSConstants.S_OK;
        }

        public int GetMemberNavigationPoint(string pszClassName, string pszUniqueMemberID, TextSpan[] pSpanNavPoint, out uint pItemID)
        {
            uint itemId = 0;
            TextSpan textSpan = default;
            var succeeded = false;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c =>
                {
                    if (ContainedLanguageCodeSupport.TryGetMemberNavigationPoint(GetThisDocument(), GlobalOptions, pszClassName, pszUniqueMemberID, out textSpan, out var targetDocument, c.UserCancellationToken))
                    {
                        succeeded = true;
                        itemId = this.ContainedDocument.FindItemIdOfDocument(targetDocument);
                    }
                });

            pItemID = itemId;
            pSpanNavPoint[0] = textSpan;
            return succeeded ? VSConstants.S_OK : VSConstants.E_FAIL;
        }

        public int GetMembers(string pszClassName, uint dwFlags, out int pcMembers, IntPtr ppbstrDisplayNames, IntPtr ppbstrMemberIDs)
        {
            IEnumerable<Tuple<string, string>> membersAndIds = null;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c => membersAndIds = ContainedLanguageCodeSupport.GetMembers(GetThisDocument(), pszClassName, (CODEMEMBERTYPE)dwFlags, c.UserCancellationToken));

            pcMembers = membersAndIds.Count();
            CreateBSTRArray(ppbstrDisplayNames, membersAndIds.Select(t => t.Item1));
            CreateBSTRArray(ppbstrMemberIDs, membersAndIds.Select(t => t.Item2));

            return VSConstants.S_OK;
        }

        public int IsValidID(string bstrID, out bool pfIsValidID)
        {
            pfIsValidID = ContainedLanguageCodeSupport.IsValidId(GetThisDocument(), bstrID);
            return VSConstants.S_OK;
        }

        public int OnRenamed(ContainedLanguageRenameType clrt, string bstrOldID, string bstrNewID)
        {
            var result = 0;

            var uiThreadOperationExecutor = ComponentModel.GetService<IUIThreadOperationExecutor>();
            uiThreadOperationExecutor.Execute(
                "Intellisense",
                defaultDescription: "",
                allowCancellation: false,
                showProgress: false,
                action: c =>
                    {
                        var refactorNotifyServices = this.ComponentModel.DefaultExportProvider.GetExportedValues<IRefactorNotifyService>();

                        if (!ContainedLanguageCodeSupport.TryRenameElement(GetThisDocument(), clrt, bstrOldID, bstrNewID, refactorNotifyServices, c.UserCancellationToken))
                        {
                            result = s_CONTAINEDLANGUAGE_CANNOTFINDITEM;
                        }
                        else
                        {
                            result = VSConstants.S_OK;
                        }
                    });

            return result;
        }

        protected Document GetThisDocument()
        {
            var document = this.ContainedDocument.GetOpenTextContainer().CurrentText.GetOpenDocumentInCurrentContextWithChanges();
            if (document == null)
            {
                throw new InvalidOperationException();
            }

            return document;
        }

        private static readonly int s_CONTAINEDLANGUAGE_CANNOTFINDITEM = MakeHResult(1, FACILITY_ITF, 0x8003);

        private const int FACILITY_ITF = 4;
        private static int MakeHResult(uint sev, uint facility, uint code)
            => unchecked((int)((sev << 31) | (facility << 16) | code));

        protected static void CreateBSTRArray(IntPtr dest, IEnumerable<string> source)
        {
            if (dest != IntPtr.Zero)
            {
                var current = Marshal.AllocCoTaskMem(source.Count() * IntPtr.Size);
                Marshal.WriteIntPtr(dest, current);
                foreach (var s in source)
                {
                    Marshal.WriteIntPtr(current, Marshal.StringToBSTR(s));
                    current = IntPtr.Add(current, IntPtr.Size);
                }
            }
        }
    }
}
