// 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.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis
{
    internal partial struct SymbolKey
    {
        /// <summary>
        /// Anonymous functions and anonymous-delegates (the special VB synthesized delegate types),
        /// only come into existence when someone has explicitly written a lambda in their source 
        /// code. So to appropriately round-trip this symbol we store the location that the lambda
        /// was at so that we can find the symbol again when we resolve the key.
        /// </summary>
        private static class AnonymousFunctionOrDelegateSymbolKey
        {
            public static void Create(ISymbol symbol, SymbolKeyWriter visitor)
            {
                Debug.Assert(symbol.IsAnonymousDelegateType() || symbol.IsAnonymousFunction());

                // Write out if this was an anonymous delegate or anonymous function.
                // In both cases they'll have the same location (the location of 
                // the lambda that forced them into existence).  When we resolve the
                // symbol later, if it's an anonymous delegate, we'll first resolve to
                // the anonymous-function, then use that anonymous-functoin to get at
                // the synthesized anonymous delegate.
                visitor.WriteBoolean(symbol.IsAnonymousDelegateType());
                visitor.WriteLocation(symbol.Locations.First());
            }

            public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? failureReason)
            {
                var isAnonymousDelegateType = reader.ReadBoolean();
                var location = reader.ReadLocation(out var locationFailureReason)!;

                if (locationFailureReason != null)
                {
                    failureReason = $"({nameof(AnonymousFunctionOrDelegateSymbolKey)} {nameof(location)} failed -> {locationFailureReason})";
                    return default;
                }

                var syntaxTree = location.SourceTree;
                if (syntaxTree == null)
                {
                    failureReason = $"({nameof(AnonymousFunctionOrDelegateSymbolKey)} {nameof(SyntaxTree)} failed)";
                    return default;
                }

                var semanticModel = reader.Compilation.GetSemanticModel(syntaxTree);
                var root = syntaxTree.GetRoot(reader.CancellationToken);
                var node = root.FindNode(location.SourceSpan, getInnermostNodeForTie: true);

                var symbol = semanticModel.GetSymbolInfo(node, reader.CancellationToken)
                                          .GetAnySymbol();

                // If this was a key for an anonymous delegate type, then go find the
                // associated delegate for this lambda and return that instead of the 
                // lambda function symbol itself.
                if (isAnonymousDelegateType && symbol is IMethodSymbol methodSymbol)
                {
                    var anonymousDelegate = methodSymbol.AssociatedAnonymousDelegate;
                    symbol = anonymousDelegate;
                }

                failureReason = null;
                return new SymbolKeyResolution(symbol);
            }
        }
    }
}
