// 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.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
    internal static class DefaultExpressionSyntaxExtensions
    {
        private static readonly LiteralExpressionSyntax s_defaultLiteralExpression =
            SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression);

        public static bool CanReplaceWithDefaultLiteral(
            this DefaultExpressionSyntax defaultExpression,
            CSharpParseOptions parseOptions,
            bool preferSimpleDefaultExpression,
            SemanticModel semanticModel,
            CancellationToken cancellationToken)
        {
            if (parseOptions.LanguageVersion < LanguageVersion.CSharp7_1 ||
                !preferSimpleDefaultExpression)
            {
                return false;
            }

            // Using the speculation analyzer can be slow.  Check for common cases first before
            // trying the expensive path.
            return CanReplaceWithDefaultLiteralFast(defaultExpression, semanticModel, cancellationToken) ??
                   CanReplaceWithDefaultLiteralSlow(defaultExpression, semanticModel, cancellationToken);
        }

        private static bool? CanReplaceWithDefaultLiteralFast(
            DefaultExpressionSyntax defaultExpression, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            if (defaultExpression?.Parent is EqualsValueClauseSyntax equalsValueClause)
            {
                var typeSyntax = GetTypeSyntax(equalsValueClause);

                if (typeSyntax != null)
                {
                    if (typeSyntax.IsVar)
                    {
                        // If we have:   var v = default(CancellationToken);    then we can't simplify this.
                        return false;
                    }

                    var entityType = semanticModel.GetTypeInfo(typeSyntax, cancellationToken).Type;
                    var defaultType = semanticModel.GetTypeInfo(defaultExpression.Type, cancellationToken).Type;

                    if (entityType != null && entityType.Equals(defaultType))
                    {
                        // We have a simple case of "CancellationToken c = default(CancellationToken)".
                        // We can just simplify without having to do any additional analysis.
                        return true;
                    }
                }
            }

            return null;
        }

        private static TypeSyntax GetTypeSyntax(EqualsValueClauseSyntax equalsValueClause)
        {
            if (equalsValueClause.IsParentKind(SyntaxKind.VariableDeclarator) &&
                equalsValueClause.Parent?.Parent is VariableDeclarationSyntax declaration)
            {
                return declaration.Type;
            }
            else if (equalsValueClause?.Parent is ParameterSyntax parameter)
            {
                return parameter.Type;
            }

            return null;
        }

        private static bool CanReplaceWithDefaultLiteralSlow(
            DefaultExpressionSyntax defaultExpression, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            var speculationAnalyzer = new SpeculationAnalyzer(
                defaultExpression, s_defaultLiteralExpression, semanticModel,
                cancellationToken,
                skipVerificationForReplacedNode: false,
                failOnOverloadResolutionFailuresInOriginalCode: true);

            return !speculationAnalyzer.ReplacementChangesSemantics();
        }
    }
}
