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

namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode? VisitLocalDeclaration(BoundLocalDeclaration node)
        {
            return RewriteLocalDeclaration(node, node.Syntax, node.LocalSymbol, VisitExpression(node.InitializerOpt), node.HasErrors);
        }

        private BoundStatement? RewriteLocalDeclaration(BoundLocalDeclaration? originalOpt, SyntaxNode syntax, LocalSymbol localSymbol, BoundExpression? rewrittenInitializer, bool hasErrors = false)
        {
            // A declaration of a local variable without an initializer has no associated IL.
            // Simply remove the declaration from the bound tree. The local symbol will
            // remain in the bound block, so codegen will make a stack frame location for it.
            if (rewrittenInitializer == null)
            {
                return null;
            }

            // A declaration of a local constant also does nothing, even though there is
            // an assignment. The value will be emitted directly where it is used. The 
            // local symbol remains in the bound block, but codegen will skip making a 
            // stack frame location for it. (We still need a symbol for it to stay 
            // around because we'll be generating debug info for it.)
            if (localSymbol.IsConst)
            {
                if (!localSymbol.Type.IsReferenceType && localSymbol.ConstantValue == null)
                {
                    // This can occur in error scenarios (e.g. bad imported metadata)
                    hasErrors = true;
                }
                else
                {
                    return null;
                }
            }

            // lowered local declaration node is associated with declaration (not whole statement)
            // this is done to make sure that debugger stepping is same as before
            var localDeclaration = syntax as LocalDeclarationStatementSyntax;
            if (localDeclaration != null)
            {
                syntax = localDeclaration.Declaration.Variables[0];
            }

            BoundStatement rewrittenLocalDeclaration = new BoundExpressionStatement(
                syntax,
                new BoundAssignmentOperator(
                    syntax,
                    new BoundLocal(
                        syntax,
                        localSymbol,
                        null,
                        localSymbol.Type
                    ),
                    rewrittenInitializer,
                    localSymbol.Type,
                    localSymbol.IsRef),
                hasErrors);

            return InstrumentLocalDeclarationIfNecessary(originalOpt, localSymbol, rewrittenLocalDeclaration);
        }

        private BoundStatement InstrumentLocalDeclarationIfNecessary(BoundLocalDeclaration? originalOpt, LocalSymbol localSymbol, BoundStatement rewrittenLocalDeclaration)
        {
            // Add sequence points, if necessary.
            if (this.Instrument && originalOpt?.WasCompilerGenerated == false && !localSymbol.IsConst &&
                (originalOpt.Syntax.Kind() == SyntaxKind.VariableDeclarator ||
                    (originalOpt.Syntax.Kind() == SyntaxKind.LocalDeclarationStatement &&
                        ((LocalDeclarationStatementSyntax)originalOpt.Syntax).Declaration.Variables.Count == 1)))
            {
                rewrittenLocalDeclaration = _instrumenter.InstrumentLocalInitialization(originalOpt, rewrittenLocalDeclaration);
            }

            return rewrittenLocalDeclaration;
        }

        public sealed override BoundNode VisitOutVariablePendingInference(OutVariablePendingInference node)
        {
            throw ExceptionUtilities.Unreachable;
        }
    }
}
