// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic.Utils;
using System.Reflection;

namespace System.Linq.Expressions
{
    /// <summary>
    /// Represents initializing the elements of a collection member of a newly created object.
    /// </summary>
    public sealed class MemberListBinding : MemberBinding
    {
        internal MemberListBinding(MemberInfo member, ReadOnlyCollection<ElementInit> initializers)
#pragma warning disable 618
            : base(MemberBindingType.ListBinding, member)
        {
#pragma warning restore 618
            Initializers = initializers;
        }

        /// <summary>
        /// Gets the element initializers for initializing a collection member of a newly created object.
        /// </summary>
        public ReadOnlyCollection<ElementInit> Initializers { get; }

        /// <summary>
        /// Creates a new expression that is like this one, but using the
        /// supplied children. If all of the children are the same, it will
        /// return this expression.
        /// </summary>
        /// <param name="initializers">The <see cref="Initializers"/> property of the result.</param>
        /// <returns>This expression if no children changed, or an expression with the updated children.</returns>
        public MemberListBinding Update(IEnumerable<ElementInit> initializers)
        {
            if (initializers != null)
            {
                if (ExpressionUtils.SameElements(ref initializers!, Initializers))
                {
                    return this;
                }
            }

            return Expression.ListBind(Member, initializers!);
        }

        internal override void ValidateAsDefinedHere(int index)
        {
        }
    }

    public partial class Expression
    {
        /// <summary>Creates a <see cref="MemberListBinding"/> where the member is a field or property.</summary>
        /// <returns>A <see cref="MemberListBinding"/> that has the <see cref="MemberBinding.BindingType"/> property equal to <see cref="MemberBindingType.ListBinding"/> and the <see cref="MemberBinding.Member"/> and <see cref="MemberListBinding.Initializers"/> properties set to the specified values.</returns>
        /// <param name="member">A <see cref="MemberInfo"/> that represents a field or property to set the <see cref="MemberBinding.Member"/> property equal to.</param>
        /// <param name="initializers">An array of <see cref="System.Linq.Expressions.ElementInit"/> objects to use to populate the <see cref="MemberListBinding.Initializers"/> collection.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="member"/> is null. -or-One or more elements of <paramref name="initializers"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="member"/> does not represent a field or property.-or-The <see cref="FieldInfo.FieldType"/> or <see cref="PropertyInfo.PropertyType"/> of the field or property that <paramref name="member"/> represents does not implement <see cref="Collections.IEnumerable"/>.</exception>
        public static MemberListBinding ListBind(MemberInfo member, params ElementInit[] initializers)
        {
            return ListBind(member, (IEnumerable<ElementInit>)initializers);
        }

        /// <summary>Creates a <see cref="MemberListBinding"/> where the member is a field or property.</summary>
        /// <returns>A <see cref="MemberListBinding"/> that has the <see cref="MemberBinding.BindingType"/> property equal to <see cref="MemberBindingType.ListBinding"/> and the <see cref="MemberBinding.Member"/> and <see cref="MemberListBinding.Initializers"/> properties set to the specified values.</returns>
        /// <param name="member">A <see cref="MemberInfo"/> that represents a field or property to set the <see cref="MemberBinding.Member"/> property equal to.</param>
        /// <param name="initializers">An <see cref="IEnumerable{T}"/> that contains <see cref="System.Linq.Expressions.ElementInit"/> objects to use to populate the <see cref="MemberListBinding.Initializers"/> collection.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="member"/> is null. -or-One or more elements of <paramref name="initializers"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="member"/> does not represent a field or property.-or-The <see cref="FieldInfo.FieldType"/> or <see cref="PropertyInfo.PropertyType"/> of the field or property that <paramref name="member"/> represents does not implement <see cref="Collections.IEnumerable"/>.</exception>
        public static MemberListBinding ListBind(MemberInfo member, IEnumerable<ElementInit> initializers)
        {
            ArgumentNullException.ThrowIfNull(member);
            ArgumentNullException.ThrowIfNull(initializers);
            Type memberType;
            ValidateGettableFieldOrPropertyMember(member, out memberType);
            ReadOnlyCollection<ElementInit> initList = initializers.ToReadOnly();
            ValidateListInitArgs(memberType, initList, nameof(member));
            return new MemberListBinding(member, initList);
        }

        /// <summary>Creates a <see cref="MemberListBinding"/> object based on a specified property accessor method.</summary>
        /// <returns>A <see cref="MemberListBinding"/> that has the <see cref="MemberBinding.BindingType"/> property equal to <see cref="MemberBindingType.ListBinding"/>, the <see cref="MemberBinding.Member"/> property set to the <see cref="MemberInfo"/> that represents the property accessed in <paramref name="propertyAccessor"/>, and <see cref="MemberListBinding.Initializers"/> populated with the elements of <paramref name="initializers"/>.</returns>
        /// <param name="propertyAccessor">A <see cref="MethodInfo"/> that represents a property accessor method.</param>
        /// <param name="initializers">An array of <see cref="Expressions.ElementInit"/> objects to use to populate the <see cref="MemberListBinding.Initializers"/> collection.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="propertyAccessor"/> is null. -or-One or more elements of <paramref name="initializers"/> is null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="propertyAccessor"/> does not represent a property accessor method.-or-The <see cref="PropertyInfo.PropertyType"/> of the property that the method represented by <paramref name="propertyAccessor"/> accesses does not implement <see cref="IEnumerable"/>.</exception>
        [RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
        public static MemberListBinding ListBind(MethodInfo propertyAccessor, params ElementInit[] initializers)
        {
            return ListBind(propertyAccessor, (IEnumerable<ElementInit>)initializers);
        }

        /// <summary>Creates a <see cref="MemberListBinding"/> based on a specified property accessor method.</summary>
        /// <returns>A <see cref="MemberListBinding"/> that has the <see cref="MemberBinding.BindingType"/> property equal to <see cref="MemberBindingType.ListBinding"/>, the <see cref="MemberBinding.Member"/> property set to the <see cref="MemberInfo"/> that represents the property accessed in <paramref name="propertyAccessor"/>, and <see cref="MemberListBinding.Initializers"/> populated with the elements of <paramref name="initializers"/>.</returns>
        /// <param name="propertyAccessor">A <see cref="MethodInfo"/> that represents a property accessor method.</param>
        /// <param name="initializers">An <see cref="IEnumerable{T}"/> that contains <see cref="Expressions.ElementInit"/> objects to use to populate the <see cref="MemberListBinding.Initializers"/> collection.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="propertyAccessor"/> is null. -or-One or more elements of <paramref name="initializers"/> are null.</exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="propertyAccessor"/> does not represent a property accessor method.-or-The <see cref="PropertyInfo.PropertyType"/> of the property that the method represented by <paramref name="propertyAccessor"/> accesses does not implement <see cref="IEnumerable"/>.</exception>
        [RequiresUnreferencedCode(PropertyFromAccessorRequiresUnreferencedCode)]
        public static MemberListBinding ListBind(MethodInfo propertyAccessor, IEnumerable<ElementInit> initializers)
        {
            ArgumentNullException.ThrowIfNull(propertyAccessor);
            ArgumentNullException.ThrowIfNull(initializers);
            return ListBind(GetProperty(propertyAccessor, nameof(propertyAccessor)), initializers);
        }

        private static void ValidateListInitArgs(Type listType, ReadOnlyCollection<ElementInit> initializers, string listTypeParamName)
        {
            if (!typeof(IEnumerable).IsAssignableFrom(listType))
            {
                throw Error.TypeNotIEnumerable(listType, listTypeParamName);
            }
            for (int i = 0, n = initializers.Count; i < n; i++)
            {
                ElementInit element = initializers[i];
                ContractUtils.RequiresNotNull(element, nameof(initializers), i);
                ValidateCallInstanceType(listType, element.AddMethod);
            }
        }
    }
}
