//---------------------------------------------------------------------
// <copyright file="EntityDataSourceWrapper.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Data;
using System.Configuration;
using System.ComponentModel;
using System.Data.Common;
using System.Data.Metadata.Edm;
using System.Reflection;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Collections.ObjectModel;
using System.Text;
using System.Web.UI;

namespace System.Web.UI.WebControls
{
    /// <summary>
    /// Wraps an entity displayed in the data source in an ICustomTypeDescriptor
    /// implementation that flattens complex types and exposes references.
    /// </summary>
    internal class EntityDataSourceWrapper : ICustomTypeDescriptor
    {
        private readonly EntityDataSourceWrapperCollection _collection;
        private readonly ObjectStateEntry _stateEntry;

        internal EntityDataSourceWrapper(EntityDataSourceWrapperCollection collection, object trackedEntity)
        {
            EntityDataSourceUtil.CheckArgumentNull(collection, "collection");
            EntityDataSourceUtil.CheckArgumentNull(trackedEntity, "trackedEntity");

            this._collection = collection;

            // retrieve state entry
            if (!this._collection.Context.ObjectStateManager.TryGetObjectStateEntry(trackedEntity, out _stateEntry))
            {
                throw new ArgumentException(Strings.ComponentNotFromProperCollection, "trackedEntity");
            }
        }

        /// <summary>
        /// Gets entity wrapped by this type descriptor.
        /// </summary>
        internal object WrappedEntity
        {
            get
            {
                return this._stateEntry.Entity;
            }
        }

        internal RelationshipManager RelationshipManager
        {
            get
            {
                return this._stateEntry.RelationshipManager;
            }
        }

        /// <summary>
        /// Gets collection containing this wrapper.
        /// </summary>
        internal EntityDataSourceWrapperCollection Collection
        {
            get { return this._collection; }
        }

        #region ICustomTypeDescriptor Implementation
        System.ComponentModel.AttributeCollection System.ComponentModel.ICustomTypeDescriptor.GetAttributes() { return new System.ComponentModel.AttributeCollection(); }
        string ICustomTypeDescriptor.GetClassName() { return null; }
        string ICustomTypeDescriptor.GetComponentName() { return null; }
        TypeConverter ICustomTypeDescriptor.GetConverter() { return null; }
        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return null; }
        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return null; }
        object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return null; }
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return null; }
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return null; }


        public PropertyDescriptorCollection GetProperties()
        {
            return ((ITypedList)this._collection).GetItemProperties(null);
        }

        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return ((ICustomTypeDescriptor)this).GetProperties();
        }

        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this.WrappedEntity;
        }
        #endregion ICustomTypeDescriptor Implementation

        /// <summary>
        /// Use this method to set the properties on the wrapped entity
        /// </summary>
        /// <param name="propertiesFromViewState"></param>
        /// <param name="wrapper"></param>
        /// <param name="overwriteSameValue"></param>
        internal void SetAllProperties(Dictionary<string, object> propertiesFromViewState, bool overwriteSameValue,
            ref Dictionary<string, Exception> propertySettingExceptionsCaught)
        {
            // We aggregate the reference descriptors rather than setting them directly
            // to account for compound keys (we need all components of the key to create
            // an EntityKey that can be set on the EntityReference)
            var referenceList = new List<KeyValuePair<EntityDataSourceReferenceKeyColumn, object>>();

            foreach (EntityDataSourceWrapperPropertyDescriptor descriptor in _collection.AllPropertyDescriptors)
            {
                // figure out which display name to match for this descriptor
                string displayName = descriptor.Column.DisplayName;

                // if we have a controlling column, use its display name instead
                if (descriptor.Column.ControllingColumn != null)
                {
                    displayName = descriptor.Column.ControllingColumn.DisplayName;
                }

                object value;
                if (propertiesFromViewState.TryGetValue(displayName, out value))
                {
                    // get all changed ReferencePropertyDescriptor from ViewState
                    EntityDataSourceReferenceKeyColumn referenceColumn = descriptor.Column as EntityDataSourceReferenceKeyColumn;

                    // convert the value as needed
                    object adjustedValue = EntityDataSourceUtil.ConvertType(value, descriptor.PropertyType, descriptor.DisplayName);

                    if (null != referenceColumn)
                    {
                        referenceList.Add(new KeyValuePair<EntityDataSourceReferenceKeyColumn, object>(
                                referenceColumn, adjustedValue));
                        continue;
                    }

                    if (overwriteSameValue || adjustedValue != descriptor.GetValue(this))
                    {
                        if (EntityDataSourceUtil.NullCanBeAssignedTo(descriptor.PropertyType) || null != adjustedValue)
                        {
                            try
                            {
                                descriptor.SetValue(this, adjustedValue);
                            }
                            catch (Exception e)
                            {
                                // The property descriptor uses reflection to set the property. Therefore, the inner exception contains the actual message.
                                Exception exceptionToThrow = e;
                                if (e.InnerException != null)
                                {
                                    exceptionToThrow = e.InnerException;
                                }
                                if (null == propertySettingExceptionsCaught)
                                {
                                    propertySettingExceptionsCaught = new Dictionary<string, Exception>();
                                }
                                propertySettingExceptionsCaught.Add(descriptor.DisplayName, exceptionToThrow);
                            }
                        }
                    }
                }
            }

            // aggregate setting for EntityKey
            SetEntityKeyProperties(referenceList, overwriteSameValue);
        }

        private void SetEntityKeyProperties(
            List<KeyValuePair<EntityDataSourceReferenceKeyColumn, object>> referenceList, bool overwriteSameValue)
        {
            EntityDataSourceUtil.CheckArgumentNull(referenceList, "referenceList");

            var groups = referenceList.GroupBy(r => r.Key.Group);

            foreach (var group in groups)
            {
                Dictionary<string, object> partialKeys = new Dictionary<string, object>();

                foreach (KeyValuePair<EntityDataSourceReferenceKeyColumn, object> reference in group)
                {
                    // convert the value as needed
                    EntityDataSourceReferenceKeyColumn column = reference.Key;
                    object keyValue = reference.Value;

                    if (null == keyValue)
                    {
                        partialKeys = null;
                        break;
                    }

                    partialKeys.Add(column.KeyMember.Name, keyValue);
                }

                // we only set the entitykey for once, although there might be more than one 
                // properties descriptor associated with the same entitykey
                group.Key.SetKeyValues(this, partialKeys);
            }
        }
    }
}
