// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Metadata;
using RuntimeTypeCache = System.RuntimeType.RuntimeTypeCache;

namespace System.Reflection
{
    internal sealed unsafe class RuntimeEventInfo : EventInfo
    {
        #region Private Data Members
        private readonly int m_token;
        private readonly EventAttributes m_flags;
        private string? m_name;
        private readonly void* m_utf8name;
        private readonly RuntimeTypeCache m_reflectedTypeCache;
        private readonly RuntimeMethodInfo? m_addMethod;
        private readonly RuntimeMethodInfo? m_removeMethod;
        private readonly RuntimeMethodInfo? m_raiseMethod;
        private readonly MethodInfo[]? m_otherMethod;
        private readonly RuntimeType m_declaringType;
        private readonly BindingFlags m_bindingFlags;
        #endregion

        #region Constructor
        internal RuntimeEventInfo(int tkEvent, RuntimeType declaredType, RuntimeTypeCache reflectedTypeCache, out bool isPrivate)
        {
            Debug.Assert(declaredType != null);
            Debug.Assert(reflectedTypeCache != null);
            Debug.Assert(!reflectedTypeCache.IsGlobal);

            MetadataImport scope = declaredType.GetRuntimeModule().MetadataImport;

            m_token = tkEvent;
            m_reflectedTypeCache = reflectedTypeCache;
            m_declaringType = declaredType;


            RuntimeType reflectedType = reflectedTypeCache.GetRuntimeType();

            scope.GetEventProps(tkEvent, out m_utf8name, out m_flags);

            Associates.AssignAssociates(scope, tkEvent, declaredType, reflectedType,
                out m_addMethod, out m_removeMethod, out m_raiseMethod,
                out _, out _, out m_otherMethod, out isPrivate, out m_bindingFlags);
        }
        #endregion

        #region Internal Members
        internal override bool CacheEquals(object? o)
        {
            return
                o is RuntimeEventInfo m &&
                m.m_token == m_token &&
                ReferenceEquals(m_declaringType, m.m_declaringType);
        }

        internal BindingFlags BindingFlags => m_bindingFlags;
        #endregion

        #region Object Overrides
        public override string ToString()
        {
            if (m_addMethod == null || m_addMethod.GetParametersNoCopy().Length == 0)
                throw new InvalidOperationException(SR.InvalidOperation_NoPublicAddMethod);

            return m_addMethod.GetParametersNoCopy()[0].ParameterType.FormatTypeName() + " " + Name;
        }

        public override bool Equals(object? obj) =>
            ReferenceEquals(this, obj) ||
            (MetadataUpdater.IsSupported &&
                obj is RuntimeEventInfo ei &&
                ei.m_token == m_token &&
                ReferenceEquals(ei.m_declaringType, m_declaringType) &&
                ReferenceEquals(ei.m_reflectedTypeCache.GetRuntimeType(), m_reflectedTypeCache.GetRuntimeType()));

        public override int GetHashCode() =>
            HashCode.Combine(m_token.GetHashCode(), m_declaringType.GetUnderlyingNativeHandle().GetHashCode());
        #endregion

        #region ICustomAttributeProvider
        public override object[] GetCustomAttributes(bool inherit)
        {
            return CustomAttribute.GetCustomAttributes(this, (typeof(object) as RuntimeType)!);
        }

        public override object[] GetCustomAttributes(Type attributeType, bool inherit)
        {
            ArgumentNullException.ThrowIfNull(attributeType);

            if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
                throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));

            return CustomAttribute.GetCustomAttributes(this, attributeRuntimeType);
        }

        public override bool IsDefined(Type attributeType, bool inherit)
        {
            ArgumentNullException.ThrowIfNull(attributeType);

            if (attributeType.UnderlyingSystemType is not RuntimeType attributeRuntimeType)
                throw new ArgumentException(SR.Arg_MustBeType, nameof(attributeType));

            return CustomAttribute.IsDefined(this, attributeRuntimeType);
        }

        public override IList<CustomAttributeData> GetCustomAttributesData()
        {
            return RuntimeCustomAttributeData.GetCustomAttributesInternal(this);
        }
        #endregion

        #region MemberInfo Overrides
        public override MemberTypes MemberType => MemberTypes.Event;
        public override string Name => m_name ??= new MdUtf8String(m_utf8name).ToString();
        public override Type? DeclaringType => m_declaringType;
        public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => HasSameMetadataDefinitionAsCore<RuntimeEventInfo>(other);
        public override Type? ReflectedType => ReflectedTypeInternal;

        private RuntimeType ReflectedTypeInternal => m_reflectedTypeCache.GetRuntimeType();

        public override int MetadataToken => m_token;
        public override Module Module => GetRuntimeModule();
        internal RuntimeModule GetRuntimeModule() { return m_declaringType.GetRuntimeModule(); }
        #endregion

        #region EventInfo Overrides
        public override MethodInfo[] GetOtherMethods(bool nonPublic)
        {
            List<MethodInfo> ret = new List<MethodInfo>();

            if (m_otherMethod is null)
                return Array.Empty<MethodInfo>();

            for (int i = 0; i < m_otherMethod.Length; i++)
            {
                if (Associates.IncludeAccessor((MethodInfo)m_otherMethod[i], nonPublic))
                    ret.Add(m_otherMethod[i]);
            }

            return ret.ToArray();
        }

        public override MethodInfo? GetAddMethod(bool nonPublic)
        {
            if (!Associates.IncludeAccessor(m_addMethod, nonPublic))
                return null;

            return m_addMethod;
        }

        public override MethodInfo? GetRemoveMethod(bool nonPublic)
        {
            if (!Associates.IncludeAccessor(m_removeMethod, nonPublic))
                return null;

            return m_removeMethod;
        }

        public override MethodInfo? GetRaiseMethod(bool nonPublic)
        {
            if (!Associates.IncludeAccessor(m_raiseMethod, nonPublic))
                return null;

            return m_raiseMethod;
        }

        public override EventAttributes Attributes => m_flags;
        #endregion
    }
}
