﻿// 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;
using System.Globalization;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;

namespace Microsoft.VisualStudio.LanguageServices.Utilities
{
    internal abstract class ProjectPropertyStorage
    {
        // For CPS projects, we prefer to use IVsBuildPropertyStorage since that allows us to easily modify the project
        // properties independent of configuration. However this is problematic in the legacy project system, where setting
        // the build property would indeed change its value, however that change would only be visible after the project is
        // unloaded and reloaded. The language service would not be updated (the error we're trying to fix would still
        // be visible) and any checkbox in the project properties dialog would not be updated. The language service issue
        // could be worked around by using VSProject.Refresh, but that does not fix the project properties dialog. Therefore
        // for the legacy project system, we choose to use ConfigurationManager to iterate & update each configuration,
        // which works correctly, even though it is a little less convinient because it creates a more verbose project file
        // (although we're dealing with a legacy project file... it's already verbose anyway).

        // It's important to note that the property name may differ in these two implementations. The build property name
        // corresponds to the name of the property in the project file (for example LangVersion), whereas the configuration
        // property name comes from an interface such as CSharpProjectConfigurationProperties3 (for example LanguageVersion).

        public static ProjectPropertyStorage Create(Project project, IServiceProvider serviceProvider)
        {
            var solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
            solution.GetProjectOfUniqueName(project.UniqueName, out var hierarchy);

            return hierarchy.IsCapabilityMatch("CPS")
                ? new BuildPropertyStorage((IVsBuildPropertyStorage)hierarchy)
                : new PerConfigurationPropertyStorage(project.ConfigurationManager);
        }

        public abstract void SetProperty(string buildPropertyName, string configurationPropertyName, string value);

        public void SetProperty(string buildPropertyName, string configurationPropertyName, bool value)
            => SetProperty(buildPropertyName, configurationPropertyName, value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());

        private sealed class BuildPropertyStorage : ProjectPropertyStorage
        {
            private readonly IVsBuildPropertyStorage _propertyStorage;

            public BuildPropertyStorage(IVsBuildPropertyStorage propertyStorage)
                => _propertyStorage = propertyStorage;

            public override void SetProperty(string buildPropertyName, string configurationPropertyName, string value)
                => _propertyStorage.SetPropertyValue(buildPropertyName, null, (uint)_PersistStorageType.PST_PROJECT_FILE, value);
        }

        private sealed class PerConfigurationPropertyStorage : ProjectPropertyStorage
        {
            private readonly ConfigurationManager _configurationManager;

            public PerConfigurationPropertyStorage(ConfigurationManager configurationManager)
                => _configurationManager = configurationManager;

            public override void SetProperty(string buildPropertyName, string configurationPropertyName, string value)
            {
                foreach (Configuration configuration in _configurationManager)
                {
                    configuration.Properties.Item(configurationPropertyName).Value = value;
                }
            }
        }
    }
}
