//---------------------------------------------------------------------
// <copyright file="RowType.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Data.Common;
using System.Text;
using System.Data.Objects.ELinq;
using System.Threading;
using System.Diagnostics;

namespace System.Data.Metadata.Edm
{
    /// <summary>
    /// Represents the Edm Row Type
    /// </summary>
    public sealed class RowType : StructuralType
    {
        private ReadOnlyMetadataCollection<EdmProperty> _properties;
        private readonly InitializerMetadata _initializerMetadata;
    
        #region Constructors
        /// <summary>
        /// Initializes a new instance of RowType class with the given list of members
        /// </summary>
        /// <param name="properties">properties for this row type</param>
        /// <exception cref="System.ArgumentException">Thrown if any individual property in the passed in properties argument is null</exception>
        internal RowType(IEnumerable<EdmProperty> properties)
            : this(properties, null)
        {
        }

        /// <summary>
        /// Initializes a RowType with the given members and initializer metadata 
        /// </summary>
        internal RowType(IEnumerable<EdmProperty> properties, InitializerMetadata initializerMetadata)
            : base(GetRowTypeIdentityFromProperties(CheckProperties(properties), initializerMetadata), EdmConstants.TransientNamespace, (DataSpace)(-1))
        {
            // Initialize the properties. 
            if (null != properties)
            {
                foreach (EdmProperty property in properties)
                {
                    this.AddProperty(property);
                }
            }

            _initializerMetadata = initializerMetadata;

            // Row types are immutable, so now that we're done initializing, set it
            // to be read-only.
            SetReadOnly();
        }


        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets LINQ initializer Metadata for this row type. If there is no associated
        /// initializer type, value is null.
        /// </summary>
        internal InitializerMetadata InitializerMetadata
        {
            get { return _initializerMetadata; }
        }

        /// <summary>
        /// Returns the kind of the type
        /// </summary>
        public override BuiltInTypeKind BuiltInTypeKind { get { return BuiltInTypeKind.RowType; } }

        /// <summary>
        /// Returns the list of properties for this row type
        /// </summary>
        /// <summary>
        /// Returns just the properties from the collection
        /// of members on this type
        /// </summary>
        public ReadOnlyMetadataCollection<EdmProperty> Properties
        {
            get
            {
                Debug.Assert(IsReadOnly, "this is a wrapper around this.Members, don't call it during metadata loading, only call it after the metadata is set to readonly");
                if (null == _properties)
                {
                    Interlocked.CompareExchange(ref _properties,
                        new FilteredReadOnlyMetadataCollection<EdmProperty, EdmMember>(
                            this.Members, Helper.IsEdmProperty), null);
                }
                return _properties;
            }
        }


        /// <summary>
        /// Adds a property
        /// </summary>
        /// <param name="property">The property to add</param>
        private void AddProperty(EdmProperty property)
        {
            EntityUtil.GenericCheckArgumentNull(property, "property");
            AddMember(property);
        }

        /// <summary>
        /// Validates a EdmMember object to determine if it can be added to this type's 
        /// Members collection. If this method returns without throwing, it is assumed
        /// the member is valid. 
        /// </summary>
        /// <param name="member">The member to validate</param>
        /// <exception cref="System.ArgumentException">Thrown if the member is not a EdmProperty</exception>
        internal override void ValidateMemberForAdd(EdmMember member)
        {
            Debug.Assert(Helper.IsEdmProperty(member), "Only members of type Property may be added to Row types.");
        }

        /// <summary>
        /// Calculates the row type identity that would result from 
        /// a given set of properties.
        /// </summary>
        /// <param name="properties">The properties that determine the row type's structure</param>
        /// <param name="initializerMetadata">Metadata describing materialization of this row type</param>
        /// <returns>A string that identifies the row type</returns>
        private static string GetRowTypeIdentityFromProperties(IEnumerable<EdmProperty> properties, InitializerMetadata initializerMetadata)
        {
            // The row type identity is formed as follows:
            // "rowtype[" + a comma-separated list of property identities + "]"
            StringBuilder identity = new StringBuilder("rowtype[");

            if (null != properties)
            {
                int i = 0;
                // For each property, append the type name and facets.
                foreach (EdmProperty property in properties)
                {
                    if (i > 0)
                    {
                        identity.Append(",");
                    }
                    identity.Append("(");
                    identity.Append(property.Name);
                    identity.Append(",");
                    property.TypeUsage.BuildIdentity(identity);
                    identity.Append(")");
                    i++;
                }
            }
            identity.Append("]");

            if (null != initializerMetadata)
            {
                identity.Append(",").Append(initializerMetadata.Identity);
            }

            return identity.ToString();
        }


        private static IEnumerable<EdmProperty> CheckProperties(IEnumerable<EdmProperty> properties)
        {
            if (null != properties)
            {
                int i = 0;
                foreach (EdmProperty prop in properties)
                {
                    if (prop == null)
                    {
                        throw EntityUtil.CollectionParameterElementIsNull("properties");
                    }
                    i++;
                }

                /*
                if (i < 1)
                {
                    throw EntityUtil.ArgumentOutOfRange("properties");
                }
                 */

            }
            return properties;
        }
        #endregion

        #region Methods
        /// <summary>
        /// EdmEquals override verifying the equivalence of all members and their type usages.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        internal override bool EdmEquals(MetadataItem item)
        {
            // short-circuit if this and other are reference equivalent
            if (Object.ReferenceEquals(this, item)) { return true; }

            // check type of item
            if (null == item || BuiltInTypeKind.RowType != item.BuiltInTypeKind) { return false; }
            RowType other = (RowType)item;

            // check each row type has the same number of members
            if (this.Members.Count != other.Members.Count) { return false; }

            // verify all members are equivalent
            for (int ordinal = 0; ordinal < this.Members.Count; ordinal++)
            {
                EdmMember thisMember = this.Members[ordinal];
                EdmMember otherMember = other.Members[ordinal];

                // if members are different, return false
                if (!thisMember.EdmEquals(otherMember) ||
                    !thisMember.TypeUsage.EdmEquals(otherMember.TypeUsage))
                {
                    return false;
                }
            }

            return true;
        }
        #endregion
    }
}
