﻿// 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 Microsoft.CodeAnalysis.CSharp.Symbols;

namespace Microsoft.CodeAnalysis.CSharp
{
    internal sealed partial class LocalRewriter
    {
        public override BoundNode VisitFieldAccess(BoundFieldAccess node)
        {
            BoundExpression? rewrittenReceiver = VisitExpression(node.ReceiverOpt);
            return MakeFieldAccess(node.Syntax, rewrittenReceiver, node.FieldSymbol, node.ConstantValue, node.ResultKind, node.Type, node);
        }

        private BoundExpression MakeFieldAccess(
            SyntaxNode syntax,
            BoundExpression? rewrittenReceiver,
            FieldSymbol fieldSymbol,
            ConstantValue? constantValueOpt,
            LookupResultKind resultKind,
            TypeSymbol type,
            BoundFieldAccess? oldNodeOpt = null)
        {
            if (fieldSymbol.ContainingType.IsTupleType)
            {
                return MakeTupleFieldAccess(syntax, fieldSymbol, rewrittenReceiver);
            }

            BoundExpression result = oldNodeOpt != null ?
                oldNodeOpt.Update(rewrittenReceiver, fieldSymbol, constantValueOpt, resultKind, type) :
                new BoundFieldAccess(syntax, rewrittenReceiver, fieldSymbol, constantValueOpt, resultKind, type);

            if (fieldSymbol.IsFixedSizeBuffer)
            {
                // a reference to a fixed buffer is translated into its address
                result = new BoundAddressOfOperator(syntax, result, type, false);
            }

            return result;
        }

        /// <summary>
        /// Converts access to a tuple instance into access into the underlying ValueTuple(s).
        ///
        /// For instance, tuple.Item8
        /// produces fieldAccess(field=Item1, receiver=fieldAccess(field=Rest, receiver=ValueTuple for tuple))
        /// </summary>
        private BoundExpression MakeTupleFieldAccess(
            SyntaxNode syntax,
            FieldSymbol tupleField,
            BoundExpression? rewrittenReceiver)
        {
            var tupleType = tupleField.ContainingType;

            NamedTypeSymbol currentLinkType = tupleType;
            FieldSymbol underlyingField = tupleField.TupleUnderlyingField;

            if ((object)underlyingField == null)
            {
                // Use-site error must have been reported elsewhere.
                return _factory.BadExpression(tupleField.Type);
            }

            if (rewrittenReceiver?.Kind == BoundKind.DefaultExpression)
            {
                // Optimization: `default((int, string)).Item2` is simply `default(string)`
                return new BoundDefaultExpression(syntax, tupleField.Type);
            }

            if (!TypeSymbol.Equals(underlyingField.ContainingType, currentLinkType, TypeCompareKind.ConsiderEverything2))
            {
                WellKnownMember wellKnownTupleRest = NamedTypeSymbol.GetTupleTypeMember(NamedTypeSymbol.ValueTupleRestPosition, NamedTypeSymbol.ValueTupleRestPosition);
                var tupleRestField = (FieldSymbol?)NamedTypeSymbol.GetWellKnownMemberInType(currentLinkType.OriginalDefinition, wellKnownTupleRest, _diagnostics, syntax);

                if (tupleRestField is null)
                {
                    // error tolerance for cases when Rest is missing
                    return _factory.BadExpression(tupleField.Type);
                }

                // make nested field accesses to Rest
                do
                {
                    FieldSymbol nestedFieldSymbol = tupleRestField.AsMember(currentLinkType);
                    rewrittenReceiver = _factory.Field(rewrittenReceiver, nestedFieldSymbol);

                    currentLinkType = (NamedTypeSymbol)currentLinkType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[NamedTypeSymbol.ValueTupleRestPosition - 1].Type;
                }
                while (!TypeSymbol.Equals(underlyingField.ContainingType, currentLinkType, TypeCompareKind.ConsiderEverything2));
            }

            // make a field access for the most local access
            return _factory.Field(rewrittenReceiver, underlyingField);
        }

        private BoundExpression MakeTupleFieldAccessAndReportUseSiteDiagnostics(BoundExpression tuple, SyntaxNode syntax, FieldSymbol field)
        {
            // Use default field rather than implicitly named fields since
            // fields from inferred names are not usable in C# 7.0.
            field = field.CorrespondingTupleField ?? field;

            UseSiteInfo<AssemblySymbol> useSiteInfo = field.GetUseSiteInfo();
            if (useSiteInfo.DiagnosticInfo?.Severity != DiagnosticSeverity.Error)
            {
                useSiteInfo = useSiteInfo.AdjustDiagnosticInfo(null);
            }

            _diagnostics.Add(useSiteInfo, syntax.Location);

            return MakeTupleFieldAccess(syntax, field, tuple);
        }
    }
}
