// 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;

    internal class AssociationEnd : EFNormalizableItem
    {
        internal static readonly string ElementName = "End";
        internal static readonly string AttributeMultiplicity = "Multiplicity";
        internal static readonly string AttributeRole = "Role";
        internal static readonly string AttributeType = "Type";

        private SingleItemBinding<EntityType> _typeBinding;
        private DefaultableValue<string> _multiplicityAttr;
        private DefaultableValue<string> _roleAttr;
        private OnDeleteAction _onDeteleAction;

        internal AssociationEnd(EFElement parent, XElement element)
            : base(parent, element)
        {
        }

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

        /// <summary>
        ///     A bindable reference to the EntityType for this end of the association
        /// </summary>
        internal SingleItemBinding<EntityType> Type
        {
            get
            {
                if (_typeBinding == null)
                {
                    _typeBinding = new SingleItemBinding<EntityType>(
                        this,
                        AttributeType,
                        EntityTypeNameNormalizer.NameNormalizer);
                }
                return _typeBinding;
            }
        }

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

        private class MultiplicityDefaultableValue : DefaultableValue<string>
        {
            internal MultiplicityDefaultableValue(EFElement parent)
                : base(parent, AttributeMultiplicity)
            {
            }

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

            public override string DefaultValue
            {
                get { return ModelConstants.Multiplicity_Many; }
            }
        }

        /// <summary>
        ///     Manages the content of the Role attribute
        /// </summary>
        internal DefaultableValue<string> Role
        {
            get
            {
                if (_roleAttr == null)
                {
                    var association = Parent as Association;
                    Debug.Assert(association != null, "Parent of AssociationEnd is not an Association!");

                    _roleAttr = new RoleDefaultableValue(this);
                }
                return _roleAttr;
            }
        }

        private class RoleDefaultableValue : DefaultableValue<String>
        {
            internal RoleDefaultableValue(EFElement parent)
                : base(parent, AttributeRole)
            {
            }

            public override string DefaultValue
            {
                get
                {
                    var ae = Parent as AssociationEnd;

                    Debug.Assert(ae != null, "unexpected parent type for Role attribute");

                    return ae != null ? ae.GetRoleDefault() : String.Empty;
                }
            }

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

        internal OnDeleteAction OnDeleteAction
        {
            get { return _onDeteleAction; }
            set { _onDeteleAction = value; }
        }

        // 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;
                }
                if (_onDeteleAction != null)
                {
                    yield return _onDeteleAction;
                }

                yield return Type;
                yield return Multiplicity;
                yield return Role;
            }
        }

        protected override void OnChildDeleted(EFContainer efContainer)
        {
            if (efContainer == _onDeteleAction)
            {
                _onDeteleAction = null;
                return;
            }

            base.OnChildDeleted(efContainer);
        }

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

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

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

            ClearEFObject(_multiplicityAttr);
            _multiplicityAttr = null;
            ClearEFObject(_typeBinding);
            _typeBinding = null;
            ClearEFObject(_roleAttr);
            _roleAttr = null;
            ClearEFObject(_onDeteleAction);
            _onDeteleAction = null;

            base.PreParse();
        }

        internal override bool ParseSingleElement(ICollection<XName> unprocessedElements, XElement elem)
        {
            if (elem.Name.LocalName == OnDeleteAction.ElementName)
            {
                if (_onDeteleAction == null)
                {
                    _onDeteleAction = new OnDeleteAction(this, elem);
                    _onDeteleAction.Parse(unprocessedElements);
                }
                else
                {
                    // multiple OnDelete elements
                    var msg = String.Format(CultureInfo.CurrentCulture, Resources.DuplicatedElementMsg, elem.Name.LocalName);
                    Artifact.AddParseErrorForObject(this, msg, ErrorCodes.DUPLICATED_ELEMENT_ENCOUNTERED);
                }
            }
            else
            {
                return base.ParseSingleElement(unprocessedElements, elem);
            }
            return true;
        }

        protected override void DoNormalize()
        {
            var assoc = Parent as Association;
            if (assoc != null)
            {
                NormalizedName = new Symbol(assoc.EntityModel.NamespaceValue, assoc.LocalName.Value, Role.Value);
                base.DoNormalize();
            }
        }

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

        private string GetRoleDefault()
        {
            // we haven't resolved yet when this gets called, so just cut off any alias or namespace prefixes
            var parts = Type.RefName.Split('.');
            return parts[parts.GetUpperBound(0)];
        }

        /// <summary>
        ///     The Role property of an AssociationEnd is used like its name in that this is the property
        ///     that others bind to.
        /// </summary>
        /// <param name="newName"></param>
        internal override void Rename(string newName)
        {
            if (string.Compare(newName, Role.Value, StringComparison.CurrentCulture) != 0)
            {
                Role.Value = newName;
            }
        }

        internal override DefaultableValue<string> GetNameAttribute()
        {
            return Role;
        }

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

        public override IValueProperty<string> Name
        {
            get { return Role; }
        }
    }
}
