// 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.Globalization;
    using System.Xml.Linq;
    using Microsoft.Data.Entity.Design.Common;
    using Microsoft.Data.Entity.Design.Model.Validation;
    using Microsoft.Data.Entity.Design.Model.XLinqAnnotations;
    using Microsoft.Data.Entity.Design.VersioningFacade;

    internal class ConceptualEntityModel : BaseEntityModel
    {
        private readonly List<ComplexType> _complexTypes = new List<ComplexType>();
        private readonly List<EnumType> _enumTypes = new List<EnumType>();
        private readonly List<UsingElement> _usings = new List<UsingElement>();

        // UseStrongSpatialTypes attribute only exists on the CSDL Schema element
        private DefaultableValue<bool> _useStrongSpatialTypesAttr;

        internal ConceptualEntityModel(EntityDesignArtifact parent, XElement element)
            : base(parent, element)
        {
            if (parent != null)
            {
                parent.ConceptualModel = this;
            }
        }

        #region properties

        /// <summary>
        ///     Returns true if this model is based on a CSDL file, false if an SSDL file
        /// </summary>
        public override bool IsCSDL
        {
            get { return true; }
        }

        public IEnumerable<ComplexType> ComplexTypes()
        {
            foreach (var ct in _complexTypes)
            {
                yield return ct;
            }
        }

        internal int ComplexTypeCount
        {
            get { return _complexTypes.Count; }
        }

        internal void AddComplexType(ComplexType complexType)
        {
            _complexTypes.Add(complexType);
        }

        internal IEnumerable<UsingElement> Usings()
        {
            foreach (var u in _usings)
            {
                yield return u;
            }
        }

        internal int UsingCount
        {
            get { return _usings.Count; }
        }

        public IEnumerable<EnumType> EnumTypes()
        {
            foreach (var et in _enumTypes)
            {
                yield return et;
            }
        }

        public void AddEnumType(EnumType enumType)
        {
            Debug.Assert(_enumTypes.Contains(enumType) == false, "EnumType:" + enumType.DisplayName + " is already added.");
            if (_enumTypes.Contains(enumType) == false)
            {
                _enumTypes.Add(enumType);
            }
        }

        /// <summary>
        ///     Manages the content of the LazyLoadingEnabled attribute
        /// </summary>
        internal DefaultableValue<bool> UseStrongSpatialTypes
        {
            get
            {
                if (_useStrongSpatialTypesAttr == null)
                {
                    _useStrongSpatialTypesAttr = new UseStrongSpatialTypesDefaultableValue(this);
                }
                return _useStrongSpatialTypesAttr;
            }
        }

        #endregion

        // 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 ct in ComplexTypes())
                {
                    yield return ct;
                }

                foreach (var et in EnumTypes())
                {
                    yield return et;
                }

                foreach (var use in Usings())
                {
                    yield return use;
                }

                yield return UseStrongSpatialTypes;
            }
        }

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

            var child2 = efContainer as UsingElement;
            if (child2 != null)
            {
                _usings.Remove(child2);
                return;
            }

            var child3 = efContainer as EnumType;
            if (child3 != null)
            {
                _enumTypes.Remove(child3);
                return;
            }

            base.OnChildDeleted(efContainer);
        }

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

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

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

            // clear this here instead of in base class, since we populate it here
            ClearEFObjectCollection(_entityContainers);
            ClearEFObjectCollection(_complexTypes);
            ClearEFObjectCollection(_usings);
            ClearEFObjectCollection(_enumTypes);
            ClearEFObject(_useStrongSpatialTypesAttr);
            _useStrongSpatialTypesAttr = null;

            base.PreParse();
        }

        protected override void DoParse(ICollection<XName> unprocessedElements)
        {
            // Ensure to parse all enum types before ConceptualProperty/ComplexConceptualProperty are parsed.
            // This is done so that we have enough information to decide which property to create.
            var csdlNamespaceName = SchemaManager.GetCSDLNamespaceName(Artifact.SchemaVersion);
            foreach (var element in XElement.Elements(XName.Get(EnumType.ElementName, csdlNamespaceName)))
            {
                var enumType = new EnumType(this, element);
                _enumTypes.Add(enumType);
                enumType.Parse(unprocessedElements);
            }

            base.DoParse(unprocessedElements);
        }

        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            // Conceptual EntityModel needs to create Conceptual EntityContainer objects
            if (elem.Name.LocalName == BaseEntityContainer.ElementName)
            {
                if (_entityContainers.Count > 0)
                {
                    // multiple EntityContainers detected, report an error
                    var msg = String.Format(CultureInfo.CurrentCulture, Resources.TOO_MANY_ENTITY_CONTAINER_ELEMENTS, Namespace.Value);
                    var error = new ErrorInfo(
                        ErrorInfo.Severity.ERROR, msg, this, ErrorCodes.TOO_MANY_ENTITY_CONTAINER_ELEMENTS, ErrorClass.ParseError);
                    Artifact.AddParseErrorForObject(this, error);
                }
                var ec = new ConceptualEntityContainer(this, elem);
                _entityContainers.Add(ec);
                ec.Parse(unprocessedElements);
            }
            else if (elem.Name.LocalName == ComplexType.ElementName)
            {
                var complexType = new ComplexType(this, elem);
                _complexTypes.Add(complexType);
                complexType.Parse(unprocessedElements);
            }
            else if (elem.Name.LocalName == UsingElement.ElementName)
            {
                var use = new UsingElement(this, elem);
                _usings.Add(use);
                use.Parse(unprocessedElements);
            }
            else if (elem.Name.LocalName == EnumType.ElementName)
            {
                // Check if enumType that represents the XElement <see DoParse method>
                var enumType = ModelItemAnnotation.GetModelItem(elem) as EnumType;
                if (enumType == null
                    || enumType.IsDisposed)
                {
                    enumType = new EnumType(this, elem);
                    _enumTypes.Add(enumType);
                    enumType.Parse(unprocessedElements);
                }
            }
            else
            {
                return base.ParseSingleElement(unprocessedElements, elem);
            }
            return true;
        }

        internal override XNamespace XNamespace
        {
            get
            {
                // if for some reason the XElement backing this model node doesn't exist yet, we'll use the 
                // desired namespace for the EF version of the artifact. 
                return XElement != null
                           ? XElement.Name.Namespace
                           : SchemaManager.GetCSDLNamespaceName(
                               SchemaManager.GetSchemaVersion(Artifact.XDocument.Root.Name.Namespace));
            }
        }
    }
}
