// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;

namespace Microsoft.CodeAnalysis.LanguageService
{
    internal abstract partial class AbstractSemanticFactsService : ISemanticFacts
    {
        public abstract ISyntaxFacts SyntaxFacts { get; }
        public abstract IBlockFacts BlockFacts { get; }

        protected abstract ISemanticFacts SemanticFacts { get; }

        protected abstract SyntaxToken ToIdentifierToken(string identifier);

        // local name can be same as field or property. but that will hide
        // those and can cause semantic change later in some context.
        // so to be safe, we consider field and property in scope when
        // creating unique name for local
        private static readonly Func<ISymbol, bool> s_LocalNameFilter = s =>
            s.Kind == SymbolKind.Local ||
            s.Kind == SymbolKind.Parameter ||
            s.Kind == SymbolKind.RangeVariable ||
            s.Kind == SymbolKind.Field ||
            s.Kind == SymbolKind.Property ||
            (s.Kind == SymbolKind.NamedType && s.IsStatic);

        public SyntaxToken GenerateUniqueName(
            SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt,
            string baseName, CancellationToken cancellationToken)
        {
            return GenerateUniqueName(
                semanticModel, location, containerOpt, baseName, filter: null, usedNames: null, cancellationToken);
        }

        public SyntaxToken GenerateUniqueName(
            SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt,
            string baseName, IEnumerable<string> usedNames, CancellationToken cancellationToken)
        {
            return GenerateUniqueName(
                semanticModel, location, containerOpt, baseName, filter: null, usedNames, cancellationToken);
        }

        public SyntaxToken GenerateUniqueLocalName(
            SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt,
            string baseName, CancellationToken cancellationToken)
        {
            return GenerateUniqueName(
                semanticModel, location, containerOpt, baseName, s_LocalNameFilter, usedNames: Enumerable.Empty<string>(), cancellationToken);
        }

        public SyntaxToken GenerateUniqueLocalName(
            SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt,
            string baseName, IEnumerable<string> usedNames, CancellationToken cancellationToken)
        {
            return GenerateUniqueName(
                semanticModel, location, containerOpt, baseName, s_LocalNameFilter, usedNames: usedNames, cancellationToken);
        }

        public SyntaxToken GenerateUniqueName(
            SemanticModel semanticModel,
            SyntaxNode location, SyntaxNode containerOpt,
            string baseName, Func<ISymbol, bool> filter,
            IEnumerable<string> usedNames, CancellationToken cancellationToken)
        {
            var container = containerOpt ?? location.AncestorsAndSelf().FirstOrDefault(
                a => BlockFacts.IsExecutableBlock(a) || SyntaxFacts.IsParameterList(a) || SyntaxFacts.IsMethodBody(a));

            var candidates = GetCollidableSymbols(semanticModel, location, container, cancellationToken);
            var filteredCandidates = filter != null ? candidates.Where(filter) : candidates;

            return GenerateUniqueName(baseName, filteredCandidates.Select(s => s.Name).Concat(usedNames));
        }

        /// <summary>
        /// Retrieves all symbols that could collide with a symbol at the specified location.
        /// A symbol can possibly collide with the location if it is available to that location and/or
        /// could cause a compiler error if its name is re-used at that location.
        /// </summary>
        protected virtual IEnumerable<ISymbol> GetCollidableSymbols(SemanticModel semanticModel, SyntaxNode location, SyntaxNode container, CancellationToken cancellationToken)
            => semanticModel.LookupSymbols(location.SpanStart).Concat(semanticModel.GetExistingSymbols(container, cancellationToken));

        public SyntaxToken GenerateUniqueName(string baseName, IEnumerable<string> usedNames)
        {
            return this.ToIdentifierToken(
                NameGenerator.EnsureUniqueness(
                    baseName, usedNames, this.SyntaxFacts.IsCaseSensitive));
        }

#nullable enable

        protected static IMethodSymbol? FindDisposeMethod(Compilation compilation, ITypeSymbol? type, bool isAsync)
        {
            if (type is null)
                return null;

            var methodToLookFor = isAsync
                ? GetDisposeMethod(typeof(IAsyncDisposable).FullName!, nameof(IAsyncDisposable.DisposeAsync))
                : GetDisposeMethod(typeof(IDisposable).FullName!, nameof(IDisposable.Dispose));
            if (methodToLookFor is null)
                return null;

            var impl = type.FindImplementationForInterfaceMember(methodToLookFor) ?? methodToLookFor;
            return impl as IMethodSymbol;

            IMethodSymbol? GetDisposeMethod(string typeName, string methodName)
            {
                var disposableType = compilation.GetBestTypeByMetadataName(typeName);
                return disposableType?.GetMembers().OfType<IMethodSymbol>().FirstOrDefault(m => m.Parameters.Length == 0 && m.Name == methodName);
            }
        }

#nullable disable

        #region ISemanticFacts implementation

        public bool SupportsImplicitInterfaceImplementation => SemanticFacts.SupportsImplicitInterfaceImplementation;

        public bool SupportsParameterizedProperties => SemanticFacts.SupportsParameterizedProperties;

        public bool ExposesAnonymousFunctionParameterNames => SemanticFacts.ExposesAnonymousFunctionParameterNames;

        public bool IsWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsWrittenTo(semanticModel, node, cancellationToken);

        public bool IsOnlyWrittenTo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsOnlyWrittenTo(semanticModel, node, cancellationToken);

        public bool IsInOutContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsInOutContext(semanticModel, node, cancellationToken);

        public bool IsInRefContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsInRefContext(semanticModel, node, cancellationToken);

        public bool IsInInContext(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsInInContext(semanticModel, node, cancellationToken);

        public bool CanReplaceWithRValue(SemanticModel semanticModel, SyntaxNode expression, CancellationToken cancellationToken)
            => SemanticFacts.CanReplaceWithRValue(semanticModel, expression, cancellationToken);

        public ISymbol GetDeclaredSymbol(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken)
            => SemanticFacts.GetDeclaredSymbol(semanticModel, token, cancellationToken);

        public bool LastEnumValueHasInitializer(INamedTypeSymbol namedTypeSymbol)
            => SemanticFacts.LastEnumValueHasInitializer(namedTypeSymbol);

        public bool TryGetSpeculativeSemanticModel(SemanticModel oldSemanticModel, SyntaxNode oldNode, SyntaxNode newNode, out SemanticModel speculativeModel)
            => SemanticFacts.TryGetSpeculativeSemanticModel(oldSemanticModel, oldNode, newNode, out speculativeModel);

        public ImmutableHashSet<string> GetAliasNameSet(SemanticModel model, CancellationToken cancellationToken)
            => SemanticFacts.GetAliasNameSet(model, cancellationToken);

        public ForEachSymbols GetForEachSymbols(SemanticModel semanticModel, SyntaxNode forEachStatement)
            => SemanticFacts.GetForEachSymbols(semanticModel, forEachStatement);

        public SymbolInfo GetCollectionInitializerSymbolInfo(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.GetCollectionInitializerSymbolInfo(semanticModel, node, cancellationToken);

        public IMethodSymbol GetGetAwaiterMethod(SemanticModel semanticModel, SyntaxNode node)
            => SemanticFacts.GetGetAwaiterMethod(semanticModel, node);

        public ImmutableArray<IMethodSymbol> GetDeconstructionAssignmentMethods(SemanticModel semanticModel, SyntaxNode node)
            => SemanticFacts.GetDeconstructionAssignmentMethods(semanticModel, node);

        public ImmutableArray<IMethodSymbol> GetDeconstructionForEachMethods(SemanticModel semanticModel, SyntaxNode node)
            => SemanticFacts.GetDeconstructionForEachMethods(semanticModel, node);

        public bool IsPartial(ITypeSymbol typeSymbol, CancellationToken cancellationToken)
            => SemanticFacts.IsPartial(typeSymbol, cancellationToken);

        public IEnumerable<ISymbol> GetDeclaredSymbols(SemanticModel semanticModel, SyntaxNode memberDeclaration, CancellationToken cancellationToken)
            => SemanticFacts.GetDeclaredSymbols(semanticModel, memberDeclaration, cancellationToken);

        public IParameterSymbol FindParameterForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, bool allowUncertainCandidates, bool allowParams, CancellationToken cancellationToken)
            => SemanticFacts.FindParameterForArgument(semanticModel, argumentNode, allowUncertainCandidates, allowParams, cancellationToken);

        public IParameterSymbol FindParameterForAttributeArgument(SemanticModel semanticModel, SyntaxNode argumentNode, bool allowUncertainCandidates, bool allowParams, CancellationToken cancellationToken)
            => SemanticFacts.FindParameterForAttributeArgument(semanticModel, argumentNode, allowUncertainCandidates, allowParams, cancellationToken);

        public ISymbol FindFieldOrPropertyForArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken)
            => SemanticFacts.FindFieldOrPropertyForArgument(semanticModel, argumentNode, cancellationToken);

        public ISymbol FindFieldOrPropertyForAttributeArgument(SemanticModel semanticModel, SyntaxNode argumentNode, CancellationToken cancellationToken)
            => SemanticFacts.FindFieldOrPropertyForAttributeArgument(semanticModel, argumentNode, cancellationToken);

        public ImmutableArray<ISymbol> GetBestOrAllSymbols(SemanticModel semanticModel, SyntaxNode node, SyntaxToken token, CancellationToken cancellationToken)
            => SemanticFacts.GetBestOrAllSymbols(semanticModel, node, token, cancellationToken);

        public bool IsInsideNameOfExpression(SemanticModel semanticModel, SyntaxNode node, CancellationToken cancellationToken)
            => SemanticFacts.IsInsideNameOfExpression(semanticModel, node, cancellationToken);

        public ImmutableArray<IMethodSymbol> GetLocalFunctionSymbols(Compilation compilation, ISymbol symbol, CancellationToken cancellationToken)
            => SemanticFacts.GetLocalFunctionSymbols(compilation, symbol, cancellationToken);

        public bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken)
            => SemanticFacts.IsInExpressionTree(semanticModel, node, expressionTypeOpt, cancellationToken);

        public string GenerateNameForExpression(SemanticModel semanticModel, SyntaxNode expression, bool capitalize, CancellationToken cancellationToken)
            => SemanticFacts.GenerateNameForExpression(semanticModel, expression, capitalize, cancellationToken);

        #endregion
    }
}
