//---------------------------------------------------------------------
// <copyright file="EntityDataSourceReferenceGroup.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.ComponentModel;
using System.Data.Common;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Data;
using System.Runtime.CompilerServices;

namespace System.Web.UI.WebControls
{
    /// <summary>
    /// Groups together reference columns pointing at the same association end.
    /// </summary>
    internal abstract class EntityDataSourceReferenceGroup
    {
        private readonly AssociationSetEnd end;

        protected EntityDataSourceReferenceGroup(AssociationSetEnd end)
        {
            EntityDataSourceUtil.CheckArgumentNull(end, "end");

            this.end = end;
        }

        internal AssociationSetEnd End { get { return this.end; } }

        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        internal static EntityDataSourceReferenceGroup Create(Type entityType, AssociationSetEnd end)
        {
            EntityDataSourceUtil.CheckArgumentNull(entityType, "entityType");

            Type groupType = typeof(EntityDataSourceReferenceGroup<>).MakeGenericType(entityType);
            return (EntityDataSourceReferenceGroup)Activator.CreateInstance(groupType, new object[] { end });
        }

        internal abstract void SetKeyValues(EntityDataSourceWrapper wrapper, Dictionary<string, object> newKeyValues);

        internal abstract EntityKey GetEntityKey(EntityDataSourceWrapper entity);
    }

    internal class EntityDataSourceReferenceGroup<T> : EntityDataSourceReferenceGroup
        where T : class
    {
        public EntityDataSourceReferenceGroup(AssociationSetEnd end)
            : base(end)
        {
        }

        internal override void SetKeyValues(EntityDataSourceWrapper wrapper, Dictionary<string, object> newKeyValues)
        {
            EntityDataSourceUtil.CheckArgumentNull(wrapper, "wrapper");

            EntityReference<T> reference = GetRelatedReference(wrapper);

            EntityKey originalEntityKeys = reference.EntityKey;

            
            if (null != newKeyValues)
            {
                if(null != originalEntityKeys)
                {
                    // mix the missing keys from the original values
                    foreach (var originalEntityKey in originalEntityKeys.EntityKeyValues)
                    {
                        object newKeyValue;
                        if (newKeyValues.TryGetValue(originalEntityKey.Key, out newKeyValue))
                        {
                            // if any part of the key is null, the EntityKey is null
                            if (null == newKeyValue)
                            {
                                newKeyValues = null;
                                break;
                            }
                        }
                        else
                        {
                            // add the original value for this partial key since it is not saved in the viewstate
                            newKeyValues.Add(originalEntityKey.Key, originalEntityKey.Value);
                        }
                    }
                }
                else
                {
                    // what we have in the newKeyValues should be sufficient to set the key
                    // but if any value is null, the whole key is null
                    foreach (var newKey in newKeyValues)
                    {
                        if (null == newKey.Value)
                        {
                            newKeyValues = null;
                            break;
                        }
                    }
                }
            }

            if (null == newKeyValues)
            {
                // if the entity key is a compound key, and if any partial key is null, then the entitykey is null
                reference.EntityKey = null;
            }
            else
            {
                reference.EntityKey = new EntityKey(EntityDataSourceUtil.GetQualifiedEntitySetName(End.EntitySet), (IEnumerable<KeyValuePair<string, object>>)newKeyValues);
            }
        }

        internal override EntityKey GetEntityKey(EntityDataSourceWrapper entity)
        {
            EntityKey key = GetRelatedReference(entity).EntityKey;
            return key;
        }

        private EntityReference<T> GetRelatedReference(EntityDataSourceWrapper entity)
        {
            RelationshipManager relationshipManager = entity.RelationshipManager;
            Debug.Assert(relationshipManager != null, "couldn't get a relationship manager");
            EntityReference<T> reference = relationshipManager.GetRelatedReference<T>(
                this.End.ParentAssociationSet.ElementType.FullName,
                this.End.CorrespondingAssociationEndMember.Name);
            return reference;
        }
    }
}
