//---------------------------------------------------------------------
// <copyright file="EntityContainerRelationshipSet.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------

namespace System.Data.EntityModel.SchemaObjectModel
{
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Xml;

    /// <summary>
    /// Represents an RelationshipSet element.
    /// </summary>
    internal abstract class EntityContainerRelationshipSet : SchemaElement
    {

        private IRelationship _relationship;
        string _unresolvedRelationshipTypeName;

        
        /// <summary>
        /// Constructs an EntityContainerRelationshipSet
        /// </summary>
        /// <param name="parentElement">Reference to the schema element.</param>
        public EntityContainerRelationshipSet( EntityContainer parentElement )
            : base( parentElement )
        {
        }

        public override string FQName
        {
            get
            {
                return this.ParentElement.Name + "." + this.Name;
            }
        }

        internal IRelationship Relationship
        {
            get
            {
                return _relationship;
            }
            set
            {
                Debug.Assert(value != null, "relationship can never be set to null");
                _relationship = value;
            }
        }

        protected abstract bool HasEnd( string role );
        protected abstract void AddEnd( IRelationshipEnd relationshipEnd, EntityContainerEntitySet entitySet );
        internal abstract IEnumerable<EntityContainerRelationshipSetEnd> Ends { get; }

        /// <summary>
        /// The method that is called when an Association attribute is encountered.
        /// </summary>
        /// <param name="reader">An XmlReader positioned at the Association attribute.</param>
        protected void HandleRelationshipTypeNameAttribute( XmlReader reader )
        {
            Debug.Assert( reader != null );
            ReturnValue<string> value = HandleDottedNameAttribute( reader, _unresolvedRelationshipTypeName, Strings.PropertyTypeAlreadyDefined );
            if ( value.Succeeded )
            {
                _unresolvedRelationshipTypeName = value.Value;
            }
        }

        
        /// <summary>
        /// Used during the resolve phase to resolve the type name to the object that represents that type
        /// </summary>
        internal override void ResolveTopLevelNames()
        {
            base.ResolveTopLevelNames();

            if ( _relationship == null )
            {
                SchemaType element;
                if ( !Schema.ResolveTypeName( this, _unresolvedRelationshipTypeName, out element ) )
                {
                    return;
                }

                _relationship = element as IRelationship;
                if ( _relationship == null )
                {
                    AddError( ErrorCode.InvalidPropertyType, EdmSchemaErrorSeverity.Error,
                        System.Data.Entity.Strings.InvalidRelationshipSetType(element.Name ) );
                    return;
                }
            }

            foreach ( EntityContainerRelationshipSetEnd end in Ends )
            {
                end.ResolveTopLevelNames();
            }
        }

        internal override void ResolveSecondLevelNames()
        {
            base.ResolveSecondLevelNames();
            foreach (EntityContainerRelationshipSetEnd end in Ends)
            {
                end.ResolveSecondLevelNames();
            }
        }

        /// <summary>
        /// Do all validation for this element here, and delegate to all sub elements
        /// </summary>
        internal override void Validate()
        {
            base.Validate();

            InferEnds();

            // check out the ends
            foreach ( EntityContainerRelationshipSetEnd end in Ends )
            {
                end.Validate();
            }


            // Enabling Association between subtypes in case of Referential Constraints, since 
            // CSD is blocked on this. We need to make a long term call about whether we should
            // really allow this. Bug #520216
            //foreach (ReferentialConstraint constraint in Relationship.Constraints)
            //{
            //    IRelationshipEnd dependentEnd = constraint.DependentRole.End;
            //    EntityContainerRelationshipSetEnd setEnd = GetEnd(dependentEnd.Name);
            //    Debug.Assert(setEnd != null);
            //    //Make sure that the EntityType of the dependant role in a referential constraint
            //    //covers the whole EntitySet( i.e. not  a subtype of the EntitySet's type).
            //    if (!setEnd.EntitySet.EntityType.IsOfType(constraint.DependentRole.End.Type))
            //    {
            //        AddError(ErrorCode.InvalidDependentRoleType, EdmSchemaErrorSeverity.Error,
            //            System.Data.Entity.Strings.InvalidDependentRoleType(dependentEnd.Type.FQName, dependentEnd.Name, 
            //                                dependentEnd.Parent.FQName, setEnd.EntitySet.Name, setEnd.ParentElement.Name));
            //    }
            //}

            // Validate Number of ends is correct
            //    What we know:
            //      No ends are missing, becuase we infered all missing ends
            //      No extra ends are there because the names have been matched, and an extra name will have caused an error
            //
            //    looks like no count validation needs to be done

        }

        /// <summary>
        /// Adds any ends that need to be infered
        /// </summary>
        private void InferEnds()
        {
            Debug.Assert( Relationship != null );

            foreach ( IRelationshipEnd relationshipEnd in Relationship.Ends )
            {
                if ( ! HasEnd( relationshipEnd.Name ) )
                {
                    EntityContainerEntitySet entitySet = InferEntitySet(relationshipEnd);
                    if (entitySet != null)
                    {
                        // we don't have this end, we need to add it
                        AddEnd(relationshipEnd, entitySet);
                    }
                }
            }
        }


        /// <summary>
        /// For the given relationship end, find the EntityContainer Property that will work for the extent
        /// </summary>
        /// <param name="relationshipEnd">The relationship end of the RelationshipSet that needs and extent</param>
        /// <returns>Null is none could be found, or the EntityContainerProperty that is the valid extent</returns>
        private EntityContainerEntitySet InferEntitySet( IRelationshipEnd relationshipEnd )
        {
            Debug.Assert(relationshipEnd != null, "relationshipEnd parameter is null");

            List<EntityContainerEntitySet> possibleExtents = new List<EntityContainerEntitySet>();
            foreach ( EntityContainerEntitySet set in ParentElement.EntitySets )
            {
                if ( relationshipEnd.Type.IsOfType( set.EntityType ) )
                {
                    possibleExtents.Add( set );
                }
            }

            if ( possibleExtents.Count == 1 )
            {
                return possibleExtents[0];
            }
            else if ( possibleExtents.Count == 0 )
            {
                // no matchs
                AddError( ErrorCode.MissingExtentEntityContainerEnd, EdmSchemaErrorSeverity.Error,
                    System.Data.Entity.Strings.MissingEntityContainerEnd(relationshipEnd.Name, FQName ) );
            }
            else
            {
                // abmigous
                AddError( ErrorCode.AmbiguousEntityContainerEnd, EdmSchemaErrorSeverity.Error,
                    System.Data.Entity.Strings.AmbiguousEntityContainerEnd(relationshipEnd.Name, FQName ) );
            }

            return null;
        }

        
        /// <summary>
        /// The parent element as an EntityContainer
        /// </summary>
        internal new EntityContainer ParentElement
        {
            get
            {
                return (EntityContainer)( base.ParentElement );
            }
        }
    }
}
