﻿// 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.Collections.Generic;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Commanding;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Interactive.Commands
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.Interactive)]
    public class InteractiveCommandHandlerTests
    {
        private const string Caret = "$$";
        private const string ExampleCode1 = @"var x = 1;";
        private const string ExampleCode2 =
@"var x = 1;
Task.Run(() => { return 1; });";
        private const string ExampleCode2Line2 =
@"Task.Run(() => { return 1; });";
        private const string ExampleMultiline =
@"namespace N {
    void goo() {
        Console.WriteLine(
            $$""LLL"");
    }
}";
        private const string ExpectedMultilineSelection =
@"Console.WriteLine(
            ""LLL"");";

        [WpfFact]
        public void TestExecuteInInteractiveWithoutSelection()
        {
            AssertExecuteInInteractive(Caret, new string[0]);

            AssertExecuteInInteractive(
@"var x = 1;
$$
var y = 2;", new string[0]);

            AssertExecuteInInteractive(ExampleCode1 + Caret, ExampleCode1);
            AssertExecuteInInteractive(ExampleCode1.Insert(3, Caret), ExampleCode1);
            AssertExecuteInInteractive(ExampleCode2 + Caret, ExampleCode2Line2);
            AssertExecuteInInteractive(ExampleMultiline, ExpectedMultilineSelection);
        }

        [WpfFact]
        public void TestExecuteInInteractiveWithEmptyBuffer()
        {
            AssertExecuteInInteractive(
@"{|Selection:|}var x = 1;
{|Selection:$$|}var y = 2;", new string[0]);

            AssertExecuteInInteractive($@"{{|Selection:{ExampleCode1}$$|}}", ExampleCode1);

            AssertExecuteInInteractive($@"{{|Selection:{ExampleCode2}$$|}}", ExampleCode2);

            AssertExecuteInInteractive(
$@"var o = new object[] {{ 1, 2, 3 }};
Console.WriteLine(o);
{{|Selection:{ExampleCode2}$$|}}

Console.WriteLine(x);", ExampleCode2);
        }

        [WpfFact]
        public void TestExecuteInInteractiveWithBoxSelection()
        {
            var expectedBoxSubmissionResult = @"int x;
int y;";
            AssertExecuteInInteractive(
$@"some text {{|Selection:$$int x;|}} also here
text some {{|Selection:int y;|}} here also", expectedBoxSubmissionResult);

            AssertExecuteInInteractive(
$@"some text {{|Selection:int x;$$|}} also here
text some {{|Selection:int y;|}} here also", expectedBoxSubmissionResult);

            AssertExecuteInInteractive(
$@"some text {{|Selection:int x;|}} also here
text some {{|Selection:$$int y;|}} here also", expectedBoxSubmissionResult);

            AssertExecuteInInteractive(
$@"some text {{|Selection:int x;|}} also here
text some {{|Selection:int y;$$|}} here also", expectedBoxSubmissionResult);
        }

        [WpfFact]
        public void TestExecuteInInteractiveWithNonEmptyBuffer()
        {
            // Execute in interactive clears the existing current buffer before execution.
            // Therefore `var x = 1;` will not be executed.
            AssertExecuteInInteractive(
                @"{|Selection:var y = 2;$$|}",
                "var y = 2;",
                submissionBuffer: "var x = 1;");
        }

        [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/23200")]
        public void TestExecuteInInteractiveWithDefines()
        {
            var exampleWithIfDirective =
@"#if DEF
public void $$Run()
{
}
#endif";

            AssertExecuteInInteractive(exampleWithIfDirective,
@"public void Run()");

            var exampleWithDefine =
$@"#define DEF
{exampleWithIfDirective}";

            AssertExecuteInInteractive(exampleWithDefine,
@"public void Run()
{
}");
        }

        [WpfFact]
        public void TestCopyToInteractiveWithoutSelection()
        {
            AssertCopyToInteractive(Caret, "");

            AssertCopyToInteractive($"{ExampleCode2}$$", ExampleCode2Line2);

            AssertCopyToInteractive(
                code: ExampleCode2 + Caret,
                submissionBuffer: ExampleCode1,
                expectedBufferText: ExampleCode1 + "\r\n" + ExampleCode2Line2);

            AssertCopyToInteractive(
                code: ExampleCode2 + Caret,
                submissionBuffer: "x = 2;",
                expectedBufferText: "x = 2;\r\n" + ExampleCode2Line2);
        }

        [WpfFact]
        public void TestCopyToInteractive()
        {
            AssertCopyToInteractive($"{{|Selection:{ExampleCode2}$$|}}", ExampleCode2);
        }

        [WpfFact]
        public void TestCopyToInteractiveWithNonEmptyBuffer()
        {
            // Copy to interactive does not clear the existing buffer.
            // Therefore `var x = 1;` will still be present in the final buffer.
            AssertCopyToInteractive(
                $"{{|Selection:{ExampleCode2}$$|}}",
                $"var x = 1;\r\n{ExampleCode2}",
                submissionBuffer: "var x = 1;");
        }

        private static void AssertCopyToInteractive(string code, string expectedBufferText, string submissionBuffer = null)
        {
            using var workspace = InteractiveWindowCommandHandlerTestState.CreateTestState(code);
            PrepareSubmissionBuffer(submissionBuffer, workspace);
            workspace.SendCopyToInteractive();
            Assert.Equal(expectedBufferText, workspace.WindowCurrentLanguageBuffer.CurrentSnapshot.GetText());
        }

        private static void AssertExecuteInInteractive(string code, string expectedSubmission, string submissionBuffer = null)
        {
            AssertExecuteInInteractive(code, new string[] { expectedSubmission }, submissionBuffer);
        }

        private static void AssertExecuteInInteractive(string code, string[] expectedSubmissions, string submissionBuffer = null)
        {
            var submissions = new List<string>();
            void appendSubmission(object _, string item)
            {
                submissions.Add(item.TrimEnd());
            }

            using var workspace = InteractiveWindowCommandHandlerTestState.CreateTestState(code);
            PrepareSubmissionBuffer(submissionBuffer, workspace);
            Assert.Equal(CommandState.Available, workspace.GetStateForExecuteInInteractive());

            workspace.Evaluator.OnExecute += appendSubmission;
            workspace.ExecuteInInteractive();
            AssertEx.Equal(expectedSubmissions, submissions);
        }

        private static void PrepareSubmissionBuffer(string submissionBuffer, InteractiveWindowCommandHandlerTestState workspace)
        {
            if (!string.IsNullOrEmpty(submissionBuffer))
            {
                workspace.WindowCurrentLanguageBuffer.Insert(0, submissionBuffer);
            }
        }
    }
}
