﻿// 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.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.StringIndentation;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.StringIndentation
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.StringIndentation)]
    public class StringIndentationTests
    {
        private static async Task TestAsync(string contents)
        {
            using var workspace = TestWorkspace.CreateWorkspace(
                TestWorkspace.CreateWorkspaceElement(LanguageNames.CSharp,
                    files: new[] { contents.Replace("|", " ") },
                    isMarkup: false));
            var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id);
            var root = await document.GetRequiredSyntaxRootAsync(default);

            var service = document.GetRequiredLanguageService<IStringIndentationService>();
            var regions = await service.GetStringIndentationRegionsAsync(document, root.FullSpan, CancellationToken.None).ConfigureAwait(false);

            var actual = ApplyRegions(contents.Replace("|", " "), regions);
            Assert.Equal(contents, actual);
        }

        private static string ApplyRegions(string val, ImmutableArray<StringIndentationRegion> regions)
        {
            var text = SourceText.From(val);
            using var _ = ArrayBuilder<TextChange>.GetInstance(out var changes);

            foreach (var region in regions)
            {
                var firstLine = text.Lines.GetLineFromPosition(region.IndentSpan.Start);
                var lastLine = text.Lines.GetLineFromPosition(region.IndentSpan.End);
                var offset = region.IndentSpan.End - lastLine.Start;

                for (var i = firstLine.LineNumber + 1; i < lastLine.LineNumber; i++)
                {
                    var lineStart = text.Lines[i].Start;
                    if (region.OrderedHoleSpans.Any(s => s.Contains(lineStart)))
                        continue;

                    changes.Add(new TextChange(new TextSpan(lineStart + offset - 1, 1), "|"));
                }
            }

            var changedText = text.WithChanges(changes);
            return changedText.ToString();
        }

        [Fact]
        public async Task TestEmptyFile()
            => await TestAsync(string.Empty);

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestLiteralError1(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        // not enough lines in literal
        var v = """"""
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestLiteralError2(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        // invalid literal
        var v = """"""
            text too early
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestZeroColumn1(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
goo
""""""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestZeroColumn2(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
    goo
""""""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestOneColumn1(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
|goo
 """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestOneColumn2(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
|   goo
 """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase1(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase2(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |bar
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase3(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |bar
               |baz
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase4(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
               |goo
               |
               |baz
                """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase5(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v = """"""
           |    goo
           |
           |    baz
            """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase6(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""
           |goo
            """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase7(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""
            |goo
             """"""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase8(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v =
            $""""""""
            |goo
             """"""""{suffix};
    }}
}}");
        }

        [Theory]
        [InlineData("")]
        [InlineData("u8")]
        public async Task TestCase9(string suffix)
        {
            await TestAsync($@"class C
{{
    void M()
    {{
        var v =
             """"""""
            |goo
             """"""""{suffix};
    }}
}}");
        }

        [Fact]
        public async Task TestCase10()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v =
             $$""""""""
            |goo
             """""""";
    }
}");
        }

        [Fact]
        public async Task TestCase11()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v =
            $$""""""""
            |goo
             """""""";
    }
}");
        }

        [Fact]
        public async Task TestCase12()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v =
           $$""""""""
            |goo
             """""""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles1()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo
           |    { 1 + 1 }
           |    baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles2()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo{
           |    1 + 1
           |    }baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles3()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo{
           |1 + 1
           |    }baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles4()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo{
           1 + 1
                }baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles5()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo{
           |1 + 1
           |    }baz
           |    quux
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles6()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    goo{
         1 + 1
                }baz
           |    quux
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles7()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |goo{
         1 + 1
         }baz
           |quux
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles8()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    { 1 + 1 }
           |    baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles9()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    {
           |        1 + 1
           |    }
           |    baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithHoles10()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var v = $""""""
           |    {
        1 + 1
                }
           |    baz
            """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles1()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
            |   $""""""
            |   |bar
            |    """"""
            |}
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles2()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
            |   $""""""
            |   |bar
            |   |{
            |   |   1 + 1
            |   |}
            |    """"""
            |}
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles3()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
            |   $""""""
            |   |bar
            |   |{
            |   1 + 1
            |    }
            |    """"""
            |}
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles4()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
                $""""""
                |bar
                |{
            1 + 1
                 }
                 """"""
             }
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles5()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
        $""""""
        |bar
         """"""
             }
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles6()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
        $""""""
        |bar
        |{
        |   1 + 1
        |}
         """"""
             }
            |baz
             """""";
    }
}");
        }

        [Fact]
        public async Task TestWithNestedHoles7()
        {
            await TestAsync(@"class C
{
    void M()
    {
        var x =
            $""""""
            |goo
            |{
        $""""""
        |bar
        |{
        1 + 1
         }
         """"""
             }
            |baz
             """""";
    }
}");
        }

        [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1542623")]
        public async Task TestWithManyConcatenatedStrings()
        {
            var input = new StringBuilder(
                """
                class C
                {
                    void M()
                    {
                        _ =
                """);

            for (var i = 0; i < 2000; i++)
            {
                input.AppendLine(
                    """
                            @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" +
                    """);
            }

            input.AppendLine(
                """
                        @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"" + "" + @"";
                    }
                }
                """);

            await TestAsync(input.ToString());
        }
    }
}
