﻿// 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.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.Editor.CSharp.RawStringLiteral;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio.Commanding;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RawStringLiteral
{
    [UseExportProvider]
    public class RawStringLiteralCommandHandlerTests
    {
        internal sealed class RawStringLiteralTestState : AbstractCommandHandlerTestState
        {
            private static readonly TestComposition s_composition = EditorTestCompositions.EditorFeaturesWpf.AddParts(
                typeof(RawStringLiteralCommandHandler));

            private readonly RawStringLiteralCommandHandler _commandHandler;

            public RawStringLiteralTestState(XElement workspaceElement)
                : base(workspaceElement, s_composition)
            {
                _commandHandler = (RawStringLiteralCommandHandler)GetExportedValues<ICommandHandler>().
                    Single(c => c is RawStringLiteralCommandHandler);
            }

            public static RawStringLiteralTestState CreateTestState(string markup, bool withSpansOnly = false)
                => new(GetWorkspaceXml(markup, withSpansOnly));

            public static XElement GetWorkspaceXml(string markup, bool withSpansOnly)
            {
                var spansOnlyMarkup = withSpansOnly ? """Markup="SpansOnly" """ : "";
                return XElement.Parse($"""
<Workspace>
    <Project Language="C#" CommonReferences="true">
        <Document {spansOnlyMarkup}>{markup}</Document>
    </Project>
</Workspace>
""");
            }

            internal void AssertCodeIs(string expectedCode, bool withSpansOnly = false)
            {
                if (withSpansOnly)
                    expectedCode = expectedCode.Replace("$", "\uD7FF");

                MarkupTestFile.GetPositionAndSpans(expectedCode, out var massaged, out int? caretPosition, out var spans);

                if (withSpansOnly)
                {
                    Assert.Null(caretPosition);
                    massaged = massaged.Replace("\uD7FF", "$");
                }

                Assert.Equal(massaged, TextView.TextSnapshot.GetText());

                if (!withSpansOnly)
                    Assert.Equal(caretPosition!.Value, TextView.Caret.Position.BufferPosition.Position);

                var virtualSpaces = spans.SingleOrDefault(kvp => kvp.Key.StartsWith("VirtualSpaces#"));
                if (virtualSpaces.Key != null)
                {
                    var virtualOffset = int.Parse(virtualSpaces.Key["VirtualSpaces-".Length..]);
                    Assert.True(TextView.Caret.InVirtualSpace);
                    Assert.Equal(virtualOffset, TextView.Caret.Position.VirtualBufferPosition.VirtualSpaces);
                }
            }

            public void SendTypeChar(char ch)
                => SendTypeChar(ch, _commandHandler.ExecuteCommand, () => EditorOperations.InsertText(ch.ToString()));

            public void SendReturn(bool handled)
                => SendReturn(_commandHandler.ExecuteCommand, () =>
                {
                    Assert.False(handled, "Return key should have been handled");
                });
        }

        #region enter tests

        [WpfFact]
        public void TestReturnInSixQuotes()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$""""""");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = """"""
$${|VirtualSpaces-4:|}
    """"""");
        }

        [WpfFact]
        public void TestReturnInSixQuotesWithSemicolonAfter()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$"""""";");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = """"""
$${|VirtualSpaces-4:|}
    """""";");
        }

        [WpfFact]
        public void TestReturnInSixQuotesNotAtMiddle()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""""$$""""");

            testState.SendReturn(handled: false);
            testState.AssertCodeIs(
@"var v = """"""""$$""""");
        }

        [WpfFact]
        public void TestReturnInSixQuotes_Interpolated()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""""$$""""""");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = $""""""
$${|VirtualSpaces-4:|}
    """"""");
        }

        [WpfFact]
        public void TestReturnInSixQuotesMoreQuotesLaterOn()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$"""""";
Console.WriteLine(""Goo"");");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = """"""
$${|VirtualSpaces-4:|}
    """""";
Console.WriteLine(""Goo"");");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument1()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(""""""$$""""""");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(""""""
$${|VirtualSpaces-4:|}
    """"""");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument2()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(""""""$$"""""")");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(""""""
$${|VirtualSpaces-4:|}
    """""")");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument3()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(""""""$$"""""");");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(""""""
$${|VirtualSpaces-4:|}
    """""");");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument4()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(
    """"""$$""""""");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(
    """"""
$${|VirtualSpaces-4:|}
    """"""");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument5()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(
    """"""$$"""""")");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(
    """"""
$${|VirtualSpaces-4:|}
    """""")");
        }

        [WpfFact]
        public void TestReturnInSixQuotesAsArgument6()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = WriteLine(
    """"""$$"""""");");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = WriteLine(
    """"""
$${|VirtualSpaces-4:|}
    """""");");
        }

        [WpfFact]
        public void TestReturnInSixQuotesWithSemicolonAfter_Interpolated()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""""$$"""""";");

            testState.SendReturn(handled: true);
            testState.AssertCodeIs(
@"var v = $""""""
$${|VirtualSpaces-4:|}
    """""";");
        }

        [WpfFact]
        public void TestReturnInSixQuotesNotAtMiddle_Interpolated()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""""""$$""""");

            testState.SendReturn(handled: false);
            testState.AssertCodeIs(
@"var v = $""""""""$$""""");
        }

        [WpfFact]
        public void TestReturnEndOfFile()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$");

            testState.SendReturn(handled: false);
        }

        [WpfFact]
        public void TestReturnInEmptyFile()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"$$");

            testState.SendReturn(handled: false);
        }

        #endregion

        #region generate initial empty raw string

        [WpfFact]
        public void TestGenerateAtEndOfFile()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""$$""""""");
        }

        [WpfFact]
        public void TestGenerateWithSemicolonAfter()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"$$;");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""$$"""""";");
        }

        [WpfFact]
        public void TestGenerateWithInterpolatedString()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = $""""""$$""""""");
        }

        [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/66538")]
        public void TestGenerateWithInterpolatedString_TwoDollarSigns()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
"""var v = $$""[||]""", withSpansOnly: true);

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
""""
var v = $$"""[||]"""
"""", withSpansOnly: true);
        }

        [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/66538")]
        public void TestGenerateWithInterpolatedString_TwoDollarSigns_FourthDoubleQuote()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
""""var v = $$"""[||]""";"""", withSpansOnly: true);

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
"""""var v = $$""""[||]"""";""""", withSpansOnly: true);
        }

        [WpfFact, WorkItem("https://github.com/dotnet/roslyn/issues/66538")]
        public void TestGenerateWithInterpolatedString_ThreeDollarSigns()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
"""var v = $$$""[||]""", withSpansOnly: true);

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
""""
var v = $$$"""[||]"""
"""", withSpansOnly: true);
        }

        [WpfFact]
        public void TestNoGenerateWithVerbatimString()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = @""""$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = @""""""$$");
        }

        [WpfFact]
        public void TestNoGenerateWithVerbatimInterpolatedString1()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = @$""""$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = @$""""""$$");
        }

        [WpfFact]
        public void TestNoGenerateWithVerbatimInterpolatedString2()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $@""""$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = $@""""""$$");
        }

        #endregion

        #region grow empty raw string

        [WpfFact]
        public void TestDoNotGrowEmptyInsideSimpleString()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = ""$$""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"$$""");
        }

        [WpfFact]
        public void TestDoNotGrowEmptyInsideFourQuotes()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"$$""""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""$$""""");
        }

        [WpfFact]
        public void TestDoGrowEmptyInsideSixQuotesInMiddle()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$""""""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""""$$""""""""");
        }

        [WpfFact]
        public void TestDoGrowEmptyInsideSixQuotesInInterpolatedRaw()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""""$$""""""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = $""""""""$$""""""""");
        }

        [WpfFact]
        public void TestDoNotGrowEmptyInsideSixQuotesWhenNotInMiddle1()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""$$""""""""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = $""""""$$""""""""");
        }

        #endregion

        #region grow delimiters

        [WpfFact]
        public void TestGrowDelimetersWhenEndExists_SingleLine()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$ """"""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""""$$ """"""""");
        }

        [WpfFact]
        public void TestGrowDelimetersWhenEndExists_MultiLine()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$

    """"""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""""$$

    """"""""");
        }

        [WpfFact]
        public void TestGrowDelimetersWhenEndExists_Interpolated()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = $""""""$$

    """"""");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = $""""""""$$

    """"""""");
        }

        [WpfFact]
        public void TestDoNotGrowDelimetersWhenEndNotThere()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""""$$");
        }

        [WpfFact]
        public void TestDoNotGrowDelimetersWhenEndTooShort()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"var v = """"""$$

    """"");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"var v = """"""""$$

    """"");
        }

        #endregion

        [WpfFact]
        public void TestTypeQuoteEmptyFile()
        {
            using var testState = RawStringLiteralTestState.CreateTestState(
@"$$");

            testState.SendTypeChar('"');
            testState.AssertCodeIs(
@"""$$");
        }
    }
}
