// 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.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitPropertyAccess(BoundPropertyAccess node)
        {
            return VisitPropertyAccess(node, isLeftOfAssignment: false);
        }

        private BoundExpression VisitPropertyAccess(BoundPropertyAccess node, bool isLeftOfAssignment)
        {
            var rewrittenReceiverOpt = VisitExpression(node.ReceiverOpt);
            return MakePropertyAccess(node.Syntax, rewrittenReceiverOpt, node.PropertySymbol, node.ResultKind, node.Type, isLeftOfAssignment, node);
        }

        private BoundExpression MakePropertyAccess(
            SyntaxNode syntax,
            BoundExpression? rewrittenReceiverOpt,
            PropertySymbol propertySymbol,
            LookupResultKind resultKind,
            TypeSymbol type,
            bool isLeftOfAssignment,
            BoundPropertyAccess? oldNodeOpt = null)
        {
            // check for System.Array.[Length|LongLength] on a single dimensional array,
            // we have a special node for such cases.
            if (rewrittenReceiverOpt is { Type: { TypeKind: TypeKind.Array } } && !isLeftOfAssignment)
            {
                var asArrayType = (ArrayTypeSymbol)rewrittenReceiverOpt.Type;
                if (asArrayType.IsSZArray)
                {
                    // NOTE: we are not interested in potential badness of Array.Length property.
                    // If it is bad reference compare will not succeed.
                    if (ReferenceEquals(propertySymbol, _compilation.GetSpecialTypeMember(SpecialMember.System_Array__Length)) ||
                        !_inExpressionLambda && ReferenceEquals(propertySymbol, _compilation.GetSpecialTypeMember(SpecialMember.System_Array__LongLength)))
                    {
                        return new BoundArrayLength(syntax, rewrittenReceiverOpt, type);
                    }
                }
            }

            if (isLeftOfAssignment && propertySymbol.RefKind == RefKind.None)
            {
                // This is a property set access. We return a BoundPropertyAccess node here.
                // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator.

                return oldNodeOpt != null ?
                    oldNodeOpt.Update(rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, type) :
                    new BoundPropertyAccess(syntax, rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, type);
            }
            else
            {
                // This is a property get access
                return MakePropertyGetAccess(syntax, rewrittenReceiverOpt, propertySymbol, oldNodeOpt);
            }
        }

        private BoundExpression MakePropertyGetAccess(SyntaxNode syntax, BoundExpression? rewrittenReceiver, PropertySymbol property, BoundPropertyAccess? oldNodeOpt)
        {
            return MakePropertyGetAccess(syntax, rewrittenReceiver, property, ImmutableArray<BoundExpression>.Empty, default, null, oldNodeOpt);
        }

        private BoundExpression MakePropertyGetAccess(
            SyntaxNode syntax,
            BoundExpression? rewrittenReceiver,
            PropertySymbol property,
            ImmutableArray<BoundExpression> rewrittenArguments,
            ImmutableArray<RefKind> argumentRefKindsOpt,
            MethodSymbol? getMethodOpt = null,
            BoundPropertyAccess? oldNodeOpt = null)
        {
            if (_inExpressionLambda && rewrittenArguments.IsEmpty)
            {
                Debug.Assert(argumentRefKindsOpt.IsDefaultOrEmpty);
                return oldNodeOpt != null ?
                    oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, property.Type) :
                    new BoundPropertyAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, property.Type);
            }
            else
            {
                var getMethod = getMethodOpt ?? property.GetOwnOrInheritedGetMethod();

                Debug.Assert(getMethod is { });
                Debug.Assert(getMethod.ParameterCount == rewrittenArguments.Length);
                Debug.Assert(getMethodOpt is null || ReferenceEquals(getMethod, getMethodOpt));

                return BoundCall.Synthesized(
                    syntax,
                    rewrittenReceiver,
                    initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
                    getMethod,
                    rewrittenArguments,
                    argumentRefKindsOpt);
            }
        }
    }
}
