﻿// 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.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio;
using Roslyn.Test.Utilities;
using Roslyn.VisualStudio.IntegrationTests;
using Roslyn.VisualStudio.IntegrationTests.InProcess;
using WindowsInput.Native;
using Xunit;

namespace Roslyn.VisualStudio.NewIntegrationTests.CSharp
{
    [Trait(Traits.Feature, Traits.Features.Formatting)]
    public class CSharpFormatting : AbstractEditorTest
    {
        protected override string LanguageName => LanguageNames.CSharp;

        public CSharpFormatting()
            : base(nameof(CSharpFormatting))
        {
        }

        [IdeFact]
        public async Task AlignOpenBraceWithMethodDeclaration()
        {
            await using (var telemetry = await TestServices.Telemetry.EnableTestTelemetryChannelAsync(HangMitigatingCancellationToken))
            {
                await SetUpEditorAsync(@"
$$class C
{
    void Main()
     {
    }
}", HangMitigatingCancellationToken);

                await TestServices.Editor.FormatDocumentAsync(HangMitigatingCancellationToken);
                await TestServices.EditorVerifier.TextContainsAsync(@"
class C
{
    void Main()
    {
    }
}", cancellationToken: HangMitigatingCancellationToken);
                await telemetry.VerifyFiredAsync(new[] { "vs/ide/vbcs/commandhandler/formatcommand" }, HangMitigatingCancellationToken);
            }
        }

        [IdeFact]
        public async Task FormatOnSemicolon()
        {
            await SetUpEditorAsync(@"
public class C
{
    void Goo()
    {
        var x =        from a             in       new List<int>()
    where x % 2 = 0
                      select x   ;$$
    }
}", HangMitigatingCancellationToken);

            await TestServices.Input.SendAsync(new InputKey[] { VirtualKeyCode.BACK, ';' }, HangMitigatingCancellationToken);
            await TestServices.EditorVerifier.TextContainsAsync(@"
public class C
{
    void Goo()
    {
        var x = from a in new List<int>()
                where x % 2 = 0
                select x;
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        public async Task FormatSelection()
        {
            await SetUpEditorAsync(@"
public class C {
    public void M( ) {$$
        }
}", HangMitigatingCancellationToken);

            await TestServices.Editor.SelectTextInCurrentDocumentAsync("public void M( ) {", HangMitigatingCancellationToken);
            await TestServices.Editor.FormatSelectionAsync(HangMitigatingCancellationToken);
            await TestServices.EditorVerifier.TextContainsAsync(@"
public class C {
    public void M()
    {
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        public async Task PasteCodeWithLambdaBody()
        {
            await SetUpEditorAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                $$
            }
        };
    }
}", HangMitigatingCancellationToken);
            await TestServices.Editor.PasteAsync(@"        Action b = () =>
        {

            };", HangMitigatingCancellationToken);

            await TestServices.EditorVerifier.TextContainsAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                Action b = () =>
                {

                };
            }
        };
    }
}", cancellationToken: HangMitigatingCancellationToken);
            // Undo should only undo the formatting
            await TestServices.Shell.ExecuteCommandAsync(VSConstants.VSStd97CmdID.Undo, HangMitigatingCancellationToken);
            await TestServices.EditorVerifier.TextContainsAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                        Action b = () =>
        {

            };
            }
        };
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        public async Task PasteCodeWithLambdaBody2()
        {
            await SetUpEditorAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                $$
            }
        };
    }
}", HangMitigatingCancellationToken);
            await TestServices.Editor.PasteAsync(@"        Action<int> b = n =>
        {
            Console.Writeline(n);
        };", HangMitigatingCancellationToken);

            await TestServices.EditorVerifier.TextContainsAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                Action<int> b = n =>
                {
                    Console.Writeline(n);
                };
            }
        };
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        public async Task PasteCodeWithLambdaBody3()
        {
            await SetUpEditorAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                $$
            }
        };
    }
}", HangMitigatingCancellationToken);
            await TestServices.Editor.PasteAsync(@"        D d = delegate(int x)
{
    return 2 * x;
};", HangMitigatingCancellationToken);

            await TestServices.EditorVerifier.TextContainsAsync(@"
using System;
class Program
{
    static void Main()
    {
        Action a = () =>
        {
            using (null)
            {
                D d = delegate (int x)
                {
                    return 2 * x;
                };
            }
        };
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        public async Task ShiftEnterWithIntelliSenseAndBraceMatching()
        {
            await SetUpEditorAsync(@"
class Program
{
    object M(object bar)
    {
        return M$$
    }
}", HangMitigatingCancellationToken);
            await TestServices.Workspace.WaitForAsyncOperationsAsync(FeatureAttribute.Workspace, HangMitigatingCancellationToken);
            await TestServices.Input.SendAsync(new InputKey[] { "(ba", (VirtualKeyCode.RETURN, VirtualKeyCode.SHIFT), "// comment" }, HangMitigatingCancellationToken);
            await TestServices.EditorVerifier.TextContainsAsync(@"
class Program
{
    object M(object bar)
    {
        return M(bar);
        // comment
    }
}", cancellationToken: HangMitigatingCancellationToken);
        }

        [IdeFact]
        [Trait(Traits.Feature, Traits.Features.EditorConfig)]
        [WorkItem("https://github.com/dotnet/roslyn/issues/15003")]
        public async Task ApplyEditorConfigAndFormatDocument()
        {
            var markup = @"
class C
{
    public int X1
    {
        get
        {
            $$return 3;
        }
    }
}";
            var expectedTextTwoSpaceIndent = @"
class C
{
  public int X1
  {
    get
    {
      return 3;
    }
  }
}";

            await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class1.cs", HangMitigatingCancellationToken);

            MarkupTestFile.GetSpans(markup, out var expectedTextFourSpaceIndent, out ImmutableArray<TextSpan> _);
            await SetUpEditorAsync(markup, HangMitigatingCancellationToken);

            /*
             * The first portion of this test verifies that Format Document uses the default indentation settings when
             * no .editorconfig is available.
             */

            await TestServices.Workspace.WaitForAllAsyncOperationsAsync(
                new[]
                {
                    FeatureAttribute.Workspace,
                    FeatureAttribute.SolutionCrawlerLegacy,
                    FeatureAttribute.DiagnosticService,
                    FeatureAttribute.ErrorSquiggles
                },
                HangMitigatingCancellationToken);
            await TestServices.Editor.FormatDocumentAsync(HangMitigatingCancellationToken);

            Assert.Equal(expectedTextFourSpaceIndent, await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken));

            /*
             * The second portion of this test adds a .editorconfig file to configure the indentation behavior, and
             * verifies that the next Format Document operation adheres to the formatting.
             */

            var editorConfig = @"root = true

[*.cs]
indent_size = 2
";

            await TestServices.SolutionExplorer.AddFileAsync(ProjectName, ".editorconfig", editorConfig, open: false, HangMitigatingCancellationToken);

            await TestServices.Workspace.WaitForAllAsyncOperationsAsync(
                new[]
                {
                    FeatureAttribute.Workspace,
                    FeatureAttribute.SolutionCrawlerLegacy,
                    FeatureAttribute.DiagnosticService,
                    FeatureAttribute.ErrorSquiggles
                },
                HangMitigatingCancellationToken);
            await TestServices.Editor.FormatDocumentAsync(HangMitigatingCancellationToken);

            Assert.Equal(expectedTextTwoSpaceIndent, await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken));

            /*
             * The third portion of this test modifies the existing .editorconfig file with a new indentation behavior,
             * and verifies that the next Format Document operation adheres to the updated formatting.
             */

            await TestServices.SolutionExplorer.SetFileContentsAsync(ProjectName, ".editorconfig", editorConfig.Replace("2", "4"), HangMitigatingCancellationToken);

            await TestServices.Workspace.WaitForAllAsyncOperationsAsync(
                new[]
                {
                    FeatureAttribute.Workspace,
                    FeatureAttribute.SolutionCrawlerLegacy,
                    FeatureAttribute.DiagnosticService,
                    FeatureAttribute.ErrorSquiggles
                },
                HangMitigatingCancellationToken);
            await TestServices.Editor.FormatDocumentAsync(HangMitigatingCancellationToken);

            Assert.Equal(expectedTextFourSpaceIndent, await TestServices.Editor.GetTextAsync(HangMitigatingCancellationToken));
        }
    }
}
