// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Objects.Internal
{
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Core.Objects.DataClasses;
    using System.Data.Entity.Resources;
    using System.Data.Entity.Utilities;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;

    // <summary>
    // Base class containing common code for different implementations of the IEntityWrapper
    // interface.  Generally speaking, operations involving the ObjectContext, RelationshipManager
    // and raw Entity are handled through this class.
    // </summary>
    // <typeparam name="TEntity"> The type of entity wrapped </typeparam>
    internal abstract class BaseEntityWrapper<TEntity> : IEntityWrapper
        where TEntity : class
    {
        // This enum allows boolean flags to be added to the wrapper without introducing a new field
        // for each one.  This helps keep the wrapper memory footprint small, which is important
        // in some high-performance NoTracking cases.
        [Flags]
        private enum WrapperFlags
        {
            None = 0,
            NoTracking = 1,
            InitializingRelatedEnds = 2,
            OverridesEquals = 4,
        }

        private readonly RelationshipManager _relationshipManager;
        private Type _identityType;
        private WrapperFlags _flags;

        // <summary>
        // Constructs a wrapper for the given entity and its associated RelationshipManager.
        // </summary>
        // <param name="entity"> The entity to be wrapped </param>
        // <param name="relationshipManager"> the RelationshipManager associated with this entity </param>
        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "entity")]
        protected BaseEntityWrapper(TEntity entity, RelationshipManager relationshipManager, bool overridesEquals)
        {
            Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
            DebugCheck.NotNull(entity);
            
            if (relationshipManager == null)
            {
                throw new InvalidOperationException(Strings.RelationshipManager_UnexpectedNull);
            }
            _relationshipManager = relationshipManager;

            if (overridesEquals)
            {
                _flags = WrapperFlags.OverridesEquals;
            }
        }

        // <summary>
        // Constructs a wrapper as part of the materialization process.  This constructor is only used
        // during materialization where it is known that the entity being wrapped is newly constructed.
        // This means that some checks are not performed that might be needed when thw wrapper is
        // created at other times, and information such as the identity type is passed in because
        // it is readily available in the materializer.
        // </summary>
        // <param name="entity"> The entity to wrap </param>
        // <param name="relationshipManager"> The RelationshipManager associated with this entity </param>
        // <param name="entitySet"> The entity set, or null if none is known </param>
        // <param name="context"> The context to which the entity should be attached </param>
        // <param name="mergeOption"> NoTracking for non-tracked entities, AppendOnly otherwise </param>
        // <param name="identityType"> The type of the entity ignoring any possible proxy type </param>
        protected BaseEntityWrapper(
            TEntity entity, RelationshipManager relationshipManager, EntitySet entitySet, ObjectContext context, MergeOption mergeOption,
            Type identityType, bool overridesEquals)
        {
            Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
            DebugCheck.NotNull(entity);

            if (relationshipManager == null)
            {
                throw new InvalidOperationException(Strings.RelationshipManager_UnexpectedNull);
            }

            _identityType = identityType;
            _relationshipManager = relationshipManager;

            if (overridesEquals)
            {
                _flags = WrapperFlags.OverridesEquals;
            }

            RelationshipManager.SetWrappedOwner(this, entity);
            
            if (entitySet != null)
            {
                Context = context;
                MergeOption = mergeOption;
                RelationshipManager.AttachContextToRelatedEnds(context, entitySet, mergeOption);
            }
        }

        // See IEntityWrapper documentation
        public RelationshipManager RelationshipManager
        {
            get { return _relationshipManager; }
        }

        // See IEntityWrapper documentation
        public ObjectContext Context { get; set; }

        // See IEntityWrapper documentation
        public MergeOption MergeOption
        {
            get { return (_flags & WrapperFlags.NoTracking) != 0 ? MergeOption.NoTracking : MergeOption.AppendOnly; }
            private set
            {
                Debug.Assert(
                    value == MergeOption.AppendOnly || value == MergeOption.NoTracking,
                    "Merge option must be one of NoTracking or AppendOnly.");
                if (value == MergeOption.NoTracking)
                {
                    _flags |= WrapperFlags.NoTracking;
                }
                else
                {
                    _flags &= ~WrapperFlags.NoTracking;
                }
            }
        }

        // See IEntityWrapper documentation
        public bool InitializingProxyRelatedEnds
        {
            get { return (_flags & WrapperFlags.InitializingRelatedEnds) != 0; }
            set
            {
                if (value)
                {
                    _flags |= WrapperFlags.InitializingRelatedEnds;
                }
                else
                {
                    _flags &= ~WrapperFlags.InitializingRelatedEnds;
                }
            }
        }

        // See IEntityWrapper documentation
        public void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
        {
            DebugCheck.NotNull(context);
            Context = context;
            MergeOption = mergeOption;
            if (entitySet != null)
            {
                RelationshipManager.AttachContextToRelatedEnds(context, entitySet, mergeOption);
            }
        }

        // See IEntityWrapper documentation
        public void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
        {
            DebugCheck.NotNull(entitySet);
            DebugCheck.NotNull(context);
            Debug.Assert(
                MergeOption.NoTracking == mergeOption ||
                MergeOption.AppendOnly == mergeOption,
                "mergeOption");

            if (!ReferenceEquals(Context, context))
            {
                Context = context;
                MergeOption = mergeOption;
                RelationshipManager.ResetContextOnRelatedEnds(context, entitySet, mergeOption);
            }
        }

        // See IEntityWrapper documentation
        public void DetachContext()
        {
            if (Context != null
                &&
                Context.ObjectStateManager.TransactionManager.IsAttachTracking
                &&
                Context.ObjectStateManager.TransactionManager.OriginalMergeOption == MergeOption.NoTracking)
            {
                // If AttachTo() failed while attaching graph retrieved with NoTracking option,
                // we don't want to clear the Context property of the wrapped entity
                MergeOption = MergeOption.NoTracking;
            }
            else
            {
                Context = null;
            }

            RelationshipManager.DetachContextFromRelatedEnds();
        }

        // See IEntityWrapper documentation
        public EntityEntry ObjectStateEntry { get; set; }

        // See IEntityWrapper documentation
        public Type IdentityType
        {
            get
            {
                if (_identityType == null)
                {
                    _identityType = EntityUtil.GetEntityIdentityType(typeof(TEntity));
                }
                return _identityType;
            }
        }

        public bool OverridesEqualsOrGetHashCode
        {
            get { return (_flags & WrapperFlags.OverridesEquals) != 0; }
        }

        // All these methods defined by IEntityWrapper
        public abstract void EnsureCollectionNotNull(RelatedEnd relatedEnd);
        public abstract EntityKey EntityKey { get; set; }
        public abstract bool OwnsRelationshipManager { get; }
        public abstract EntityKey GetEntityKeyFromEntity();
        public abstract void SetChangeTracker(IEntityChangeTracker changeTracker);
        public abstract void TakeSnapshot(EntityEntry entry);
        public abstract void TakeSnapshotOfRelationships(EntityEntry entry);
        public abstract object GetNavigationPropertyValue(RelatedEnd relatedEnd);
        public abstract void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value);
        public abstract void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value);
        public abstract void CollectionAdd(RelatedEnd relatedEnd, object value);
        public abstract bool CollectionRemove(RelatedEnd relatedEnd, object value);
        public abstract object Entity { get; }
        public abstract TEntity TypedEntity { get; }
        public abstract void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value);
        public abstract void UpdateCurrentValueRecord(object value, EntityEntry entry);
        public abstract bool RequiresRelationshipChangeTracking { get; }
    }
}
