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

namespace Microsoft.Data.Entity.Design.Model.Entity
{
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Xml.Linq;

    internal abstract class EntitySet : NameableAnnotatableElement
    {
        internal static readonly string ElementName = "EntitySet";
        internal static readonly string AttributeEntityType = "EntityType";

        private SingleItemBinding<EntityType> _entityTypeBinding;

        protected EntitySet(EFElement parent, XElement element)
            : base(parent, element)
        {
        }

        /// <summary>
        ///     A bindable reference to the EntityType for this set
        /// </summary>
        internal SingleItemBinding<EntityType> EntityType
        {
            get
            {
                if (_entityTypeBinding == null)
                {
                    _entityTypeBinding = new SingleItemBinding<EntityType>(
                        this,
                        AttributeEntityType,
                        EntityTypeNameNormalizer.NameNormalizer);
                }

                return _entityTypeBinding;
            }
        }

#if DEBUG
        internal override ICollection<string> MyAttributeNames()
        {
            var s = base.MyAttributeNames();
            s.Add(AttributeEntityType);
            return s;
        }
#endif

        protected override void PreParse()
        {
            Debug.Assert(State != EFElementState.Parsed, "this object should not already be in the parsed state");

            ClearEFObject(_entityTypeBinding);
            _entityTypeBinding = null;

            base.PreParse();
        }

        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            return false;
        }

        protected override void DoNormalize()
        {
            if (Parent is BaseEntityContainer)
            {
                var normalizedName = EntitySetNameNormalizer.NameNormalizer(this, LocalName.Value);
                Debug.Assert(null != normalizedName, "Null NormalizedName for refName " + LocalName.Value);
                NormalizedName = (normalizedName != null ? normalizedName.Symbol : Symbol.EmptySymbol);
                base.DoNormalize();
            }
        }

        protected override void DoResolve(EFArtifactSet artifactSet)
        {
            EntityType.Rebind();
            if (EntityType.Status == BindingStatus.Known)
            {
                State = EFElementState.Resolved;
            }
        }

        // we unfortunately get a warning from the compiler when we use the "base" keyword in "iterator" types generated by using the
        // "yield return" keyword.  By adding this method, I was able to get around this.  Unfortunately, I wasn't able to figure out
        // a way to implement this once and have derived classes share the implementation (since the "base" keyword is resolved at 
        // compile-time and not at runtime.
        private IEnumerable<EFObject> BaseChildren
        {
            get { return base.Children; }
        }

        internal override IEnumerable<EFObject> Children
        {
            get
            {
                foreach (var efobj in BaseChildren)
                {
                    yield return efobj;
                }

                yield return EntityType;
            }
        }

        internal override string EFTypeName
        {
            get { return ElementName; }
        }

        /// <summary>
        ///     An EntitySet is always referred to by its bare Name.  This is because an EntitySet is always
        ///     referred to in the context of an EntityContainer.
        /// </summary>
        internal override string GetRefNameForBinding(ItemBinding binding)
        {
            return LocalName.Value;
        }

        internal BaseEntityModel EntityModel
        {
            get
            {
                if (Parent != null
                    && Parent.Parent != null)
                {
                    var baseType = Parent.Parent as BaseEntityModel;
                    Debug.Assert(baseType != null, "this.Parent.Parent should be a BaseEntityModel");
                    return baseType;
                }

                return null;
            }
        }

        /// <summary>
        ///     Returns all entity types in the set.
        /// </summary>
        /// <returns></returns>
        internal List<EntityType> GetEntityTypesInTheSet()
        {
            var entityTypes = new List<EntityType>();
            Debug.Assert(EntityType != null, "Entity type is null.");
            if (EntityType != null
                && EntityType.Status == BindingStatus.Known)
            {
                entityTypes.Add(EntityType.Target);
                // If the EntityType is a conceptual entity type, we also need to add all derived types as well.
                var conceptualEntityType = EntityType.Target as ConceptualEntityType;
                if (conceptualEntityType != null)
                {
                    entityTypes.AddRange(conceptualEntityType.ResolvableAllDerivedTypes);
                }
            }
            return entityTypes;
        }
    }
}
