// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

#if CODEANALYSIS_V3_OR_BETTER

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Analyzer.Utilities
{
    /// <summary>
    /// Analyzer configuration options for a given syntax tree from <see cref="AnalyzerConfigOptions"/>
    /// </summary>
    internal sealed class SyntaxTreeCategorizedAnalyzerConfigOptions : AbstractCategorizedAnalyzerConfigOptions
    {
        private readonly AnalyzerConfigOptions? _analyzerConfigOptions;
        private static readonly ConditionalWeakTable<ImmutableDictionary<string, string>, SyntaxTreeCategorizedAnalyzerConfigOptions> s_perTreeOptionsCache =
            new ConditionalWeakTable<ImmutableDictionary<string, string>, SyntaxTreeCategorizedAnalyzerConfigOptions>();

        public static readonly SyntaxTreeCategorizedAnalyzerConfigOptions Empty = new SyntaxTreeCategorizedAnalyzerConfigOptions(analyzerConfigOptions: null);

        private SyntaxTreeCategorizedAnalyzerConfigOptions(AnalyzerConfigOptions? analyzerConfigOptions)
        {
            _analyzerConfigOptions = analyzerConfigOptions;
        }

        public static SyntaxTreeCategorizedAnalyzerConfigOptions Create(AnalyzerConfigOptions? analyzerConfigOptions)
        {
            if (analyzerConfigOptions == null)
            {
                return Empty;
            }

            var optionsMap = TryGetBackingOptionsDictionary(analyzerConfigOptions);
            if (optionsMap == null)
            {
                return new SyntaxTreeCategorizedAnalyzerConfigOptions(analyzerConfigOptions);
            }

            if (optionsMap.IsEmpty)
            {
                return Empty;
            }

            return s_perTreeOptionsCache.GetValue(optionsMap, _ => new SyntaxTreeCategorizedAnalyzerConfigOptions(analyzerConfigOptions));

            // Local functions.
            static ImmutableDictionary<string, string>? TryGetBackingOptionsDictionary(AnalyzerConfigOptions analyzerConfigOptions)
            {
                // Reflection based optimization for analyzer config options.
                // Ideally 'AnalyzerConfigOptions' would expose such an the API.
                var type = analyzerConfigOptions.GetType();
                const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
                return type.GetField("_backing", flags)?.GetValue(analyzerConfigOptions) as ImmutableDictionary<string, string> ??
                    type.GetField("_analyzerOptions", flags)?.GetValue(analyzerConfigOptions) as ImmutableDictionary<string, string>;
            }
        }

        public override bool IsEmpty => ReferenceEquals(this, Empty);

        protected override bool TryGetOptionValue(string optionKeyPrefix, string? optionKeySuffix, string optionName, [NotNullWhen(returnValue: true)] out string? valueString)
        {
            if (IsEmpty)
            {
                valueString = null;
                return false;
            }

            RoslynDebug.Assert(_analyzerConfigOptions != null);
            var key = optionKeyPrefix;
            if (optionKeySuffix != null)
            {
                key += $"{optionKeySuffix}.";
            }

            key += optionName;

            return _analyzerConfigOptions.TryGetValue(key, out valueString);
        }
    }
}

#endif