// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Metadata.Edm
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Data.Entity.Resources;
    using System.Data.Entity.Utilities;

    // <summary>
    // Class representing a collection of member objects
    // </summary>
    internal sealed class MemberCollection : MetadataCollection<EdmMember>
    {
        // This way this collection works is that it has storage for members on the current type and access to
        // members in the base types. As of that requirement, MemberCollection has a reference back to the declaring
        // type that owns this collection. Whenever MemberCollection is asked to do a look by name, it looks at the
        // current collection, if it doesn't find it, then it ask for it from the declaring type's base type's
        // MemberCollection. Because of this order, members in derived types hide members in the base type if they
        // have the same name. For look up by index, base type members have lower index then current type's members.
        // Add/Update/Remove operations on this collection is only allowed for members owned by this MemberCollection
        // and not allowed for members owned by MemberCollections in the base types. For example, if the caller tries
        // to remove a member by ordinal which is within the base type's member ordinal range, it throws an exception.
        // Hence, base type members are in a sense "readonly" to this MemberCollection. When enumerating all the
        // members, the enumeration starts from members in the root type in the inheritance chain. With this special
        // enumeration requirement, we have a specialized enumerator class for this MemberCollection. See the
        // Enumerator class for details on how it works.

        // <summary>
        // Default constructor for constructing an empty collection
        // </summary>
        // <param name="declaringType"> The type that has this member collection </param>
        // <exception cref="System.ArgumentNullException">Thrown if the declaring type is null</exception>
        public MemberCollection(StructuralType declaringType)
            : this(declaringType, null)
        {
        }

        // <summary>
        // The constructor for constructing the collection with the given items
        // </summary>
        // <param name="declaringType"> The type that has this member collection </param>
        // <param name="items"> The items to populate the collection </param>
        // <exception cref="System.ArgumentNullException">Thrown if the declaring type is null</exception>
        public MemberCollection(StructuralType declaringType, IEnumerable<EdmMember> items)
            : base(items)
        {
            DebugCheck.NotNull(declaringType);
            _declaringType = declaringType;
        }

        private readonly StructuralType _declaringType;

        // <summary>
        // Returns the collection as a readonly collection
        // </summary>
        public override ReadOnlyCollection<EdmMember> AsReadOnly
        {
            get { return new ReadOnlyCollection<EdmMember>(this); }
        }

        // <summary>
        // Gets the count on the number of items in the collection
        // </summary>
        public override int Count
        {
            get { return GetBaseTypeMemberCount() + base.Count; }
        }

        // <summary>
        // Gets or sets an item from the collection with the given index
        // </summary>
        // <param name="index"> The index to search for </param>
        // <returns> An item from the collection </returns>
        // <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is out of the range for the Collection</exception>
        public override EdmMember this[int index]
        {
            get
            {
                var relativeIndex = GetRelativeIndex(index);
                if (relativeIndex < 0)
                {
                    // This means baseTypeMemberCount must be non-zero, so we can safely cast the base type to StructuralType
                    return ((StructuralType)_declaringType.BaseType).Members[index];
                }

                return base[relativeIndex];
            }
            set
            {
                var relativeIndex = GetRelativeIndex(index);
                if (relativeIndex < 0)
                {
                    // This means baseTypeMemberCount must be non-zero, so we can safely cast the base type to StructuralType
                    ((StructuralType)_declaringType.BaseType).Members.Source[index] = value;
                }
                else
                {
                    base[relativeIndex] = value;    
                }                
            }
        }

        // <summary>
        // Adds an item to the collection
        // </summary>
        // <param name="member"> The item to add to the list </param>
        // <exception cref="System.ArgumentNullException">Thrown if member argument is null</exception>
        // <exception cref="System.InvalidOperationException">Thrown if the member passed in or the collection itself instance is in ReadOnly state</exception>
        // <exception cref="System.ArgumentException">Thrown if the member that is being added already belongs to another MemberCollection</exception>
        // <exception cref="System.ArgumentException">Thrown if the MemberCollection already contains a member with the same identity</exception>
        public override void Add(EdmMember member)
        {
            // Make sure the member is valid for the add operation. 
            ValidateMemberForAdd(member, "member");

            base.Add(member);

            // Fix up the declaring type
            member.ChangeDeclaringTypeWithoutCollectionFixup(_declaringType);
        }

        // <summary>
        // Determines if this collection contains an item of the given identity
        // </summary>
        // <param name="identity"> The identity of the item to check for </param>
        // <returns> True if the collection contains the item with the given identity </returns>
        public override bool ContainsIdentity(string identity)
        {
            if (base.ContainsIdentity(identity))
            {
                return true;
            }

            // The item is not in this collection, check the base type member collection
            var baseType = _declaringType.BaseType;
            if (baseType != null
                && ((StructuralType)baseType).Members.Contains(identity))
            {
                return true;
            }

            return false;
        }

        // <summary>
        // Find the index of an item
        // </summary>
        // <param name="item"> The item whose index is to be looked for </param>
        // <returns> The index of the found item, -1 if not found </returns>
        public override int IndexOf(EdmMember item)
        {
            // Try to get it from this collection, if found, then the relative index needs to be added with the number
            // of members in the base type to get the absolute index
            var relativeIndex = base.IndexOf(item);
            if (relativeIndex != -1)
            {
                return relativeIndex + GetBaseTypeMemberCount();
            }

            // Try to find it in the base type
            var baseType = _declaringType.BaseType as StructuralType;
            if (baseType != null)
            {
                return baseType.Members.IndexOf(item);
            }

            return -1;
        }

        // <summary>
        // Copies the items in this collection to an array
        // </summary>
        // <param name="array"> The array to copy to </param>
        // <param name="arrayIndex"> The index in the array at which to start the copy </param>
        // <exception cref="System.ArgumentNullException">Thrown if array argument is null</exception>
        // <exception cref="System.ArgumentOutOfRangeException">Thrown if the arrayIndex is less than zero</exception>
        // <exception cref="System.ArgumentException">Thrown if the array argument passed in with respect to the arrayIndex passed in not big enough to hold the MemberCollection being copied</exception>
        public override void CopyTo(EdmMember[] array, int arrayIndex)
        {
            // Check on the array index
            if (arrayIndex < 0)
            {
                throw new ArgumentOutOfRangeException("arrayIndex");
            }

            // Check if the array together with the array index has enough room to copy
            var baseTypeMemberCount = GetBaseTypeMemberCount();
            if (base.Count + baseTypeMemberCount
                > array.Length - arrayIndex)
            {
                throw new ArgumentOutOfRangeException("arrayIndex");
            }

            // If the base type has any members, copy those first
            if (baseTypeMemberCount > 0)
            {
                ((StructuralType)_declaringType.BaseType).Members.CopyTo(array, arrayIndex);
            }

            base.CopyTo(array, arrayIndex + baseTypeMemberCount);
        }

        // <summary>
        // Gets an item from the collection with the given identity
        // </summary>
        // <param name="identity"> The identity of the item to search for </param>
        // <param name="ignoreCase"> Whether case is ignore in the search </param>
        // <param name="item"> An item from the collection, null if the item is not found </param>
        // <returns> True an item is retrieved </returns>
        // <exception cref="System.ArgumentNullException">if identity argument is null</exception>
        public override bool TryGetValue(string identity, bool ignoreCase, out EdmMember item)
        {
            // See if it's in this collection
            if (!base.TryGetValue(identity, ignoreCase, out item))
            {
                // Now go to the parent type to find it
                var baseType = _declaringType.BaseType;
                if (baseType != null)
                {
                    ((StructuralType)baseType).Members.TryGetValue(identity, ignoreCase, out item);
                }
            }

            return item != null;
        }

        // <summary>
        // Get the declared only members of a particular type
        // </summary>
        internal ReadOnlyMetadataCollection<T> GetDeclaredOnlyMembers<T>() where T : EdmMember
        {
            var newCollection = new MetadataCollection<T>();
            for (var i = 0; i < base.Count; i++)
            {
                var member = base[i] as T;
                if (member != null)
                {
                    newCollection.Add(member);
                }
            }

            return new ReadOnlyMetadataCollection<T>(newCollection);
        }

        // <summary>
        // Get the number of members the base type has.  If the base type is not a structural type or has no
        // members, it returns 0
        // </summary>
        // <returns> The number of members in the base type </returns>
        private int GetBaseTypeMemberCount()
        {
            // The count of members is what in this collection plus base type's member collection
            var baseType = _declaringType.BaseType as StructuralType;
            if (baseType != null)
            {
                return baseType.Members.Count;
            }

            return 0;
        }

        // <summary>
        // Gets the index relative to this collection for the given index.  For an index to really refers to something in
        // the base type, the return value is negative relative to this collection.  For an index refers to something in this
        // collection, the return value is positive.  In both cases, it's simply (index) - (base type member count)
        // </summary>
        // <returns> The relative index </returns>
        private int GetRelativeIndex(int index)
        {
            var baseTypeMemberCount = GetBaseTypeMemberCount();
            var thisTypeMemberCount = base.Count;

            // Check if the index is in range
            if (index < 0
                || index >= baseTypeMemberCount + thisTypeMemberCount)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            return index - baseTypeMemberCount;
        }

        private void ValidateMemberForAdd(EdmMember member, string argumentName)
        {
            // Check to make sure the given member is not associated with another type
            Check.NotNull(member, argumentName);

            // Validate the item with the declaring type. 
            _declaringType.ValidateMemberForAdd(member);
        }
    }
}
