﻿// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.ChangeSignature;
using Microsoft.CodeAnalysis.Editor.CSharp.ChangeSignature;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Editor.UnitTests.ChangeSignature;
using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities.ChangeSignature;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ChangeSignature
{
    public partial class ChangeSignatureTests : AbstractChangeSignatureTests
    {
        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        public async Task RemoveParameters1()
        {
            var markup = @"
static class Ext
{
    /// <summary>
    /// This is a summary of <see cref=""M(object, int, string, bool, int, string, int[])""/>
    /// </summary>
    /// <param name=""o""></param>
    /// <param name=""a""></param>
    /// <param name=""b""></param>
    /// <param name=""c""></param>
    /// <param name=""x""></param>
    /// <param name=""y""></param>
    /// <param name=""p""></param>
    static void $$M(this object o, int a, string b, bool c, int x = 0, string y = ""Zero"", params int[] p)
    {
        object t = new object();

        M(t, 1, ""two"", true, 3, ""four"", new[] { 5, 6 });
        M(t, 1, ""two"", true, 3, ""four"", 5, 6);
        t.M(1, ""two"", true, 3, ""four"", new[] { 5, 6 });
        t.M(1, ""two"", true, 3, ""four"", 5, 6);

        M(t, 1, ""two"", true, 3, ""four"");
        M(t, 1, ""two"", true, 3);
        M(t, 1, ""two"", true);

        M(t, 1, ""two"", c: true);
        M(t, 1, ""two"", true, 3, y: ""four"");

        M(t, 1, ""two"", true, 3, p: new[] { 5 });
        M(t, 1, ""two"", true, p: new[] { 5 });
        M(t, 1, ""two"", true, y: ""four"");
        M(t, 1, ""two"", true, x: 3);

        M(t, 1, ""two"", true, y: ""four"", x: 3);
        M(t, 1, y: ""four"", x: 3, b: ""two"", c: true);
        M(t, y: ""four"", x: 3, c: true, b: ""two"", a: 1);
        M(t, p: new[] { 5 }, y: ""four"", x: 3, c: true, b: ""two"", a: 1);
        M(p: new[] { 5 }, y: ""four"", x: 3, c: true, b: ""two"", a: 1, o: t);
    }
}";
            var updatedSignature = new[] { 0, 2, 5 };
            var updatedCode = @"
static class Ext
{
    /// <summary>
    /// This is a summary of <see cref=""M(object, string, string)""/>
    /// </summary>
    /// <param name=""o""></param>
    /// <param name=""b""></param>
    /// <param name=""y""></param>
    /// 
    /// 
    /// 
    /// 
    static void M(this object o, string b, string y = ""Zero"")
    {
        object t = new object();

        M(t, ""two"", ""four"");
        M(t, ""two"", ""four"");
        t.M(""two"", ""four"");
        t.M(""two"", ""four"");

        M(t, ""two"", ""four"");
        M(t, ""two"");
        M(t, ""two"");

        M(t, ""two"");
        M(t, ""two"", y: ""four"");

        M(t, ""two"");
        M(t, ""two"");
        M(t, ""two"", y: ""four"");
        M(t, ""two"");

        M(t, ""two"", y: ""four"");
        M(t, y: ""four"", b: ""two"");
        M(t, y: ""four"", b: ""two"");
        M(t, y: ""four"", b: ""two"");
        M(y: ""four"", b: ""two"", o: t);
    }
}";

            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode);
        }

        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        public async Task RemoveParameters_GenericParameterType()
        {
            var markup = @"
class DA
{
    void M(params int[] i) { }

    void B()
    {
        DP20<int>.D d = new DP20<int>.D(M);

        /*DA19*/$$d(0);
        d();
        d(0, 1);
    }
}
public class DP20<T>
{
    public delegate void /*DP20*/D(params T[] t);
    public event D E1;
    public event D E2;

    public void M1(params T[] t) { }
    public void M2(params T[] t) { }
    public void M3(params T[] t) { }

    void B()
    {
        D d = new D(M1);
        E1 += new D(M2);
        E2 -= new D(M3);
    }
}";
            var updatedSignature = Array.Empty<int>();
            var updatedCode = @"
class DA
{
    void M() { }

    void B()
    {
        DP20<int>.D d = new DP20<int>.D(M);

        /*DA19*/d();
        d();
        d();
    }
}
public class DP20<T>
{
    public delegate void /*DP20*/D();
    public event D E1;
    public event D E2;

    public void M1() { }
    public void M2() { }
    public void M3() { }

    void B()
    {
        D d = new D(M1);
        E1 += new D(M2);
        E2 -= new D(M3);
    }
}";

            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode);
        }

        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1102830")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/784")]
        public async Task RemoveParameters_ExtensionMethodInAnotherFile()
        {
            var workspaceXml = @"
<Workspace>
    <Project Language=""C#"" AssemblyName=""CSharpAssembly"" CommonReferences=""true"">";

            for (var i = 0; i <= 4; i++)
            {
                workspaceXml += $@"
<Document FilePath = ""C{i}.cs"">
class C{i}
{{
    void M()
    {{
        C5 c = new C5();
        c.Ext(1, ""two"");
    }}
}}
</Document>";
            }

            workspaceXml += @"
<Document FilePath = ""C5.cs"">
public class C5
{
}

public static class C5Ext
{
    public void $$Ext(this C5 c, int i, string s)
    {
    }
}
</Document>";

            for (var i = 6; i <= 9; i++)
            {
                workspaceXml += $@"
<Document FilePath = ""C{i}.cs"">
class C{i}
{{
    void M()
    {{
        C5 c = new C5();
        c.Ext(1, ""two"");
    }}
}}
</Document>";
            }

            workspaceXml += @"
    </Project>
</Workspace>";

            var updatedSignature = new[] {
                new AddedParameterOrExistingIndex(0),
                new AddedParameterOrExistingIndex(2) };

            using var testState = ChangeSignatureTestState.Create(XElement.Parse(workspaceXml));
            testState.TestChangeSignatureOptionsService.UpdatedSignature = updatedSignature;
            var result = await testState.ChangeSignatureAsync().ConfigureAwait(false);

            Assert.True(result.Succeeded);
            Assert.Null(result.ChangeSignatureFailureKind);

            foreach (var updatedDocument in testState.Workspace.Documents.Select(d => result.UpdatedSolution.GetDocument(d.Id)))
            {
                if (updatedDocument.Name == "C5.cs")
                {
                    Assert.Contains("void Ext(this C5 c, string s)", (await updatedDocument.GetTextAsync(CancellationToken.None)).ToString());
                }
                else
                {
                    Assert.Contains(@"c.Ext(""two"");", (await updatedDocument.GetTextAsync(CancellationToken.None)).ToString());
                }
            }
        }

        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        [WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1102830")]
        [WorkItem("https://github.com/dotnet/roslyn/issues/784")]
        public async Task AddRemoveParameters_ExtensionMethodInAnotherFile()
        {
            var workspaceXml = @"
<Workspace>
    <Project Language=""C#"" AssemblyName=""CSharpAssembly"" CommonReferences=""true"">";

            for (var i = 0; i <= 4; i++)
            {
                workspaceXml += $@"
<Document FilePath = ""C{i}.cs"">
class C{i}
{{
    void M()
    {{
        C5 c = new C5();
        c.Ext(1, ""two"");
    }}
}}
</Document>";
            }

            workspaceXml += @"
<Document FilePath = ""C5.cs"">
public class C5
{
}

public static class C5Ext
{
    public void $$Ext(this C5 c, int i, string s)
    {
    }
}
</Document>";

            for (var i = 6; i <= 9; i++)
            {
                workspaceXml += $@"
<Document FilePath = ""C{i}.cs"">
class C{i}
{{
    void M()
    {{
        C5 c = new C5();
        c.Ext(1, ""two"");
    }}
}}
</Document>";
            }

            workspaceXml += @"
    </Project>
</Workspace>";

            var updatedSignature = new[] {
                new AddedParameterOrExistingIndex(0),
                new AddedParameterOrExistingIndex(2),
                new AddedParameterOrExistingIndex(new AddedParameter(null, "int", "newIntegerParameter", CallSiteKind.Value, callSiteValue:"123"), "int") };

            using var testState = ChangeSignatureTestState.Create(XElement.Parse(workspaceXml));
            testState.TestChangeSignatureOptionsService.UpdatedSignature = updatedSignature;
            var result = await testState.ChangeSignatureAsync().ConfigureAwait(false);

            Assert.True(result.Succeeded);
            Assert.Null(result.ChangeSignatureFailureKind);

            foreach (var updatedDocument in testState.Workspace.Documents.Select(d => result.UpdatedSolution.GetDocument(d.Id)))
            {
                if (updatedDocument.Name == "C5.cs")
                {
                    Assert.Contains("void Ext(this C5 c, string s, int newIntegerParameter)", (await updatedDocument.GetTextAsync(CancellationToken.None)).ToString());
                }
                else
                {
                    Assert.Contains(@"c.Ext(""two"", 123);", (await updatedDocument.GetTextAsync(CancellationToken.None)).ToString());
                }
            }
        }

        [WpfFact]
        [Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        [Trait(Traits.Feature, Traits.Features.Interactive)]
        public void ChangeSignatureCommandDisabledInSubmission()
        {
            using var workspace = TestWorkspace.Create(XElement.Parse(@"
                <Workspace>
                    <Submission Language=""C#"" CommonReferences=""true"">  
                        class C
                        {
                            void M$$(int x)
                            {
                            }
                        }
                    </Submission>
                </Workspace> "),
                workspaceKind: WorkspaceKind.Interactive,
                composition: EditorTestCompositions.EditorFeaturesWpf);
            // Force initialization.
            workspace.GetOpenDocumentIds().Select(id => workspace.GetTestDocument(id).GetTextView()).ToList();

            var textView = workspace.Documents.Single().GetTextView();

            var handler = workspace.ExportProvider.GetCommandHandler<CSharpChangeSignatureCommandHandler>(PredefinedCommandHandlerNames.ChangeSignature, ContentTypeNames.CSharpContentType);

            var state = handler.GetCommandState(new RemoveParametersCommandArgs(textView, textView.TextBuffer));
            Assert.True(state.IsUnspecified);

            state = handler.GetCommandState(new ReorderParametersCommandArgs(textView, textView.TextBuffer));
            Assert.True(state.IsUnspecified);
        }

        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        [WorkItem("https://github.com/dotnet/roslyn/issues/44126")]
        public async Task RemoveParameters_ImplicitObjectCreation()
        {
            var markup = @"
public class C
{
    public $$C(int a, string b) { }

    void M()
    {
        C c = new(1, ""b"");
    }
}";
            var updatedSignature = new[] { 1 };
            var updatedCode = @"
public class C
{
    public C(string b) { }

    void M()
    {
        C c = new(""b"");
    }
}";

            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode);
        }

        [Fact, Trait(Traits.Feature, Traits.Features.ChangeSignature)]
        [WorkItem("https://github.com/dotnet/roslyn/issues/66547")]
        public async Task RemoveParameters_SpecialSymbolNamedParameter()
        {
            var markup = @"
void $$m(object? param, bool @new = true)
{
}

m(null, @new: false);";

            var updatedSignature = new[] { 1 };
            var updatedCode = @"
void m(bool @new = true)
{
}

m(@new: false);";

            await TestChangeSignatureViaCommandAsync(LanguageNames.CSharp, markup, updatedSignature: updatedSignature, expectedUpdatedInvocationDocumentCode: updatedCode);
        }
    }
}
