﻿// 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.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.InheritanceMargin;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.Extensibility.Testing;
using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin;
using Microsoft.VisualStudio.LanguageServices.Implementation.InheritanceMargin.MarginGlyph;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using Xunit;

namespace Roslyn.VisualStudio.NewIntegrationTests.InProcess
{
    [TestService]
    internal partial class InheritanceMarginInProcess
    {
        private const string MarginName = nameof(InheritanceMarginViewMargin);

        public async Task EnableOptionsAsync(string languageName, CancellationToken cancellationToken)
        {
            var optionService = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
            var showInheritanceMargin = optionService.GetOption(InheritanceMarginOptionsStorage.ShowInheritanceMargin, languageName);
            var combinedWithIndicatorMargin = optionService.GetOption(InheritanceMarginOptionsStorage.InheritanceMarginCombinedWithIndicatorMargin);
            var showGlobalUsings = optionService.GetOption(InheritanceMarginOptionsStorage.InheritanceMarginIncludeGlobalImports, languageName);

            if (showInheritanceMargin != true)
            {
                optionService.SetGlobalOption(InheritanceMarginOptionsStorage.ShowInheritanceMargin, languageName, true);
            }

            if (!showGlobalUsings)
            {
                optionService.SetGlobalOption(InheritanceMarginOptionsStorage.InheritanceMarginIncludeGlobalImports, languageName, true);
            }

            if (combinedWithIndicatorMargin)
            {
                // Glyphs in Indicator margin are owned by editor, and we don't know when the glyphs would be added/removed.
                optionService.SetGlobalOption(InheritanceMarginOptionsStorage.InheritanceMarginCombinedWithIndicatorMargin, false);
            }
        }

        public async Task DisableOptionsAsync(string languageName, CancellationToken cancellationToken)
        {
            var optionService = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
            var showInheritanceMargin = optionService.GetOption(InheritanceMarginOptionsStorage.ShowInheritanceMargin, languageName);
            var showGlobalUsings = optionService.GetOption(InheritanceMarginOptionsStorage.InheritanceMarginIncludeGlobalImports, languageName);

            if (showInheritanceMargin != false)
            {
                optionService.SetGlobalOption(InheritanceMarginOptionsStorage.ShowInheritanceMargin, languageName, false);
            }

            if (showGlobalUsings)
            {
                optionService.SetGlobalOption(InheritanceMarginOptionsStorage.InheritanceMarginIncludeGlobalImports, languageName, false);
            }
        }

        private async Task EnsureGlyphsAppearAsync(Func<CancellationToken, Task> makeChangeFunc, int expectedGlyphsNumberInMargin, CancellationToken cancellationToken)
        {
            var margin = await GetTextViewMarginAsync(cancellationToken);
            var marginCanvas = (Canvas)margin.VisualElement;
            var taskCompletionSource = new TaskCompletionSource<bool>();
            using var _ = cancellationToken.Register(() => taskCompletionSource.TrySetCanceled());

            try
            {
                marginCanvas.LayoutUpdated += OnGlyphChanged;
                await makeChangeFunc(cancellationToken);
                await taskCompletionSource.Task;
            }
            finally
            {
                marginCanvas.LayoutUpdated -= OnGlyphChanged;
            }

            void OnGlyphChanged(object sender, EventArgs e)
            {
                if (marginCanvas.Children.Count == expectedGlyphsNumberInMargin)
                {
                    taskCompletionSource.TrySetResult(true);
                }
            }
        }

        public Task EnableOptionsAndEnsureGlyphsAppearAsync(string languageName, int expectedGlyphsNumberInMargin, CancellationToken cancellationToken)
            => EnsureGlyphsAppearAsync(cts => EnableOptionsAsync(languageName, cts), expectedGlyphsNumberInMargin, cancellationToken);

        public Task SetTextAndEnsureGlyphsAppearAsync(string text, int expectedGlyphsNumberInMargin, CancellationToken cancellationToken)
            => EnsureGlyphsAppearAsync(cts => TestServices.Editor.SetTextAsync(text, cts), expectedGlyphsNumberInMargin, cancellationToken);

        public async Task ClickTheGlyphOnLine(int lineNumber, CancellationToken cancellationToken)
        {
            await WaitForApplicationIdleAsync(cancellationToken);
            var glyph = await GetTheGlyphOnLineAsync(lineNumber, cancellationToken);

            var point = await GetCenterOfGlyphOnScreenAsync(glyph, cancellationToken);
            await TestServices.Input.MoveMouseAsync(point, cancellationToken);
            await TestServices.Input.SendWithoutActivateAsync(
                simulator => simulator.Mouse.LeftButtonClick(), cancellationToken);
        }

        public async Task<InheritanceMarginGlyph> GetTheGlyphOnLineAsync(int lineNumber, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            var activeView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);
            var wpfTextViewLine = activeView.TextViewLines[lineNumber - 1];
            var midOfTheLine = wpfTextViewLine.TextTop + wpfTextViewLine.Height / 2;
            var margin = await GetTextViewMarginAsync(cancellationToken);
            var containingCanvas = (Canvas)margin.VisualElement;

            var glyphsOnLine = new List<InheritanceMarginGlyph>();
            foreach (var glyph in containingCanvas.Children)
            {
                if (glyph is InheritanceMarginGlyph inheritanceMarginGlyph)
                {
                    var glyphTop = Canvas.GetTop(inheritanceMarginGlyph);
                    var glyphBottom = glyphTop + inheritanceMarginGlyph.ActualHeight;
                    if (midOfTheLine > glyphTop && midOfTheLine < glyphBottom)
                    {
                        glyphsOnLine.Add(inheritanceMarginGlyph);
                    }
                }
            }

            if (glyphsOnLine.Count != 1)
            {
                Assert.False(true, $"{glyphsOnLine.Count} glyphs are found at line: {lineNumber}.");
            }

            return glyphsOnLine[0];
        }

        private async Task<IWpfTextViewMargin> GetTextViewMarginAsync(CancellationToken cancellationToken)
        {
            await WaitForApplicationIdleAsync(cancellationToken);
            var vsTextManager = await GetRequiredGlobalServiceAsync<SVsTextManager, IVsTextManager>(cancellationToken);
            var vsTextView = await vsTextManager.GetActiveViewAsync(JoinableTaskFactory, cancellationToken);
            var testViewHost = await vsTextView.GetTextViewHostAsync(JoinableTaskFactory, cancellationToken);
            return testViewHost.GetTextViewMargin(MarginName);
        }

        private async Task<Point> GetCenterOfGlyphOnScreenAsync(FrameworkElement glyph, CancellationToken cancellationToken)
        {
            await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
            var center = new Point(glyph.ActualWidth / 2, glyph.ActualHeight / 2);
            return glyph.PointToScreen(center);
        }
    }
}
