// 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.Xml.Linq;
    using Microsoft.Data.Entity.Design.VersioningFacade;

    internal class MappingModel : EFRuntimeModelRoot
    {
        internal static readonly string ElementName = "Mapping";
        internal static readonly string AttributeSpace = "Space";
        internal static readonly string AttributeShouldDefaultUnspecifiedMaps = "ShouldDefaultUnspecifiedMaps";

        private DefaultableValue<bool> _defaultUnspecifiedMaps;
        private readonly List<Alias> _aliases = new List<Alias>();
        private readonly List<EntityContainerMapping> _entityContainerMappings = new List<EntityContainerMapping>();

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

        /// <summary>
        ///     Returns the string value of the Space attribute (at this point we are assuming that
        ///     this is a read only attribute).
        /// </summary>
        internal string Space
        {
            get { return GetAttributeValue(AttributeSpace); }
        }

        internal DefaultableValue<bool> ShouldDefaultUnspecifiedMaps
        {
            get
            {
                if (_defaultUnspecifiedMaps == null)
                {
                    _defaultUnspecifiedMaps = new ShouldDefaultUnspecifiedMapsDefaultableValue(this);
                }
                return _defaultUnspecifiedMaps;
            }
        }

        private class ShouldDefaultUnspecifiedMapsDefaultableValue : DefaultableValue<bool>
        {
            internal ShouldDefaultUnspecifiedMapsDefaultableValue(EFElement parent)
                : base(parent, AttributeShouldDefaultUnspecifiedMaps)
            {
            }

            internal override string AttributeName
            {
                get { return AttributeShouldDefaultUnspecifiedMaps; }
            }

            public override bool DefaultValue
            {
                get { return false; }
            }
        }

        internal IList<Alias> Aliases()
        {
            return _aliases.AsReadOnly();
        }

        // TODO: Need to figure out what does it means to have more than one EntityContainer.  
        // In the meantime use this helper to get the first one
        internal EntityContainerMapping FirstEntityContainerMapping
        {
            get
            {
                if (_entityContainerMappings.Count == 0)
                {
                    return null;
                }
                else
                {
                    return _entityContainerMappings[0];
                }
            }
        }

        internal IList<EntityContainerMapping> EntityContainerMappings()
        {
            return _entityContainerMappings.AsReadOnly();
        }

        internal void AddEntityContainerMapping(EntityContainerMapping entityContainerMapping)
        {
            _entityContainerMappings.Add(entityContainerMapping);
        }

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

                foreach (var child2 in EntityContainerMappings())
                {
                    yield return child2;
                }

                yield return ShouldDefaultUnspecifiedMaps;
            }
        }

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

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

            base.OnChildDeleted(efContainer);
        }

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

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

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

            ClearEFObject(_defaultUnspecifiedMaps);
            _defaultUnspecifiedMaps = null;

            ClearEFObjectCollection(_aliases);
            ClearEFObjectCollection(_entityContainerMappings);

            base.PreParse();
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            if (elem.Name.LocalName == Alias.ElementName)
            {
                var a = new Alias(this, elem);
                a.Parse(unprocessedElements);
                _aliases.Add(a);
            }
            else if (elem.Name.LocalName == EntityContainerMapping.ElementName)
            {
                var ecm = new EntityContainerMapping(this, elem);
                _entityContainerMappings.Add(ecm);
                ecm.Parse(unprocessedElements);
            }
            else
            {
                return base.ParseSingleElement(unprocessedElements, elem);
            }
            return true;
        }

        /// <summary>
        ///     Retrieve the root "mapping" node for current node.  If there is no root mapping node, this will return null.
        /// </summary>
        /// <returns></returns>
        internal static MappingModel GetMappingRoot(EFElement node)
        {
            EFContainer currNode = node;
            MappingModel model = null;
            while (currNode != null)
            {
                model = currNode as MappingModel;
                if (model != null)
                {
                    break;
                }
                else
                {
                    currNode = currNode.Parent;
                }
            }

            return model;
        }

        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.GetMSLNamespaceName(
                               SchemaManager.GetSchemaVersion(Artifact.XDocument.Root.Name.Namespace));
            }
        }

        public override IValueProperty<string> Name
        {
            get { throw new NotImplementedException("What do we do here?  this isn't really a bindable thing..."); }
        }
    }
}
