// 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.Objects.DataClasses;

    // <summary>
    // Implementation of the change tracking strategy for entities that require snapshot change tracking.
    // These are typically entities that do not implement IEntityWithChangeTracker.
    // </summary>
    internal sealed class SnapshotChangeTrackingStrategy : IChangeTrackingStrategy
    {
        private static readonly SnapshotChangeTrackingStrategy _instance = new SnapshotChangeTrackingStrategy();

        // <summary>
        // Returns the single static instance of this class; a single instance is all that is needed
        // because the class is stateless.
        // </summary>
        public static SnapshotChangeTrackingStrategy Instance
        {
            get { return _instance; }
        }

        // Private constructor to help prevent additional instances being created.
        private SnapshotChangeTrackingStrategy()
        {
        }

        // See IChangeTrackingStrategy documentation
        public void SetChangeTracker(IEntityChangeTracker changeTracker)
        {
            // Nothing to do when using snapshots for change tracking
        }

        // See IChangeTrackingStrategy documentation
        public void TakeSnapshot(EntityEntry entry)
        {
            if (entry != null)
            {
                entry.TakeSnapshot(false);
            }
        }

        // See IChangeTrackingStrategy documentation
        public void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value)
        {
            // If the target is the entity, then this is a change to a member on the entity itself rather than
            // a change to some complex type property defined on the entity.  In this case we can use the change tracking
            // API in the normal way.
            if (ReferenceEquals(target, entry.Entity))
            {
                // equivalent of EntityObject.ReportPropertyChanging()
                ((IEntityChangeTracker)entry).EntityMemberChanging(member.CLayerName);
                member.SetValue(target, value);
                // equivalent of EntityObject.ReportPropertyChanged()
                ((IEntityChangeTracker)entry).EntityMemberChanged(member.CLayerName);

                if (member.IsComplex)
                {
                    // This is required because the OSE contains a separate cache of user objects for
                    // complex objects such that original values can be looked up correctly.
                    entry.UpdateComplexObjectSnapshot(member, target, ordinal, value);
                }
            }
            else
            {
                // Must be a complex type.  We would like to do this:
                // ((IEntityChangeTracker)entry).EntityComplexMemberChanging(topLevelMember.CLayerName, target, member.CLayerName);
                // ((IEntityChangeTracker)entry).EntityComplexMemberChanged(topLevelMember.CLayerName, target, member.CLayerName);
                //
                // However, we have no way of getting the topLevelMember.CLayerName.  This is because the value record does not
                // contain any reference to its parent.  (In non-POCO, ComplexObject takes care of this.)
                // Therefore, in this case we are going to just call a localized DetectChanges to make sure that changes in the
                // complex types are found.
                //
                // Note that this case only happens when the entity is POCO and complex types are set through the CurrentValues
                // object.  This is probably not a very common pattern.
                member.SetValue(target, value);
                if (entry.State
                    != EntityState.Added)
                {
                    // Entry is not Detached - checked in ValidateState() in EntityEntry.SetCurrentEntityValue
                    entry.DetectChangesInProperties(true);
                }
            }
        }

        // See IChangeTrackingStrategy documentation
        public void UpdateCurrentValueRecord(object value, EntityEntry entry)
        {
            // No change tracker, but may or may not be a proxy
            entry.UpdateRecordWithoutSetModified(value, entry.CurrentValues);
            entry.DetectChangesInProperties(false);
        }
    }
}
