// 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.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Precedence;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Precedence;
using Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses;

namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryParentheses
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    internal class CSharpRemoveUnnecessaryExpressionParenthesesDiagnosticAnalyzer
        : AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer<SyntaxKind, ParenthesizedExpressionSyntax>
    {
        protected override SyntaxKind GetSyntaxKind()
            => SyntaxKind.ParenthesizedExpression;

        protected override ISyntaxFacts GetSyntaxFacts()
            => CSharpSyntaxFacts.Instance;

        protected override bool CanRemoveParentheses(
            ParenthesizedExpressionSyntax parenthesizedExpression,
            SemanticModel semanticModel, CancellationToken cancellationToken,
            out PrecedenceKind precedence, out bool clarifiesPrecedence)
        {
            return CanRemoveParenthesesHelper(
                parenthesizedExpression, semanticModel, cancellationToken,
                out precedence, out clarifiesPrecedence);
        }

        public static bool CanRemoveParenthesesHelper(
            ParenthesizedExpressionSyntax parenthesizedExpression, SemanticModel semanticModel, CancellationToken cancellationToken,
            out PrecedenceKind parentPrecedenceKind, out bool clarifiesPrecedence)
        {
            var result = parenthesizedExpression.CanRemoveParentheses(semanticModel, cancellationToken);
            if (!result)
            {
                parentPrecedenceKind = default;
                clarifiesPrecedence = false;
                return false;
            }

            var inner = parenthesizedExpression.Expression;
            var innerPrecedence = inner.GetOperatorPrecedence();
            var innerIsSimple = innerPrecedence is OperatorPrecedence.Primary or
                                OperatorPrecedence.None;

            ExpressionSyntax parentExpression;
            switch (parenthesizedExpression.Parent)
            {
                case ConditionalExpressionSyntax _:
                    // If our parent is a conditional, then only remove parens if the inner
                    // expression is a primary. i.e. it's ok to remove any of the following:
                    //
                    //      (a()) ? (b.length) : (c[0])
                    //
                    // But we shouldn't remove parens for anything more complex like:
                    //
                    //      ++a ? b + c : d << e
                    //
                    parentPrecedenceKind = PrecedenceKind.Other;
                    clarifiesPrecedence = false;
                    return innerIsSimple;

                case BinaryExpressionSyntax binaryExpression:
                    parentExpression = binaryExpression;
                    break;

                case IsPatternExpressionSyntax isPatternExpression:
                    // on the left side of an 'x is pat' expression
                    parentExpression = isPatternExpression;
                    break;

                case ConstantPatternSyntax constantPattern when constantPattern.Parent is IsPatternExpressionSyntax isPatternExpression:
                    // on the right side of an 'x is const_pattern' expression
                    parentExpression = isPatternExpression;
                    break;

                default:
                    parentPrecedenceKind = PrecedenceKind.Other;
                    clarifiesPrecedence = false;
                    return true;
            }

            // We're parented by something binary-like. 
            parentPrecedenceKind = CSharpExpressionPrecedenceService.Instance.GetPrecedenceKind(parentExpression);

            // Precedence is clarified any time we have expression with different precedence
            // (and the inner expression is not a primary expression).  in other words, this
            // is helps clarify precedence:
            //
            //      a + (b * c)
            //
            // However, this does not:
            //
            //      a + (b.Length)
            clarifiesPrecedence = !innerIsSimple &&
                                  parentExpression.GetOperatorPrecedence() != innerPrecedence;
            return true;
        }
    }
}
