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

namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator
{
    internal enum DisplayClassVariableKind
    {
        Local,
        Parameter,
        This,
    }

    /// <summary>
    /// A field in a display class that represents a captured
    /// variable: either a local, a parameter, or "this".
    /// </summary>
    [DebuggerDisplay("{GetDebuggerDisplay(), nq}")]
    internal sealed class DisplayClassVariable
    {
        internal readonly string Name;
        internal readonly DisplayClassVariableKind Kind;
        internal readonly DisplayClassInstance DisplayClassInstance;
        internal readonly ConsList<FieldSymbol> DisplayClassFields;

        internal DisplayClassVariable(string name, DisplayClassVariableKind kind, DisplayClassInstance displayClassInstance, ConsList<FieldSymbol> displayClassFields)
        {
            Debug.Assert(displayClassFields.Any());

            this.Name = name;
            this.Kind = kind;
            this.DisplayClassInstance = displayClassInstance;
            this.DisplayClassFields = displayClassFields;

            // Verify all type parameters are substituted.
            Debug.Assert(this.ContainingSymbol.IsContainingSymbolOfAllTypeParameters(this.Type));
        }

        internal TypeSymbol Type
        {
            get { return this.DisplayClassFields.Head.Type; }
        }

        internal Symbol ContainingSymbol
        {
            get { return this.DisplayClassInstance.ContainingSymbol; }
        }

        internal DisplayClassVariable ToOtherMethod(MethodSymbol method, TypeMap typeMap)
        {
            var otherInstance = this.DisplayClassInstance.ToOtherMethod(method, typeMap);
            return SubstituteFields(otherInstance, typeMap);
        }

        internal BoundExpression ToBoundExpression(SyntaxNode syntax)
        {
            var expr = this.DisplayClassInstance.ToBoundExpression(syntax);
            var fields = ArrayBuilder<FieldSymbol>.GetInstance();
            fields.AddRange(this.DisplayClassFields);
            fields.ReverseContents();
            foreach (var field in fields)
            {
                expr = new BoundFieldAccess(syntax, expr, field, constantValueOpt: null) { WasCompilerGenerated = true };
            }
            fields.Free();
            return expr;
        }

        internal DisplayClassVariable SubstituteFields(DisplayClassInstance otherInstance, TypeMap typeMap)
        {
            var otherFields = SubstituteFields(this.DisplayClassFields, typeMap);
            return new DisplayClassVariable(this.Name, this.Kind, otherInstance, otherFields);
        }

        private string GetDebuggerDisplay()
        {
            return DisplayClassInstance.GetDebuggerDisplay(DisplayClassFields);
        }

        private static ConsList<FieldSymbol> SubstituteFields(ConsList<FieldSymbol> fields, TypeMap typeMap)
        {
            if (!fields.Any())
            {
                return ConsList<FieldSymbol>.Empty;
            }

            var head = SubstituteField(fields.Head, typeMap);
            var tail = SubstituteFields(fields.Tail, typeMap);
            return tail.Prepend(head);
        }

        private static FieldSymbol SubstituteField(FieldSymbol field, TypeMap typeMap)
        {
            Debug.Assert(!field.IsStatic);
            Debug.Assert(!field.IsReadOnly || GeneratedNameParser.GetKind(field.Name) == GeneratedNameKind.AnonymousTypeField);
            // CONSIDER: Instead of digging fields out of the unsubstituted type and then performing substitution
            // on each one individually, we could dig fields out of the substituted type.
            return new EEDisplayClassFieldSymbol(typeMap.SubstituteNamedType(field.ContainingType), field.Name, typeMap.SubstituteType(field.TypeWithAnnotations));
        }

        private sealed class EEDisplayClassFieldSymbol : FieldSymbol
        {
            private readonly NamedTypeSymbol _container;
            private readonly string _name;
            private readonly TypeWithAnnotations _type;

            internal EEDisplayClassFieldSymbol(NamedTypeSymbol container, string name, TypeWithAnnotations type)
            {
                _container = container;
                _name = name;
                _type = type;
            }

            public override Symbol AssociatedSymbol
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            public override Symbol ContainingSymbol
            {
                get { return _container; }
            }

            public override Accessibility DeclaredAccessibility
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            public override bool IsConst
            {
                get { return false; }
            }

            public override bool IsReadOnly
            {
                get { return false; }
            }

            public override bool IsStatic
            {
                get { return false; }
            }

            public override bool IsVolatile
            {
                get { return false; }
            }

            internal override bool IsRequired => throw ExceptionUtilities.Unreachable();

            public override FlowAnalysisAnnotations FlowAnalysisAnnotations
            {
                get { return FlowAnalysisAnnotations.None; }
            }

            public override ImmutableArray<Location> Locations
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            public override string Name
            {
                get { return _name; }
            }

            internal override bool HasRuntimeSpecialName
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override bool HasSpecialName
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override bool IsNotSerialized
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override MarshalPseudoCustomAttributeData MarshallingInformation
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override ObsoleteAttributeData ObsoleteAttributeData
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override int? TypeLayoutOffset
            {
                get { throw ExceptionUtilities.Unreachable(); }
            }

            internal override ConstantValue GetConstantValue(ConstantFieldsInProgress inProgress, bool earlyDecodingWellKnownAttributes)
            {
                throw ExceptionUtilities.Unreachable();
            }

            internal override TypeWithAnnotations GetFieldType(ConsList<FieldSymbol> fieldsBeingBound)
            {
                return _type;
            }

            public override RefKind RefKind => RefKind.None;

            public override ImmutableArray<CustomModifier> RefCustomModifiers => ImmutableArray<CustomModifier>.Empty;
        }
    }
}
