// 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 System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation;
using Microsoft.VisualStudio.Debugger.Metadata;
using Roslyn.Utilities;
using Type = Microsoft.VisualStudio.Debugger.Metadata.Type;

namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
    [Flags]
    internal enum DeclarationInfo : byte
    {
        /// <summary>
        /// A declaration with this name has not been encountered.
        /// </summary>
        None = 0,
        /// <summary>
        /// This member is defined on the declared type or one of its base classes.
        /// </summary>
        FromDeclaredTypeOrBase = 0,
        /// <summary>
        /// This member is defined on a type that inherits from the declared type (is more derived).
        /// </summary>
        FromSubTypeOfDeclaredType = 1,
        /// <summary>
        /// This member should be hidden (under "Non-Public members" node), because Just My Code is on and
        /// no symbols have been loaded for the declaring type's module.
        /// </summary>
        HideNonPublic = 1 << 2,
        /// <summary>
        /// More than one non-virtual member with this name exists in the type hierarchy.
        /// The ResultProvider should include the declaring type of this member in the member name to disambiguate.
        /// </summary>
        IncludeTypeInMemberName = 1 << 3,
        /// <summary>
        /// The full name for this member access expression will require a cast to the declaring type.
        /// </summary>
        RequiresExplicitCast = 1 << 4,
    }

    internal static class DeclarationInfoExtensions
    {
        internal static bool IsSet(this DeclarationInfo info, DeclarationInfo value)
        {
            return (info & value) == value;
        }
    }

    internal readonly struct MemberAndDeclarationInfo
    {
        public static readonly IComparer<MemberAndDeclarationInfo> Comparer = new MemberNameComparer();

        private readonly MemberInfo _member;

        public readonly DkmClrDebuggerBrowsableAttributeState? BrowsableState;
        public readonly bool HideNonPublic;
        public readonly bool IncludeTypeInMemberName;
        public readonly bool RequiresExplicitCast;
        public readonly bool CanFavorite;
        public readonly bool IsFavorite;

        /// <summary>
        /// Exists to correctly order fields with the same name from different types in the inheritance hierarchy.
        /// </summary>
        private readonly int _inheritanceLevel;

        public MemberAndDeclarationInfo(MemberInfo member, DkmClrDebuggerBrowsableAttributeState? browsableState, DeclarationInfo info, int inheritanceLevel, bool canFavorite, bool isFavorite)
        {
            Debug.Assert(member != null);

            _member = member;
            this.BrowsableState = browsableState;
            this.HideNonPublic = info.IsSet(DeclarationInfo.HideNonPublic);
            this.IncludeTypeInMemberName = info.IsSet(DeclarationInfo.IncludeTypeInMemberName);
            this.RequiresExplicitCast = info.IsSet(DeclarationInfo.RequiresExplicitCast);
            this.CanFavorite = canFavorite && SupportsCanFavorite(member, info);
            this.IsFavorite = isFavorite;

            _inheritanceLevel = inheritanceLevel;
        }

        public Type DeclaringType
        {
            get
            {
                return _member.DeclaringType;
            }
        }

        public bool IsPublic
        {
            get
            {
                return _member.IsPublic();
            }
        }

        public bool IsStatic
        {
            get
            {
                return IsMemberStatic(_member);
            }
        }

        public MemberTypes MemberType
        {
            get
            {
                return _member.MemberType;
            }
        }

        public string Name
        {
            get
            {
                return _member.Name;
            }
        }

        public Type Type
        {
            get
            {
                return GetMemberType(_member);
            }
        }

        public Type OriginalDefinitionType
        {
            get
            {
                return GetMemberType(_member.GetOriginalDefinition());
            }
        }

        private static Type GetMemberType(MemberInfo member)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)member).FieldType;
                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;
                default:
                    throw ExceptionUtilities.UnexpectedValue(member.MemberType);
            }
        }

        private static bool SupportsCanFavorite(MemberInfo member, DeclarationInfo info)
        {
            if (IsMemberStatic(member))
            {
                return false;
            }

            Type memberType = GetMemberType(member);

            if (memberType.IsByRef || memberType.IsPointer)
            {
                return false;
            }

            if (member.Name.Contains("."))
            {
                return false;
            }

            if (info.IsSet(DeclarationInfo.IncludeTypeInMemberName))
            {
                return false;
            }

            return true;
        }

        private static bool IsMemberStatic(MemberInfo member)
        {
            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)member).IsStatic;
                case MemberTypes.Property:
                    return ((PropertyInfo)member).GetGetMethod(nonPublic: true).IsStatic;
                default:
                    throw ExceptionUtilities.UnexpectedValue(member.MemberType);
            }
        }

        public DkmClrCustomTypeInfo TypeInfo
        {
            get
            {
                switch (_member.MemberType)
                {
                    case MemberTypes.Field:
                    case MemberTypes.Property:
                        return _member.GetCustomAttributesData().GetCustomTypeInfo();
                    default:
                        // If we ever see a method, we'll have to use ReturnTypeCustomAttributes.
                        throw ExceptionUtilities.UnexpectedValue(_member.MemberType);
                }
            }
        }

        public Type GetExplicitlyImplementedInterface(out string memberName)
        {
            memberName = _member.Name;

            // We only display fields and properties and fields never implement interface members.
            if (_member.MemberType == MemberTypes.Property)
            {
                // A dot is neither necessary nor sufficient for determining whether a member explicitly
                // implements an interface member, but it does characterize the set of members we're
                // interested in displaying differently.  For example, if the property is from VB, it will
                // be an explicit interface implementation, but will not have a dot.
                var dotPos = memberName.LastIndexOf('.');
                if (dotPos >= 0)
                {
                    var property = (PropertyInfo)_member;
                    var accessors = property.GetAccessors(nonPublic: true);
                    Debug.Assert(accessors.Length > 0);

                    // We'll just pick the first interface we find since we don't have a good way
                    // to display more than one.
                    foreach (var accessor in accessors)
                    {
                        foreach (var interfaceAccessor in accessor.GetExplicitInterfacesImplemented())
                        {
                            memberName = memberName.Substring(dotPos + 1);
                            return interfaceAccessor.DeclaringType;
                        }
                    }
                }
            }

            return null;
        }

        private sealed class MemberNameComparer : IComparer<MemberAndDeclarationInfo>
        {
            public int Compare(MemberAndDeclarationInfo x, MemberAndDeclarationInfo y)
            {
                var comp = string.Compare(x.Name, y.Name, StringComparison.Ordinal);
                return comp != 0 ? comp : (y._inheritanceLevel - x._inheritanceLevel);
            }
        }
    }
}
