﻿// 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 Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.EditorConfigSettings.DataProvider
{
    [UseExportProvider]
    [Trait(Traits.Feature, Traits.Features.EditorConfigUI)]
    public partial class DataProviderTests
    {
        private static Workspace GetWorkspace(string? projectFilePath = null)
        {
            var projectId = ProjectId.CreateNewId();
            var workspace = new AdhocWorkspace(VisualStudioTestCompositions.LanguageServices.GetHostServices(), WorkspaceKind.Host);
            Assert.True(workspace.TryApplyChanges(workspace.CurrentSolution
                .AddProject(ProjectInfo.Create(projectId, VersionStamp.Create(), "proj1", "proj1.dll", LanguageNames.CSharp, filePath: projectFilePath))
                .AddDocument(DocumentId.CreateNewId(projectId), "goo.cs", "public class Goo { }")
                .AddAdditionalDocument(DocumentId.CreateNewId(projectId), "add.txt", "text")
                .AddAnalyzerReference(projectId, new MockAnalyzerReference())
                .AddAnalyzerConfigDocument(DocumentId.CreateNewId(projectId), "editorcfg", SourceText.From("config"), filePath: "/a/b")));
            return workspace;
        }

        private static IWorkspaceSettingsProviderFactory<T> GettingSettingsProviderFactoryFromWorkspace<T>()
            => GetWorkspace("/a/b/proj1.csproj").Services.GetRequiredService<IWorkspaceSettingsProviderFactory<T>>();

        private static ILanguageSettingsProviderFactory<T> GettingSettingsProviderFactoryFromLanguageService<T>(string languageName)
            => GetWorkspace("/a/b/proj1.csproj").Services.GetLanguageServices(languageName).GetRequiredService<ILanguageSettingsProviderFactory<T>>();

        private static IWorkspaceSettingsProviderFactory<T> GettingSettingsProviderFactoryFromWorkspaceWithNullProjectPath<T>()
            => GetWorkspace().Services.GetRequiredService<IWorkspaceSettingsProviderFactory<T>>();

        private static ILanguageSettingsProviderFactory<T> GettingSettingsProviderFactoryFromLanguageServiceWithNullProjectPath<T>(string languageName)
            => GetWorkspace().Services.GetLanguageServices(languageName).GetRequiredService<ILanguageSettingsProviderFactory<T>>();

        private static ISettingsProvider<T> TestGettingSettingsProviderFromWorkspace<T>()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspace<T>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            Assert.NotNull(settingsProvider);
            return settingsProvider;
        }

        private static ISettingsProvider<T> TestGettingSettingsProviderFromLanguageService<T>()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromLanguageService<T>(LanguageNames.CSharp);
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            Assert.NotNull(settingsProvider);
            return settingsProvider;
        }

        [Fact]
        public void TestGettingAnalyzerSettingsProvider()
        {
            TestGettingSettingsProviderFromWorkspace<AnalyzerSetting>();
        }

        [Fact]
        public void TestGettingCodeStyleSettingsProvider()
        {
            TestGettingSettingsProviderFromWorkspace<CodeStyleSetting>();
            TestGettingSettingsProviderFromLanguageService<CodeStyleSetting>();
        }

        [Fact]
        public void TestGettingWhitespaceSettingsProvider()
        {
            TestGettingSettingsProviderFromWorkspace<Setting>();
            TestGettingSettingsProviderFromLanguageService<Setting>();
        }

        [Fact]
        public void TestGettingNamingStyleSettingsProvider()
        {
            TestGettingSettingsProviderFromWorkspace<NamingStyleSetting>();
        }

        [Fact]
        public void TestGettingAnalyzerSettingsProviderWorkspaceServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspace<AnalyzerSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            var result = Assert.Single(dataSnapShot);
            Assert.Equal("MyDiagnostic", result.Id);
            Assert.Equal("MockDiagnostic", result.Title);
            Assert.Equal(string.Empty, result.Description);
            Assert.Equal("InternalCategory", result.Category);
            Assert.True(result.IsEnabled);
        }

        [Fact]
        public void TestGettingCodeStyleSettingProviderWorkspaceServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspace<CodeStyleSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();

            // We need to substract for string options that are not yet supported.
            // https://github.com/dotnet/roslyn/issues/62937
            var optionsWithUI = CodeStyleOptions2.AllOptions
                .Remove(CodeStyleOptions2.OperatorPlacementWhenWrapping)
                .Remove(CodeStyleOptions2.FileHeaderTemplate)
                .Remove(CodeStyleOptions2.RemoveUnnecessarySuppressionExclusions)
                .Remove(CodeStyleOptions2.ForEachExplicitCastInSource);

            AssertEx.Equal(
                optionsWithUI.OrderBy(o => o.Definition.ConfigName),
                dataSnapShot.Select(setting => setting.Key.Option).OrderBy(o => o.Definition.ConfigName));
        }

        [Fact]
        public void TestGettingNamingStyleSettingProviderWorkspaceServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspace<NamingStyleSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Collection(dataSnapShot,
                namingStyle1 =>
                {
                    Assert.Equal(CompilerExtensionsResources.Begins_with_I, namingStyle1.StyleName);
                    Assert.Equal(CompilerExtensionsResources.Interface, namingStyle1.TypeName);
                    Assert.Equal(ReportDiagnostic.Info, namingStyle1.Severity);
                },
                namingStyle2 =>
                {
                    Assert.Equal(CompilerExtensionsResources.Pascal_Case, namingStyle2.StyleName);
                    Assert.Equal(CompilerExtensionsResources.Types, namingStyle2.TypeName);
                    Assert.Equal(ReportDiagnostic.Info, namingStyle2.Severity);
                },
                namingStyle3 =>
                {
                    Assert.Equal(CompilerExtensionsResources.Pascal_Case, namingStyle3.StyleName);
                    Assert.Equal(CompilerExtensionsResources.Non_Field_Members, namingStyle3.TypeName);
                    Assert.Equal(ReportDiagnostic.Info, namingStyle3.Severity);
                });
        }

        [Fact]
        public void TestGettingCodeStyleSettingsProviderLanguageServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromLanguageService<CodeStyleSetting>(LanguageNames.CSharp);
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();

            // We don't support PreferredModifierOrder yet:
            var optionsWithUI = CSharpCodeStyleOptions.AllOptions
                .Remove(CSharpCodeStyleOptions.PreferredModifierOrder);

            AssertEx.SetEqual(optionsWithUI, dataSnapShot.Select(setting => setting.Key.Option));
        }

        [Fact]
        public void TestGettingWhitespaceSettingProviderWorkspaceServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspace<Setting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();

            var expectedOptions = new IOption2[]
            {
                FormattingOptions2.IndentationSize,
                FormattingOptions2.InsertFinalNewLine,
                FormattingOptions2.NewLine,
                FormattingOptions2.TabSize,
                FormattingOptions2.UseTabs,
                CodeStyleOptions2.OperatorPlacementWhenWrapping
            };

            AssertEx.Equal(
                expectedOptions.Select(option => option.Definition.ConfigName).OrderBy(n => n),
                dataSnapShot.Select(item => item.Key.Option.Definition.ConfigName).OrderBy(n => n));
        }

        [Fact]
        public void TestGettingWhitespaceSettingProviderLanguageServiceAsync()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromLanguageService<Setting>(LanguageNames.CSharp);
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapshot = settingsProvider.GetCurrentDataSnapshot();

            // multiple settings may share the same option (e.g. settings representing flags of an enum):
            var optionsForSettings = dataSnapshot.GroupBy(s => s.Key.Option).Select(g => g.Key).ToArray();
            AssertEx.SetEqual(CSharpFormattingOptions2.AllOptions, optionsForSettings);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath1()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromLanguageServiceWithNullProjectPath<Setting>(LanguageNames.CSharp);
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath2()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspaceWithNullProjectPath<Setting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath3()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromLanguageServiceWithNullProjectPath<CodeStyleSetting>(LanguageNames.CSharp);
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath4()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspaceWithNullProjectPath<CodeStyleSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath5()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspaceWithNullProjectPath<AnalyzerSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }

        [Fact]
        public void TestGettingSettingProviderWithNullProjectPath6()
        {
            var settingsProviderFactory = GettingSettingsProviderFactoryFromWorkspaceWithNullProjectPath<NamingStyleSetting>();
            var settingsProvider = settingsProviderFactory.GetForFile("/a/b/config");
            var model = new TestViewModel();
            settingsProvider.RegisterViewModel(model);
            var dataSnapShot = settingsProvider.GetCurrentDataSnapshot();
            Assert.Empty(dataSnapShot);
        }
    }
}
