﻿// 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 System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests.Renamer
{
    [UseExportProvider]
    public abstract class RenamerTests : TestBase
    {
        private const string DefaultDocumentName = "DocumentName";
        private static readonly string s_defaultDocumentPath = @$"Document\Path\{DefaultDocumentName}";

        protected abstract string LanguageName { get; }

        protected struct DocumentWithInfo
        {
            public string Text { get; set; }
            public string DocumentName { get; set; }
            public string DocumentFilePath { get; set; }
            public readonly string[] DocumentFolders => GetDocumentFolders(DocumentFilePath);
        }

        protected async Task TestRenameDocument(
            DocumentWithInfo[] startDocuments,
            DocumentWithInfo[] endDocuments,
            string[] expectedErrors = null)
        {
            using var workspace = new AdhocWorkspace();
            var solution = workspace.CurrentSolution;

            var projectId = ProjectId.CreateNewId();
            var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ProjectName", "AssemblyName", LanguageName, filePath: "");
            var documentIdToDocumentInfoMap = new List<(DocumentId, DocumentWithInfo)>();

            solution = solution
                    .AddProject(projectInfo);

            var remainingErrors = new HashSet<string>(expectedErrors ?? new string[0]);

            for (var i = 0; i < startDocuments.Length; i++)
            {
                var startDocument = startDocuments[i];
                var startSourceText = SourceText.From(startDocument.Text);
                var documentId = DocumentId.CreateNewId(projectId);

                solution = solution
                    .AddDocument(
                        documentId,
                        startDocument.DocumentName,
                        startSourceText,
                        filePath: startDocument.DocumentFilePath,
                        folders: startDocument.DocumentFolders);

                documentIdToDocumentInfoMap.Add((documentId, endDocuments[i]));
            }

            var options = new DocumentRenameOptions();

            foreach (var (documentId, endDocument) in documentIdToDocumentInfoMap)
            {
                var document = solution.GetDocument(documentId);
                var documentRenameResult = await Rename.Renamer.RenameDocumentAsync(document, options, endDocument.DocumentName, endDocument.DocumentFolders);

                foreach (var action in documentRenameResult.ApplicableActions)
                {
                    foreach (var error in action.GetErrors())
                    {
                        Assert.True(remainingErrors.Contains(error), $"Error '{error}' was unexpected");
                        remainingErrors.Remove(error);
                    }

                    // https://github.com/dotnet/roslyn/issues/44220
                    Assert.NotNull(action.GetDescription());
                }

                solution = await documentRenameResult.UpdateSolutionAsync(solution, CancellationToken.None);
                var updatedDocument = solution.GetDocument(documentId);

                if (endDocument.DocumentName is object)
                {
                    Assert.Equal(endDocument.DocumentName, updatedDocument.Name);
                }

                if (endDocument.DocumentFolders is object)
                {
                    AssertEx.SetEqual(endDocument.DocumentFolders, updatedDocument.Folders);
                }

                AssertEx.EqualOrDiff(endDocument.Text, (await updatedDocument.GetTextAsync()).ToString());
                Assert.Equal(0, remainingErrors.Count);
            }
        }

        private static string[] GetDocumentFolders(string filePath)
        {
            if (filePath is null)
            {
                return null;
            }

            var splitPath = filePath.Split('\\');
            if (splitPath.Length == 1)
            {
                return splitPath;
            }

            return splitPath.Take(splitPath.Length - 1).ToArray();
        }

        protected Task TestRenameDocument(string startText, string expectedText, string newDocumentName = null, string newDocumentPath = null, string documentName = null, string documentPath = null, string[] expectedErrors = null)
        {
            var defaultDocumentName = documentName ?? DefaultDocumentName;
            var defaultDocumentPath = documentPath ?? s_defaultDocumentPath;

            var startDocuments = new[]
            {
                new DocumentWithInfo()
                {
                    Text = startText,
                    DocumentName = defaultDocumentName,
                    DocumentFilePath = defaultDocumentPath
                }
            };

            var endDocuments = new[]
            {
                new DocumentWithInfo()
                {
                    Text = expectedText,
                    DocumentName = newDocumentName,
                    DocumentFilePath = newDocumentPath
                }
            };

            return TestRenameDocument(startDocuments, endDocuments, expectedErrors);
        }

        protected async Task TestEmptyActionSet(string startText, string newDocumentName = null, string newDocumentPath = null, string documentName = null, string documentPath = null)
        {
            var defaultDocumentName = documentName ?? DefaultDocumentName;
            var defaultDocumentPath = documentPath ?? s_defaultDocumentPath;

            var startDocuments = new[]
            {
                new DocumentWithInfo()
                {
                    Text = startText,
                    DocumentName = defaultDocumentName,
                    DocumentFilePath = defaultDocumentPath
                }
            };

            var endDocuments = new[]
            {
                new DocumentWithInfo()
                {
                    Text = startText,
                    DocumentName = newDocumentName,
                    DocumentFilePath = newDocumentPath
                }
            };

            using var workspace = new AdhocWorkspace();
            var solution = workspace.CurrentSolution;

            var projectId = ProjectId.CreateNewId();
            var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ProjectName", "AssemblyName", LanguageName, filePath: "");
            var documentIdToDocumentInfoMap = new List<(DocumentId, DocumentWithInfo)>();

            solution = solution
                    .AddProject(projectInfo);

            for (var i = 0; i < startDocuments.Length; i++)
            {
                var startDocument = startDocuments[i];
                var startSourceText = SourceText.From(startDocument.Text);
                var documentId = DocumentId.CreateNewId(projectId);

                solution = solution
                    .AddDocument(
                        documentId,
                        startDocument.DocumentName,
                        startSourceText,
                        filePath: startDocument.DocumentFilePath,
                        folders: startDocument.DocumentFolders);

                documentIdToDocumentInfoMap.Add((documentId, endDocuments[i]));
            }

            var options = new DocumentRenameOptions();

            foreach (var (documentId, endDocument) in documentIdToDocumentInfoMap)
            {
                var document = solution.GetDocument(documentId);
                var documentRenameResult = await Rename.Renamer.RenameDocumentAsync(document, options, endDocument.DocumentName, endDocument.DocumentFolders);
                Assert.Empty(documentRenameResult.ApplicableActions);
            }
        }

        protected async Task TestRenameMappedFile(string startText, string documentName, string newDocumentName)
        {
            using var workspace = new AdhocWorkspace();
            var solution = workspace.CurrentSolution;

            var projectId = ProjectId.CreateNewId();
            var projectInfo = ProjectInfo.Create(projectId, VersionStamp.Create(), "ProjectName", "AssemblyName", LanguageName, filePath: "");

            solution = solution.AddProject(projectInfo);

            var startSourceText = SourceText.From(startText, encoding: null, SourceHashAlgorithms.Default);
            var documentId = DocumentId.CreateNewId(projectId);

            var documentInfo = DocumentInfo.Create(
                documentId,
                documentName,
                folders: GetDocumentFolders(s_defaultDocumentPath),
                loader: TextLoader.From(TextAndVersion.Create(startSourceText, VersionStamp.Create(), documentName)),
                filePath: s_defaultDocumentPath,
                isGenerated: true)
                .WithDocumentServiceProvider(new TestDocumentServiceProvider());

            solution = solution.AddDocument(documentInfo);

            var options = new DocumentRenameOptions();
            var document = solution.GetDocument(documentId);
            var documentRenameResult = await Rename.Renamer.RenameDocumentAsync(document, options, newDocumentName, GetDocumentFolders(s_defaultDocumentPath));
            Assert.Empty(documentRenameResult.ApplicableActions);
        }
    }
}
