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

    internal class ComplexType : DocumentableAnnotatableElement
    {
        internal static readonly string ElementName = "ComplexType";

        private DefaultableValue<string> _typeAccessAttr;
        private readonly List<Property> _properties = new List<Property>();

        internal ComplexType(ConceptualEntityModel model, XElement element)
            : base(model, element)
        {
        }

        /// <summary>
        ///     Manages the content of the TypeAccess attribute
        /// </summary>
        internal DefaultableValue<string> TypeAccess
        {
            get
            {
                if (_typeAccessAttr == null)
                {
                    _typeAccessAttr = new TypeAccessDefaultableValue(this);
                }
                return _typeAccessAttr;
            }
        }

        internal void AddProperty(Property prop)
        {
            _properties.Add(prop);
        }

        internal IEnumerable<Property> Properties()
        {
            foreach (var p in _properties)
            {
                yield return p;
            }
        }

        internal Property FindPropertyByLocalName(string name)
        {
            foreach (var p in _properties)
            {
                if (String.CompareOrdinal(p.LocalName.Value, name) == 0)
                {
                    return p;
                }
            }
            return null;
        }

        // 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 Properties())
                {
                    yield return child;
                }

                yield return TypeAccess;
            }
        }

        protected override void OnChildDeleted(EFContainer efContainer)
        {
            var child = efContainer as Property;
            if (child != null)
            {
                _properties.Remove(child);
                return;
            }

            base.OnChildDeleted(efContainer);
        }

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

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

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

            ClearEFObjectCollection(_properties);
            ClearEFObject(_typeAccessAttr);
            _typeAccessAttr = null;

            base.PreParse();
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            if (elem.Name.LocalName == Property.ElementName)
            {
                Property prop;
                var conceptualModel = (ConceptualEntityModel)GetParentOfType(typeof(ConceptualEntityModel));
                Debug.Assert(
                    conceptualModel != null,
                    typeof(ComplexType).Name + "ParseSingleElement: Unable to find parent of type ConceptualEntityModel");

                if (conceptualModel != null
                    && ModelHelper.IsElementComplexProperty(elem, conceptualModel))
                {
                    prop = new ComplexConceptualProperty(this, elem);
                }
                else
                {
                    prop = new ConceptualProperty(this, elem);
                }
                prop.Parse(unprocessedElements);
                AddProperty(prop);
            }
            else
            {
                return base.ParseSingleElement(unprocessedElements, elem);
            }
            return true;
        }

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

        internal override DeleteEFElementCommand GetDeleteCommand()
        {
            return new DeleteComplexTypeCommand(this);
        }

        /// <summary>
        ///     Special case for FunctionImport
        ///     We need to return "Collection(typeName)" string for the ReturnType attribute of a FunctionImport.
        /// </summary>
        /// <param name="binding"></param>
        /// <returns></returns>
        internal override string GetRefNameForBinding(ItemBinding binding)
        {
            var fi = binding.Parent as FunctionImport;
            if (fi != null)
            {
                return string.Format(
                    CultureInfo.InvariantCulture,
                    FunctionImport.CollectionFormat,
                    NormalizedNameExternal);
            }

            return base.GetRefNameForBinding(binding);
        }
    }
}
