//---------------------------------------------------------------------
// <copyright file="EntityWrapperFactory.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.Linq.Expressions;
using System.Security.Permissions;
using System.Threading;
using System.Data.Common.Utils;
using System.Runtime.CompilerServices;

namespace System.Data.Objects.Internal
{
    /// <summary>
    /// Factory class for creating IEntityWrapper instances.
    /// </summary>
    internal static class EntityWrapperFactory
    {
        // A cache of functions used to create IEntityWrapper instances for a given type
        private static readonly Memoizer<Type, Func<object, IEntityWrapper>> _delegateCache = new Memoizer<Type, Func<object, IEntityWrapper>>(CreateWrapperDelegate, null);

        /// <summary>
        /// The single instance of the NullEntityWrapper.
        /// </summary>
        internal static IEntityWrapper NullWrapper
        {
            get { return NullEntityWrapper.NullWrapper; }
        }

        /// <summary>
        /// Called to create a new wrapper outside of the normal materialization process.
        /// This method is typically used when a new entity is created outside the context and then is
        /// added or attached.  The materializer bypasses this method and calls wrapper constructors
        /// directory for performance reasons.
        /// This method does not check whether or not the wrapper already exists in the context.
        /// </summary>
        /// <param name="entity">The entity for which a wrapper will be created</param>
        /// <param name="key">The key associated with that entity, or null</param>
        /// <returns>The new wrapper instance</returns>
        internal static IEntityWrapper CreateNewWrapper(object entity, EntityKey key)
        {
            Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
            if (entity == null)
            {
                return NullEntityWrapper.NullWrapper;
            }
            // We used a cache of functions based on the actual type of entity that we need to wrap.
            // Creatung these functions is slow, but once they are created they are relatively fast.
            IEntityWrapper wrappedEntity = _delegateCache.Evaluate(entity.GetType())(entity);
            wrappedEntity.RelationshipManager.SetWrappedOwner(wrappedEntity, entity);
            // We cast to object here to avoid calling the overridden != operator on EntityKey.
            // This creates a very small perf gain, which is none-the-less significant for lean no-tracking cases.
            if ((object)key != null && (object)wrappedEntity.EntityKey == null)
            {
                wrappedEntity.EntityKey = key;
            }

            // If the entity is a proxy, set the wrapper to match
            EntityProxyTypeInfo proxyTypeInfo;
            if (EntityProxyFactory.TryGetProxyType(entity.GetType(), out proxyTypeInfo))
            {
                proxyTypeInfo.SetEntityWrapper(wrappedEntity);
            }

            return wrappedEntity;
        }

        // Creates a delegate that can then be used to create wrappers for a given type.
        // This is slow which is why we only create the delegate once and then cache it.
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private static Func<object, IEntityWrapper> CreateWrapperDelegate(Type entityType)
        {
            // For entities that implement all our interfaces we create a special lightweight wrapper that is both
            // smaller and faster than the strategy-based wrapper.
            // Otherwise, the wrapper is provided with different delegates depending on which interfaces are implemented.
            bool isIEntityWithRelationships = typeof(IEntityWithRelationships).IsAssignableFrom(entityType);
            bool isIEntityWithChangeTracker = typeof(IEntityWithChangeTracker).IsAssignableFrom(entityType);
            bool isIEntityWithKey = typeof(IEntityWithKey).IsAssignableFrom(entityType);
            bool isProxy = EntityProxyFactory.IsProxyType(entityType);
            MethodInfo createDelegate;
            if (isIEntityWithRelationships && isIEntityWithChangeTracker && isIEntityWithKey && !isProxy)
            {
                createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedLightweight", BindingFlags.NonPublic | BindingFlags.Static);
            }
            else if (isIEntityWithRelationships)
            {
                // This type of strategy wrapper is used when the entity implements IEntityWithRelationships
                // In this case it is important that the entity itself is used to create the RelationshipManager
                createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedWithRelationships", BindingFlags.NonPublic | BindingFlags.Static);
            }
            else
            {
                createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedWithoutRelationships", BindingFlags.NonPublic | BindingFlags.Static);
            }
            createDelegate = createDelegate.MakeGenericMethod(entityType);
            return (Func<object, IEntityWrapper>)createDelegate.Invoke(null, new object[0]);
        }

        // Returns a delegate that creates the fast LightweightEntityWrapper
        private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedLightweight<TEntity>()
             where TEntity : IEntityWithRelationships, IEntityWithKey, IEntityWithChangeTracker
        {
            return (entity) => new LightweightEntityWrapper<TEntity>((TEntity)entity);
        }

        // Returns a delegate that creates a strategy-based wrapper for entities that implement IEntityWithRelationships
        private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedWithRelationships<TEntity>()
            where TEntity : IEntityWithRelationships
        {
            Func<object, IPropertyAccessorStrategy> propertyAccessorStrategy;
            Func<object, IEntityKeyStrategy> keyStrategy;
            Func<object, IChangeTrackingStrategy> changeTrackingStrategy;
            CreateStrategies<TEntity>(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy);
            
            return (entity) => new EntityWrapperWithRelationships<TEntity>((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy);
        }

        // Returns a delegate that creates a strategy-based wrapper for entities that do not implement IEntityWithRelationships
        private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedWithoutRelationships<TEntity>()
        {
            Func<object, IPropertyAccessorStrategy> propertyAccessorStrategy;
            Func<object, IEntityKeyStrategy> keyStrategy;
            Func<object, IChangeTrackingStrategy> changeTrackingStrategy;
            CreateStrategies<TEntity>(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy);

            return (entity) => new EntityWrapperWithoutRelationships<TEntity>((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy);
        }

        // Creates delegates that create strategy objects appropriate for the type of entity.
        private static void CreateStrategies<TEntity>(out Func<object, IPropertyAccessorStrategy> createPropertyAccessorStrategy,
                                                      out Func<object, IChangeTrackingStrategy> createChangeTrackingStrategy,
                                                      out Func<object, IEntityKeyStrategy> createKeyStrategy)
        {
            Type entityType = typeof(TEntity);
            bool isIEntityWithRelationships = typeof(IEntityWithRelationships).IsAssignableFrom(entityType);
            bool isIEntityWithChangeTracker = typeof(IEntityWithChangeTracker).IsAssignableFrom(entityType);
            bool isIEntityWithKey = typeof(IEntityWithKey).IsAssignableFrom(entityType);
            bool isProxy = EntityProxyFactory.IsProxyType(entityType);

            if (!isIEntityWithRelationships || isProxy)
            {
                createPropertyAccessorStrategy = GetPocoPropertyAccessorStrategyFunc();
            }
            else
            {
                createPropertyAccessorStrategy = GetNullPropertyAccessorStrategyFunc();
            }

            if (isIEntityWithChangeTracker)
            {
                createChangeTrackingStrategy = GetEntityWithChangeTrackerStrategyFunc();
            }
            else
            {
                createChangeTrackingStrategy = GetSnapshotChangeTrackingStrategyFunc();
            }

            if (isIEntityWithKey)
            {
                createKeyStrategy = GetEntityWithKeyStrategyStrategyFunc();
            }
            else
            {
                createKeyStrategy = GetPocoEntityKeyStrategyFunc();
            }
        }

        /// <summary>
        /// Convenience function that gets the ObjectStateManager from the context and calls
        /// WrapEntityUsingStateManager.
        /// </summary>
        /// <param name="entity">the entity to wrap</param>
        /// <param name="context">the context in which the entity may exist, or null</param>
        /// <returns>a new or existing wrapper</returns>
        internal static IEntityWrapper WrapEntityUsingContext(object entity, ObjectContext context)
        {
            EntityEntry existingEntry;
            return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry);
        }

        /// <summary>
        /// Convenience function that gets the ObjectStateManager from the context and calls
        /// WrapEntityUsingStateManager.
        /// </summary>
        /// <param name="entity">The entity to wrap</param>
        /// <param name="context">The context in which the entity may exist, or null</param>
        /// <param name="existingEntry">Set to the existing state entry if one is found, else null</param>
        /// <returns>a new or existing wrapper</returns>
        internal static IEntityWrapper WrapEntityUsingContextGettingEntry(object entity, ObjectContext context, out EntityEntry existingEntry)
        {
            return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry);
        }

        /// <summary>
        /// Wraps an entity and returns a new wrapper, or returns an existing wrapper if one
        /// already exists in the ObjectStateManager or in a RelationshipManager associated with
        /// the entity.
        /// </summary>
        /// <param name="entity">the entity to wrap</param>
        /// <param name="context">the state manager  in which the entity may exist, or null</param>
        /// <returns>a new or existing wrapper</returns>
        internal static IEntityWrapper WrapEntityUsingStateManager(object entity, ObjectStateManager stateManager)
        {
            EntityEntry existingEntry;
            return WrapEntityUsingStateManagerGettingEntry(entity, stateManager, out existingEntry);
        }

        /// <summary>
        /// Wraps an entity and returns a new wrapper, or returns an existing wrapper if one
        /// already exists in the ObjectStateManager or in a RelationshipManager associated with
        /// the entity.
        /// </summary>
        /// <param name="entity">The entity to wrap</param>
        /// <param name="context">The state manager  in which the entity may exist, or null</param>
        /// <param name="existingEntry">The existing state entry for the given entity if one exists, otherwise null</param>
        /// <returns>A new or existing wrapper</returns>
        internal static IEntityWrapper WrapEntityUsingStateManagerGettingEntry(object entity, ObjectStateManager stateManager, out EntityEntry existingEntry)
        {
            Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
            IEntityWrapper wrapper = null;
            existingEntry = null;

            if (entity == null)
            {
                return NullEntityWrapper.NullWrapper;
            }
            // First attempt to find an existing wrapper in the ObjectStateMager.
            if (stateManager != null)
            {
                existingEntry = stateManager.FindEntityEntry(entity);
                if (existingEntry != null)
                {
                    return existingEntry.WrappedEntity;
                }
                if (stateManager.TransactionManager.TrackProcessedEntities)
                {
                    if (stateManager.TransactionManager.WrappedEntities.TryGetValue(entity, out wrapper))
                    {
                        return wrapper;
                    }
                }
            }
            // If no entity was found in the OSM, then check if one exists on an associated
            // RelationshipManager.  This only works where the entity implements IEntityWithRelationshops.
            IEntityWithRelationships entityWithRelationships = entity as IEntityWithRelationships;
            if (entityWithRelationships != null)
            {
                RelationshipManager relManager = entityWithRelationships.RelationshipManager;
                if (relManager == null)
                {
                    throw EntityUtil.UnexpectedNullRelationshipManager();
                }
                IEntityWrapper wrappedEntity = relManager.WrappedOwner;
                if (!Object.ReferenceEquals(wrappedEntity.Entity, entity))
                {
                    // This means that the owner of the RelationshipManager must have been set
                    // incorrectly in the call to RelationshipManager.Create().
                    throw EntityUtil.InvalidRelationshipManagerOwner();
                }
                return wrappedEntity;
            }
            else
            {
                // Finally look to see if the instance is a proxy and get the wrapper from the proxy
                EntityProxyFactory.TryGetProxyWrapper(entity, out wrapper);
            }

            // If we could not find an existing wrapper, then go create a new one
            if (wrapper == null)
            {
                IEntityWithKey withKey = entity as IEntityWithKey;
                wrapper = CreateNewWrapper(entity, withKey == null ? null : withKey.EntityKey);
            }
            if (stateManager != null && stateManager.TransactionManager.TrackProcessedEntities)
            {
                stateManager.TransactionManager.WrappedEntities.Add(entity, wrapper);
            }
            return wrapper;
        }

        /// <summary>
        /// When an entity enters Object Services that was retreived with NoTracking, it may not have certain fields set that are in many cases
        /// assumed to be present. This method updates the wrapper with a key and a context.
        /// </summary>
        /// <param name="wrapper">The wrapped entity</param>
        /// <param name="context">The context that will be using this wrapper</param>
        /// <param name="entitySet">The entity set this wrapped entity belongs to</param>
        internal static void UpdateNoTrackingWrapper(IEntityWrapper wrapper, ObjectContext context, EntitySet entitySet)
        {
            if (wrapper.EntityKey == null)
            {
                wrapper.EntityKey = context.ObjectStateManager.CreateEntityKey(entitySet, wrapper.Entity);
            }
            if (wrapper.Context == null)
            {
                wrapper.AttachContext(context, entitySet, MergeOption.NoTracking);
            }
        }

        /// <summary>
        /// Returns a func that will create a PocoPropertyAccessorStrategy object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IPropertyAccessorStrategy> GetPocoPropertyAccessorStrategyFunc()
        {
            return (object entity) => new PocoPropertyAccessorStrategy(entity);
        }

        /// <summary>
        /// Returns a func that will create a null IPropertyAccessorStrategy strategy object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IPropertyAccessorStrategy> GetNullPropertyAccessorStrategyFunc()
        {
            return (object entity) => null;
        }

        /// <summary>
        /// Returns a func that will create a EntityWithChangeTrackerStrategy object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IChangeTrackingStrategy> GetEntityWithChangeTrackerStrategyFunc()
        {
            return (object entity) => new EntityWithChangeTrackerStrategy((IEntityWithChangeTracker)entity);
        }

        /// <summary>
        /// Returns a func that will create a SnapshotChangeTrackingStrategy object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IChangeTrackingStrategy> GetSnapshotChangeTrackingStrategyFunc()
        {
            return (object entity) => SnapshotChangeTrackingStrategy.Instance;
        }

        /// <summary>
        /// Returns a func that will create a EntityWithKeyStrategy object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IEntityKeyStrategy> GetEntityWithKeyStrategyStrategyFunc()
        {
            return (object entity) => new EntityWithKeyStrategy((IEntityWithKey)entity);
        }

        /// <summary>
        /// Returns a func that will create a GetPocoEntityKeyStrategyFunc object for a given entity.
        /// </summary>
        /// <returns>The func to be used to create the strategy object.</returns>
        internal static Func<object, IEntityKeyStrategy> GetPocoEntityKeyStrategyFunc()
        {
            return (object entity) => new PocoEntityKeyStrategy();
        }
    }
}
