// 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.Mapping
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Xml.Linq;
    using Microsoft.Data.Entity.Design.Common;
    using Microsoft.Data.Entity.Design.Model.Entity;

    internal class EntitySetMapping : EFElement
    {
        internal static readonly string ElementName = "EntitySetMapping";
        internal static readonly string AttributeName = "Name";

        private readonly List<EntityTypeMapping> _entityTypeMappings = new List<EntityTypeMapping>();
        private readonly List<QueryView> _queryViews = new List<QueryView>();
        private SingleItemBinding<EntitySet> _name;

        internal EntitySetMapping(EFElement parent, XElement element)
            : base(parent, element)
        {
            Debug.Assert(parent is EntityContainerMapping, "parent should be a EntityContainerMapping");
        }

        internal EntityContainerMapping EntityContainerMapping
        {
            get
            {
                var parent = Parent as EntityContainerMapping;
                Debug.Assert(parent != null, "this.Parent should be a EntityContainerMapping");
                return parent;
            }
        }

        /// <summary>
        ///     Returns the binding to the EntitySet pointed to by the Name attribute
        /// </summary>
        internal SingleItemBinding<EntitySet> Name
        {
            get
            {
                if (_name == null)
                {
                    _name = new SingleItemBinding<EntitySet>(
                        this,
                        AttributeName,
                        EntitySetNameNormalizer.NameNormalizer);
                }
                return _name;
            }
        }

        /// <summary>
        ///     Adds the passed in EntityTypeMapping to the internal model (this EntitySetMapping becomes its parent)
        ///     and also to the XLinq tree.
        /// </summary>
        /// <param name="etm"></param>
        internal void AddEntityTypeMapping(EntityTypeMapping etm)
        {
            Debug.Assert(
                etm.Parent == this,
                "unexpected condition.  You are trying to move an EntityTypeMapping between parents.  This is not valid.  Use EntityTypeMapping's Clone() method.");

            _entityTypeMappings.Add(etm);

            Debug.Assert(
                etm.Parent != null && etm.Parent.XContainer != null,
                "The new parent for this EntityTypeMapping does not have a valid XLinq node");
            if (etm.Parent != null
                && etm.Parent.XContainer != null
                && etm.Parent.XContainer != etm.XElement.Parent)
            {
                Debug.Fail("this entity type mapping already has a Parent.  This is not valid.  Use EntityTypeMapping's Clone() method.");
            }
        }

        internal IList<EntityTypeMapping> EntityTypeMappings()
        {
            return _entityTypeMappings.AsReadOnly();
        }

        internal IList<QueryView> QueryViews
        {
            get { return _queryViews.AsReadOnly(); }
        }

        internal bool HasQueryViewElement
        {
            get { return (QueryViews.Count > 0); }
        }

        // 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;
                }
                foreach (var child in EntityTypeMappings())
                {
                    yield return child;
                }
                yield return Name;

                foreach (var childQueryView in QueryViews)
                {
                    yield return childQueryView;
                }
            }
        }

        protected override void OnChildDeleted(EFContainer efContainer)
        {
            var child1 = efContainer as EntityTypeMapping;
            if (child1 != null)
            {
                _entityTypeMappings.Remove(child1);
                return;
            }

            base.OnChildDeleted(efContainer);
        }

#if DEBUG
        internal override ICollection<string> MyAttributeNames()
        {
            var s = base.MyAttributeNames();
            s.Add(AttributeName);
            var ghostChild = GetGhostChild();
            if (ghostChild != null)
            {
                var s2 = ghostChild.MyAttributeNames();
                foreach (var str in s2)
                {
                    s.Add(str);
                }
            }
            return s;
        }
#endif

#if DEBUG
        internal override ICollection<string> MyChildElementNames()
        {
            var s = base.MyChildElementNames();
            s.Add(EntityTypeMapping.ElementName);
            s.Add(QueryView.ElementName);
            return s;
        }
#endif

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

            ClearEFObject(_name);
            _name = null;

            ClearEFObjectCollection(_entityTypeMappings);

            ClearEFObjectCollection(_queryViews);

            base.PreParse();
        }

        protected override void DoParse(ICollection<XName> unprocessedElements)
        {
            // call base DoParse first
            base.DoParse(unprocessedElements);

            // now see if this has an attribute called "TypeName"
            if (GetAttributeValue(EntityTypeMapping.AttributeTypeName) != null)
            {
                if (_entityTypeMappings.Count == 0)
                {
                    // TypeName attribute and no EntityTypeMapping children.  Create a "ghost-node".
                    var etm = new EntityTypeMapping(this, XElement);
                    _entityTypeMappings.Add(etm);
                    etm.Parse(unprocessedElements);

                    // Add an error - we don't want to support this syntax in the designer.
                    var msg = String.Format(
                        CultureInfo.CurrentCulture, Resources.ModelParse_GhostNodeNotSupportedByDesigner,
                        EntityTypeMapping.AttributeTypeName, ElementName);
                    Artifact.AddParseErrorForObject(this, msg, ErrorCodes.ModelParse_GhostNodeNotSupportedByDesigner);
                }
                else
                {
                    // TypeName attribute and EntityTypeMapping children.  These are mutually exclusive.
                    var msg = String.Format(
                        CultureInfo.CurrentCulture, Resources.ModelParse_MutuallyExclusiveAttributeAndChildElement,
                        EntityTypeMapping.AttributeTypeName, EntityTypeMapping.ElementName);
                    Artifact.AddParseErrorForObject(this, msg, ErrorCodes.ModelParse_MutuallyExclusiveAttributeAndChildElement);
                }
            }
        }

        private string DisplayNameInternal(bool localize)
        {
            string resource;
            if (localize)
            {
                resource = Resources.MappingModel_EntitySetMappingDisplayName;
            }
            else
            {
                resource = "{0} (EntitySet)";
            }

            return string.Format(
                CultureInfo.CurrentCulture,
                resource,
                Name.RefName);
        }

        internal override string DisplayName
        {
            get { return DisplayNameInternal(true); }
        }

        internal override string NonLocalizedDisplayName
        {
            get { return DisplayNameInternal(false); }
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            if (elem.Name.LocalName == EntityTypeMapping.ElementName)
            {
                var etm = new EntityTypeMapping(this, elem);
                _entityTypeMappings.Add(etm);
                etm.Parse(unprocessedElements);
            }
            else if (elem.Name.LocalName == QueryView.ElementName)
            {
                var qv = new QueryView(this, elem);
                qv.Parse(unprocessedElements);
                _queryViews.Add(qv);
            }
            else
            {
                return base.ParseSingleElement(unprocessedElements, elem);
            }
            return true;
        }

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