﻿// 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 Microsoft.Cci;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace Microsoft.CodeAnalysis.CodeGen
{
    internal sealed class CompilationTestData
    {
        internal readonly struct MethodData
        {
            public readonly ILBuilder ILBuilder;
            public readonly IMethodSymbolInternal Method;

            public MethodData(ILBuilder ilBuilder, IMethodSymbolInternal method)
            {
                this.ILBuilder = ilBuilder;
                this.Method = method;
            }
        }

        // The map is used for storing a list of methods and their associated IL.
        public readonly ConcurrentDictionary<IMethodSymbolInternal, MethodData> Methods = new ConcurrentDictionary<IMethodSymbolInternal, MethodData>();

        // The emitted module.
        public CommonPEModuleBuilder? Module;

        // MetadataWriter used to emit metadata
        public MetadataWriter? MetadataWriter { get; private set; }

        public Func<ISymWriterMetadataProvider, SymUnmanagedWriter>? SymWriterFactory;

        private ImmutableDictionary<string, MethodData>? _lazyMethodsByName;

        public void SetMetadataWriter(MetadataWriter writer)
        {
            Debug.Assert(MetadataWriter == null);
            MetadataWriter = writer;
        }

        public void SetMethodILBuilder(IMethodSymbolInternal method, ILBuilder builder)
        {
            Methods.Add(method, new MethodData(builder, method));
        }

        public ILBuilder GetIL(Func<IMethodSymbolInternal, bool> predicate)
        {
            return Methods.Single(p => predicate(p.Key)).Value.ILBuilder;
        }

        // Returns map indexed by name for those methods that have a unique name.
        public ImmutableDictionary<string, MethodData> GetMethodsByName()
        {
            if (_lazyMethodsByName == null)
            {
                var map = new Dictionary<string, MethodData>();
                foreach (var pair in Methods)
                {
                    var name = GetMethodName(pair.Key);
                    if (map.ContainsKey(name))
                    {
                        map[name] = default(MethodData);
                    }
                    else
                    {
                        map.Add(name, pair.Value);
                    }
                }
                var methodsByName = map.Where(p => p.Value.Method != null).ToImmutableDictionary();
                Interlocked.CompareExchange(ref _lazyMethodsByName, methodsByName, null);
            }
            return _lazyMethodsByName;
        }

        private static readonly SymbolDisplayFormat _testDataKeyFormat = new SymbolDisplayFormat(
            compilerInternalOptions:
                SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames |
                SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes,
            globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
            memberOptions:
                SymbolDisplayMemberOptions.IncludeParameters |
                SymbolDisplayMemberOptions.IncludeContainingType |
                SymbolDisplayMemberOptions.IncludeExplicitInterface,
            parameterOptions:
                SymbolDisplayParameterOptions.IncludeParamsRefOut |
                SymbolDisplayParameterOptions.IncludeExtensionThis |
                SymbolDisplayParameterOptions.IncludeType,
            // Not showing the name is important because we visit parameters to display their
            // types.  If we visited their types directly, we wouldn't get ref/out/params.
            miscellaneousOptions:
                SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
                SymbolDisplayMiscellaneousOptions.ExpandValueTuple |
                SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
                SymbolDisplayMiscellaneousOptions.UseAsterisksInMultiDimensionalArrays |
                SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName);

        private static readonly SymbolDisplayFormat _testDataOperatorKeyFormat = new SymbolDisplayFormat(
             _testDataKeyFormat.CompilerInternalOptions,
             _testDataKeyFormat.GlobalNamespaceStyle,
             _testDataKeyFormat.TypeQualificationStyle,
             _testDataKeyFormat.GenericsOptions,
             _testDataKeyFormat.MemberOptions | SymbolDisplayMemberOptions.IncludeType,
             _testDataKeyFormat.ParameterOptions,
             _testDataKeyFormat.DelegateStyle,
             _testDataKeyFormat.ExtensionMethodStyle,
             _testDataKeyFormat.PropertyStyle,
             _testDataKeyFormat.LocalOptions,
             _testDataKeyFormat.KindOptions,
             _testDataKeyFormat.MiscellaneousOptions);

        private static string GetMethodName(IMethodSymbolInternal methodSymbol)
        {
            IMethodSymbol iMethod = (IMethodSymbol)methodSymbol.GetISymbol();
            var format = (iMethod.MethodKind == MethodKind.UserDefinedOperator) ?
                _testDataOperatorKeyFormat :
                _testDataKeyFormat;
            return iMethod.ToDisplayString(format);
        }
    }
}
