// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
//  Contents:  MarkupObject and MarkupProperty implementation for 
//             DependencyObject
//
//
//  Class hierarchy in this file:
//
//      (MarkupObject)
//          ElementMarkupObject
//  
//      (MarkupProperty)
//          ElementPropertyBase
//              ElementObjectPropertyBase
//                  ElementProperty
//                  ElementPseudoPropertyBase
//                      ElementKey
//                      ElementConstructorArgument
//                      ElementItemsPseudoProperty
//                      ElementDictionaryItemsPseudoProperty
//          ElementStringValueProperty
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Security;
using System.Text;

using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Documents;

namespace System.Windows.Markup.Primitives 
{
    /// <summary>
    /// An implementation of MarkupObject for DependencyObjects that works also for CLR only objects
    /// </summary>
    internal class ElementMarkupObject : MarkupObject
    {
        internal ElementMarkupObject(object instance, XamlDesignerSerializationManager manager)
        {
            Debug.Assert(instance != null);
            _instance = instance;
            _context = new ElementObjectContext(this, null);
            _manager = manager;
        }

        public override Type ObjectType
        {
            get { return _instance.GetType(); }
        }

        public override object Instance
        {
            get { return _instance; }
        }

        internal override IEnumerable<MarkupProperty> GetProperties(bool mapToConstructorArgs)
        {
            ValueSerializer valueSerializer = ValueSerializer.GetSerializerFor(ObjectType, Context);
            if (valueSerializer != null && valueSerializer.CanConvertToString(_instance, Context))
            {
                yield return new ElementStringValueProperty(this);
                if (_key != null)
                {
                    yield return _key;
                }
            }
            else 
            {
                Dictionary<string, string> constructorArguments = null;

                // if this markup item is the value of property in attribute form and the instance
                // it represents is a MarkupExtension, then we return any properties that are 
                // constructor arguments as ElementConstructorArgument markup properties
                if (mapToConstructorArgs && _instance is MarkupExtension)
                {
                    ParameterInfo[] parameters;
                    ICollection arguments;
                    if(TryGetConstructorInfoArguments(_instance, out parameters, out arguments))
                    {
                        int i = 0;
                        foreach (object value in arguments)
                        {
                            if (constructorArguments == null)
                            {
                                constructorArguments = new Dictionary<string, string>();
                            }
                            // store a list of the parameters that are constructor arguments
                            // so that we don't return those properties again
                            constructorArguments.Add(parameters[i].Name, parameters[i].Name);

                            yield return new ElementConstructorArgument(value, parameters[i++].ParameterType, this);
                        }
                    }
                }
                foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(_instance))
                {
                    DesignerSerializationVisibility visibility = descriptor.SerializationVisibility;

                    if (visibility != DesignerSerializationVisibility.Hidden &&
                        (!descriptor.IsReadOnly || visibility == DesignerSerializationVisibility.Content) &&
                        ShouldSerialize(descriptor, _instance, _manager))
                    {
                        if (visibility == DesignerSerializationVisibility.Content)
                        {
                            // Ensure the collection has content
                            object value = descriptor.GetValue(_instance);
                            if (value == null)
                            {
                                continue;
                            }
                            else
                            {
                                ICollection collection = value as ICollection;
                                if (collection != null && collection.Count < 1)
                                {
                                    continue;
                                }
                                IEnumerable enumerable = value as IEnumerable;
                                if (enumerable != null && !enumerable.GetEnumerator().MoveNext())
                                {
                                    continue;
                                }
                            }
                        }
                        if (constructorArguments != null)
                        {
                            ConstructorArgumentAttribute constructorArgumentAttribute = descriptor.Attributes[typeof(ConstructorArgumentAttribute)] as ConstructorArgumentAttribute;
                            if (constructorArgumentAttribute != null && constructorArguments.ContainsKey(constructorArgumentAttribute.ArgumentName))
                            {
                                // Skip this property, it has already been represented by a constructor parameter
                                continue;
                            }
                        }
                        yield return new ElementProperty(this, descriptor);
                    }
                }
                IDictionary dictionary = _instance as IDictionary;
                if (dictionary != null)
                {
                    yield return new ElementDictionaryItemsPseudoProperty(dictionary, typeof(IDictionary), this);
                }
                else
                {
                    IEnumerable enumerable = _instance as IEnumerable;
                    if (enumerable != null && enumerable.GetEnumerator().MoveNext())
                    {
                        yield return new ElementItemsPseudoProperty(enumerable, typeof(IEnumerable), this);
                    }
                }
                    
                if (_key != null)
                {
                    yield return _key;
                }
            }
        }

        public override AttributeCollection Attributes
        {
            get { return TypeDescriptor.GetAttributes(ObjectType); }
        }

        public override void AssignRootContext(IValueSerializerContext context)
        {
            _context = new ElementObjectContext(this, context);
        }

        internal IValueSerializerContext Context
        {
            get { return _context; }
        }

        internal XamlDesignerSerializationManager Manager
        {
            get { return _manager; }
        }

        internal void SetKey(ElementKey key)
        {
            _key = key;
        }

        private sealed class ElementObjectContext : ValueSerializerContextWrapper, IValueSerializerContext
        {
            ElementMarkupObject _object;

            public ElementObjectContext(ElementMarkupObject obj, IValueSerializerContext baseContext): base(baseContext)
            {
                _object = obj;
            }

            object ITypeDescriptorContext.Instance
            {
                get 
                {
                    return _object.Instance;
                }
            }
        }

        private static bool ShouldSerialize(PropertyDescriptor pd, object instance, XamlDesignerSerializationManager manager)
        {
            MethodInfo shouldSerializeMethod;
            object invokeInstance = instance;

            DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(pd);
            if (dpd != null && dpd.IsAttached) 
            {
                Type ownerType = dpd.DependencyProperty.OwnerType;
                string propertyName = dpd.DependencyProperty.Name;
                string keyName = $"{propertyName}!";
                if (!TryGetShouldSerializeMethod(new ShouldSerializeKey(ownerType, keyName), out shouldSerializeMethod)) 
                {
                    string methodName = $"ShouldSerialize{propertyName}";
                    shouldSerializeMethod = ownerType.GetMethod(methodName, BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsObject, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = ownerType.GetMethod(methodName, BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsManager, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = ownerType.GetMethod(methodName, BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsMode, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = ownerType.GetMethod(methodName, BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsObjectManager, null);
                    if (shouldSerializeMethod != null && shouldSerializeMethod.ReturnType != typeof(bool))
                        shouldSerializeMethod = null;
                    CacheShouldSerializeMethod(new ShouldSerializeKey(ownerType, keyName), shouldSerializeMethod);
                }
                invokeInstance = null; // static method
            }
            else 
            {
                if (!TryGetShouldSerializeMethod(new ShouldSerializeKey(instance.GetType(), pd.Name), out shouldSerializeMethod)) 
                {
                    Type instanceType = instance.GetType();
                    string methodName = $"ShouldSerialize{pd.Name}";
                    shouldSerializeMethod = instanceType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsObject, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = instanceType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsManager, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = instanceType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsMode, null);
                    if (shouldSerializeMethod == null)
                        shouldSerializeMethod = instanceType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static |
                        BindingFlags.NonPublic | BindingFlags.Public, null, _shouldSerializeArgsObjectManager, null);
                    if (shouldSerializeMethod != null && shouldSerializeMethod.ReturnType != typeof(bool))
                        shouldSerializeMethod = null;
                    CacheShouldSerializeMethod(new ShouldSerializeKey(instanceType, pd.Name), shouldSerializeMethod);
                }
            }
            if (shouldSerializeMethod != null)
            {
                ParameterInfo[] parameters = shouldSerializeMethod.GetParameters();
                if (parameters != null)
                {
                    object[] args;
                    if (parameters.Length == 1)
                    {
                        if (parameters[0].ParameterType == typeof(DependencyObject))
                        {
                            args = new object[] { instance as DependencyObject };
                        }
                        else if (parameters[0].ParameterType == typeof(XamlWriterMode))
                        {
                            args = new object[] { manager.XamlWriterMode };
                        }
                        else
                        {
                            args = new object[] { manager };
                        }
                    }
                    else
                        args = new object[] { instance as DependencyObject, manager };
                    return (bool)shouldSerializeMethod.Invoke(invokeInstance, args);
                }
            }
            return pd.ShouldSerializeValue(instance);
        }

        private struct ShouldSerializeKey
        {
            public ShouldSerializeKey(Type type, string propertyName)
            {
                _type = type;
                _propertyName = propertyName;
            }

            public override bool Equals(object obj)
            {
                if (!(obj is ShouldSerializeKey)) return false;
                ShouldSerializeKey other = (ShouldSerializeKey)obj;
                return other._type == _type && other._propertyName == _propertyName;
            }


            /// <summary>
            ///     Forward to .Equals
            /// </summary>
            public static bool operator ==(ShouldSerializeKey key1, ShouldSerializeKey key2)
            {
                return key1.Equals(key2);
            }

            /// <summary>
            ///     Forward to .Equals
            /// </summary>
            public static bool operator !=(ShouldSerializeKey key1, ShouldSerializeKey key2)
            {
                return !(key1.Equals(key2));
            }

            public override int GetHashCode()
            {
                return _type.GetHashCode() ^ _propertyName.GetHashCode();
            }

            private Type _type;
            private string _propertyName;
        }

        private static bool TryGetShouldSerializeMethod(ShouldSerializeKey key, out MethodInfo methodInfo)
        {
            object value = _shouldSerializeCache[key];
            if (value == null || value == _shouldSerializeCacheLock)
            {
                // The _shouldSerializeCacheLock is used as a sentinal for null
                methodInfo = null;
                return value != null;
            }
            else
            {
                methodInfo = value as MethodInfo;
                return true;
            }
        }

        private static void CacheShouldSerializeMethod(ShouldSerializeKey key, MethodInfo methodInfo)
        {
            // The instance stored in _shouldSerializeCacheLock is used as a sentinal for null
            // The avoids having to perform two lookups in the Hashtable to detect a cached null value.
            object value = methodInfo == null ? _shouldSerializeCacheLock : methodInfo;
            lock (_shouldSerializeCacheLock)
            {
                _shouldSerializeCache[key] = value;
            }
        }
        
        private bool TryGetConstructorInfoArguments(object instance, out ParameterInfo[] parameters, out ICollection arguments)
        {        
            // Detect if the instance should be constructed using constructor parameters by
            // seeing if it can be converted to an instance descriptor that uses a constructor.
            TypeConverter converter = TypeDescriptor.GetConverter(instance);
            if (converter != null && converter.CanConvertTo(Context, typeof(InstanceDescriptor)))
            {
                InstanceDescriptor instanceDescriptor;
                try
                {
                    instanceDescriptor = converter.ConvertTo(_context, TypeConverterHelper.InvariantEnglishUS,
                        instance, typeof(InstanceDescriptor)) as InstanceDescriptor;
                }
                catch (InvalidOperationException)
                {
                    // If we get this just ignore the converter
                    instanceDescriptor = null;
                }
                catch (NotSupportedException)
                {
                    // If we get this just ignore the converter
                    instanceDescriptor = null;
                }
                
                if (instanceDescriptor != null)
                {            
                    ConstructorInfo ctorInfo = instanceDescriptor.MemberInfo as ConstructorInfo;
                    if (ctorInfo != null)
                    {
                        ParameterInfo[] ctorParameters = ctorInfo.GetParameters();
                        
                        if (ctorParameters != null && ctorParameters.Length == instanceDescriptor.Arguments.Count)
                        {
                            parameters = ctorParameters;
                            arguments = instanceDescriptor.Arguments;
                            return true;
                        }
                    }
                }
            }
            
            parameters = null;
            arguments = null;
            return false;
        }

        private static readonly object _shouldSerializeCacheLock = new object();
        private static Hashtable _shouldSerializeCache = new Hashtable();
        private static Type[] _shouldSerializeArgsObject = new Type[] { typeof(DependencyObject) };
        private static Type[] _shouldSerializeArgsManager = new Type[] { typeof(XamlDesignerSerializationManager) };
        private static Type[] _shouldSerializeArgsMode = new Type[] { typeof(XamlWriterMode) };
        private static Type[] _shouldSerializeArgsObjectManager = new Type[] { typeof(DependencyObject), typeof(XamlDesignerSerializationManager) };
        private static Attribute[] _propertyAttributes = new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) };

        private object _instance;
        private IValueSerializerContext _context;
        private ElementKey _key;
        private XamlDesignerSerializationManager _manager;
    }

    internal abstract class ElementPropertyBase : MarkupProperty
    {
        public ElementPropertyBase(XamlDesignerSerializationManager manager)
        {
            _manager = manager;
        }

        public override bool IsComposite
        {
            get
            {
                if (!_isCompositeCalculated)
                {
                    _isCompositeCalculated = true;
                    object value = Value;
                    
                    if (value == null)
                    {
                        _isComposite = true;
                    }
                    else if (value is string && PropertyType.IsAssignableFrom(typeof(object)))
                    {
                        _isComposite = false;
                    }
                    else if (value is MarkupExtension)
                    {
                        _isComposite = true;
                    }
                    else
                    {
                        _isComposite = !CanConvertToString(value);
                    }
                }
                return _isComposite;
            }
        }

        public override string StringValue
        {
            get
            {
                if (IsComposite)
                {
                    return String.Empty;
                }
                
                object value = Value;
                string stringValue = value as string;
                if (stringValue != null)
                {
                    return stringValue;
                }

                ValueSerializer serializer = GetValueSerializer();
                if (serializer == null)
                {
                    return String.Empty;
                }
                
                return serializer.ConvertToString(value, Context);
            }
        }

        public override IEnumerable<MarkupObject> Items
        {
            get
            {
                object value = Value;
                if (value != null)
                {
                    if (PropertyDescriptor != null && (PropertyDescriptor.IsReadOnly ||
                        (!PropertyIsAttached(PropertyDescriptor) && PropertyType == value.GetType() &&
                            // These types the serializer will synthesize a start element when
                            // reading them in
                            (typeof(IList).IsAssignableFrom(PropertyType) ||
                            typeof(IDictionary).IsAssignableFrom(PropertyType) ||
                            typeof(Freezable).IsAssignableFrom(PropertyType) ||
                            typeof(FrameworkElementFactory).IsAssignableFrom(PropertyType))) &&
                            HasNoSerializableProperties(value) &&
                            !IsEmpty(value)))
                    {
                        IDictionary dictionary = value as IDictionary;
                        if (dictionary != null)
                        {
                            Type keyType = GetDictionaryKeyType(dictionary);

                            // Attempt to emit the contents of the dictionary in a more deterministic
                            // order. This is not guarenteed to be deterministic because it uses ToString
                            // which is not guarenteed to be unique for each value. Keys with non-unique
                            // ToString() values will not be emitted deterministically.
                            DictionaryEntry[] entries = new DictionaryEntry[dictionary.Count];
                            dictionary.CopyTo(entries, 0);
                            Array.Sort(entries, delegate(DictionaryEntry one, DictionaryEntry two)
                            {
                                return String.Compare(one.Key.ToString(), two.Key.ToString());
                            });
                            foreach (DictionaryEntry entry in entries)
                            {
                                ElementMarkupObject subItem 
                                    = new ElementMarkupObject(
                                        ElementProperty.CheckForMarkupExtension(typeof(Object), entry.Value, Context, false), Manager);
                                
                                subItem.SetKey(new ElementKey(entry.Key, keyType, subItem));
                                yield return subItem;
                            }
                        }
                        else
                        {
                            IEnumerable items = value as IEnumerable;
                            if (items != null)
                            {
                                foreach (object o in items)
                                {
                                    MarkupObject subItem 
                                        = new ElementMarkupObject(
                                            ElementProperty.CheckForMarkupExtension(typeof(Object), o, Context, false), Manager);
                                    
                                    yield return subItem;
                                }
                            }
                            else
                            {
                                if (PropertyType == typeof(FrameworkElementFactory) && value is FrameworkElementFactory)
                                {
                                    MarkupObject subItem = new FrameworkElementFactoryMarkupObject(value as FrameworkElementFactory, Manager);
                                    yield return subItem;
                                }
                                else
                                {
                                    MarkupObject subItem 
                                        = new ElementMarkupObject(
                                            ElementProperty.CheckForMarkupExtension(typeof(Object), value, Context, true), Manager);
                                    
                                    yield return subItem;
                                }
                            }
                        }
                    }
                    else
                    {
                        MarkupObject subItem 
                            = new ElementMarkupObject(
                                ElementProperty.CheckForMarkupExtension(typeof(Object), value, Context, true), Manager);
                        
                        yield return subItem;
                    }
                }
                else
                {
                    MarkupObject subItem = new ElementMarkupObject(new System.Windows.Markup.NullExtension(), Manager);
                    yield return subItem;
                }
            }
        }

        private bool PropertyIsAttached(PropertyDescriptor descriptor) 
        {
            DependencyPropertyDescriptor dependencyPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(PropertyDescriptor);
            return dependencyPropertyDescriptor != null && dependencyPropertyDescriptor.IsAttached;
        }

        private bool HasNoSerializableProperties(object value)
        {
            if (value is FrameworkElementFactory)
                return true;
            ElementMarkupObject item = new ElementMarkupObject(value, Manager);
            foreach (MarkupProperty property in item.Properties)
            {
                if (!property.IsContent)
                {
                    return false;
                }
            }
            return true;
        }

        private bool IsEmpty(object value)
        {
            IEnumerable collection = value as IEnumerable;
            if (collection != null)
            {
                foreach (object o in collection)
                    return false;
                return true;
            }
            return false;
        }

        protected IValueSerializerContext Context
        {
            get
            {
                if (_context == null)
                {
                    _context = new ElementPropertyContext(this, GetItemContext());
                }
                return _context;
            }
        }

        internal XamlDesignerSerializationManager Manager
        {
            get { return _manager; }
        }

        static readonly List<Type> EmptyTypes = new List<Type>();

        public override IEnumerable<Type> TypeReferences
        {
            get
            {
                ValueSerializer serializer = GetValueSerializer();
                if (serializer == null)
                    return EmptyTypes;
                else
                    return serializer.TypeReferences(Value, Context);
            }
        }

        protected bool CanConvertToString( object value )
        {
            // See if this value can be converted to a string by a value serializer or type converter
            
            if( value == null )
            {
                return false;
            }
            
            ValueSerializer serializer = GetValueSerializer();
            return serializer != null && serializer.CanConvertToString(value, Context);
        }

        protected abstract IValueSerializerContext GetItemContext();
        protected abstract Type GetObjectType();

        private sealed class ElementPropertyContext : ValueSerializerContextWrapper, IValueSerializerContext
        {
            ElementPropertyBase _property;

            public ElementPropertyContext(ElementPropertyBase property, IValueSerializerContext baseContext)
                : base(baseContext)
            {
                _property = property;
            }

            PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
            {
                get
                {
                    return _property.PropertyDescriptor;
                }
            }
        }

        private ValueSerializer GetValueSerializer()
        {
            PropertyDescriptor descriptor = this.PropertyDescriptor;
            if (descriptor == null)
            {
                DependencyProperty property = this.DependencyProperty;
                if (property != null)
                    descriptor = DependencyPropertyDescriptor.FromProperty(property, GetObjectType());
            }
            if (descriptor != null)
                return ValueSerializer.GetSerializerFor(descriptor, GetItemContext());
            else
                return ValueSerializer.GetSerializerFor(PropertyType, GetItemContext());
        }

        private static Dictionary<Type, Type> _keyTypeMap;

        private static Type GetDictionaryKeyType(IDictionary value)
        {
            Type type = value.GetType();
            Type result;

            if (_keyTypeMap == null)
                _keyTypeMap = new Dictionary<Type, Type>();
            if (!_keyTypeMap.TryGetValue(type, out result))
            {
                foreach (Type interfaceType in type.GetInterfaces())
                {
                    if (interfaceType.IsGenericType)
                    {
                        Type genericTypeDefinition = interfaceType.GetGenericTypeDefinition();
                        if (genericTypeDefinition == typeof(System.Collections.Generic.IDictionary<,>))
                            return interfaceType.GetGenericArguments()[0];
                    }
                }
                result = typeof(object);
                _keyTypeMap[type] = result;
            }
            return result;
        }

        private bool _isComposite;
        private bool _isCompositeCalculated;
        private IValueSerializerContext _context;
        private XamlDesignerSerializationManager _manager;
    }

    internal abstract class ElementObjectPropertyBase : ElementPropertyBase
    {
        protected ElementObjectPropertyBase(ElementMarkupObject obj): base(obj.Manager)
        {
            Debug.Assert(obj != null);
            _object = obj;
        }

        protected override IValueSerializerContext GetItemContext()
        {
            return _object.Context;
        }

        protected override Type GetObjectType()
        {
            return _object.ObjectType;
        }

        protected readonly ElementMarkupObject _object;
    }

    /// <summary>
    /// An implementation of MarkupProperty for DependencyObjects that works also for CLR only objects
    /// </summary>
    internal class ElementProperty : ElementObjectPropertyBase
    {
        internal ElementProperty(ElementMarkupObject obj, PropertyDescriptor descriptor) : base(obj)
        {
            Debug.Assert(descriptor != null);
            _descriptor = descriptor;
        }

        public override string Name
        {
            get { return _descriptor.Name; }
        }

        public override Type PropertyType
        {
            get { return _descriptor.PropertyType; }
        }

        public override PropertyDescriptor PropertyDescriptor
        {
            get { return _descriptor; }
        }

        public override bool IsAttached
        {
            get 
            {
                UpdateDependencyProperty();
                return _isAttached; 
            }
        }

        public override DependencyProperty DependencyProperty
        {
            get
            {
                UpdateDependencyProperty();
                return _dependencyProperty;
            }
        }

        public override object Value
        {
            get {
                object value;

                DependencyProperty DP = DependencyProperty;

                // For DPs, we use ReadLocalValue to find the property, and handle
                // unset values.
                if (DP != null)
                {
                    DependencyObject DO = _object.Instance as DependencyObject;
                    Debug.Assert(DO != null);

                    // get the local value of the DP
                    value = DO.ReadLocalValue(DP);

                    // Expressions require special handling

                    Expression expression = value as Expression;
                    if (expression != null)
                    {
                        // Special-case: Expressions always get converted to an ME if 
                        // possible (and requested), and get de-referenced otherwise.
                        
                        TypeConverter converter = TypeDescriptor.GetConverter(value);
                        
                        if (Manager.XamlWriterMode == XamlWriterMode.Expression &&
                            converter.CanConvertTo(typeof(MarkupExtension)))
                        {
                            value = converter.ConvertTo(expression, typeof(MarkupExtension));
                        }
                        else
                        {
                            value = expression.GetValue(DO, DP);
                        }
                    }

                    // If the expression gave us UnsetValue, fall back to the default.
                    if (value == DependencyProperty.UnsetValue)
                    {
                        value = DP.GetDefaultValue(DO.DependencyObjectType);
                    }
                }

                // If this is the VisualTree property of a template, we have to do special processing.
                // Long-term plan is to replace this with an attribute on the type, that tells us how
                // to serialize its content.
                else if ((Name == "Template" || Name == "VisualTree")
                         &&
                         Context.Instance is FrameworkTemplate
                         &&
                         (Context.Instance as FrameworkTemplate).HasContent )
                {
                    // Instantiate the template, in un-optimized form.  This makes the template content
                    // load as if it were loaded from xaml outside a template.  The only exception is that
                    // TemplateBinding's will show up as a TemplateBindingExtension.
                    
                    value = (Context.Instance as FrameworkTemplate).LoadContent();
                }

                // Otherwise, get the value from the property descriptor.                
                else
                {
                    value = _descriptor.GetValue(_object.Instance);
                }


                // Convert the resulting value from above into a MarkupExtension, if necessary/possible,
                // but only if there is no value serializer; if the type supports conversion to both
                // string and ME, we give preference to the string conversion.

                if( !(value is MarkupExtension) && !CanConvertToString(value) )
                {
                    value = CheckForMarkupExtension(PropertyType, value, Context, true);
                }

                return value;
            }
        }

        public override AttributeCollection Attributes
        {
            get { return _descriptor.Attributes; }
        }

        private void UpdateDependencyProperty()
        {
            if (!_isDependencyPropertyCached)
            {
                DependencyPropertyDescriptor dpDesc = DependencyPropertyDescriptor.FromProperty(_descriptor);
                if (dpDesc != null) 
                {
                    _dependencyProperty = dpDesc.DependencyProperty;
                    _isAttached = dpDesc.IsAttached;
                }
                _isDependencyPropertyCached = true;
            }
        }

        //
        //  Check for values that can be converted into markup extensions, either because
        //  they are a well known type ((null, arrays, enums, and types), or because they
        //  can be type converted into an ME.
        //
        
        internal static object CheckForMarkupExtension(
                                    Type propertyType,
                                    object value, 
                                    IValueSerializerContext context, 
                                    bool convertEnums)
        {
            // null => NullExtension
            
            if (value == null)
            {
                return new NullExtension();
            }

            // See if the type has a type converter that can create a MarkupExtension
            // (Have to do this after the null check so that GetConverter doesn't get
            // an invalid argument.)

            TypeConverter converter = TypeDescriptor.GetConverter(value);
            if (converter.CanConvertTo(context, typeof(MarkupExtension)))
            {
                // The type provides a converter that creates a MarkupExtension.
                return converter.ConvertTo(context, TypeConverterHelper.InvariantEnglishUS, value, typeof(MarkupExtension));
            }

            // System.Type => TypeExtension
            
            Type type = value as Type;
            if (type != null)
            {
                // If the property is declared to be a type already, we don't need to convert it
                // into {x:Type} syntax.
                
                if( propertyType == typeof(Type) )
                {
                    return value;
                }

                return new TypeExtension(type);
            }

            // Enums => StaticExtension
            
            if (convertEnums)
            {
                Enum enumValue = value as Enum;
                if (enumValue != null)
                {
                    ValueSerializer typeSerializer = context.GetValueSerializerFor(typeof(Type));
                    Debug.Assert(typeSerializer != null, "typeSerializer for Enum was null");
                    string typeName = typeSerializer.ConvertToString(enumValue.GetType(), context);
                    return new StaticExtension($"{typeName}.{enumValue}");
                }
            }

            // Arrays => ArrayExtension
            
            Array array = value as Array;
            if (array != null)
            {
                return new ArrayExtension(array);
            }

            // Otherwise, value is unchanged.
            
            return value;
        }

        private PropertyDescriptor _descriptor;
        private bool _isDependencyPropertyCached;
        private DependencyProperty _dependencyProperty;
        private bool _isAttached;
    }

    /// <summary>
    /// Pseudo-property for strings passed to a type converter
    /// </summary>
    internal class ElementStringValueProperty : MarkupProperty
    {
        internal ElementStringValueProperty(ElementMarkupObject obj)
        {
            _object = obj;
        }

        public override string Name
        {
            get { return "StringValue"; }
        }

        public override Type PropertyType
        {
            get { return typeof(string); }
        }

        public override bool IsValueAsString
        {
            get { return true; }
        }

        public override object Value
        {
            get { return StringValue; }
        }

        public override string StringValue
        {
            get
            {
                ValueSerializer serializer = ValueSerializer.GetSerializerFor(_object.ObjectType, _object.Context);
                Debug.Assert(serializer != null && serializer.CanConvertToString(_object.Instance, _object.Context), 
                    "StringValue property value created for a type that cannot be converted to string");
                return serializer.ConvertToString(_object.Instance, _object.Context);
            }
        }

        public override IEnumerable<MarkupObject> Items
        {
            get
            {
                Debug.WriteLine("ElementMarkupObject.Items is not implemented");
                return null;
            }
        }

        public override AttributeCollection Attributes
        {
            get { return AttributeCollection.Empty; }
        }

        public override IEnumerable<Type> TypeReferences
        {
            get
            {
                ValueSerializer serializer = ValueSerializer.GetSerializerFor(_object.ObjectType, _object.Context);
                Debug.Assert(serializer != null && serializer.CanConvertToString(_object.Instance, _object.Context), 
                    "StringValue property value created for a type that cannot be converted to string");
                return serializer.TypeReferences(_object.Instance, _object.Context);
            }
        }

        private ElementMarkupObject _object;
    }

    /// <summary>
    /// Pseudo-property for the key of a dictionary element
    /// </summary>
    internal abstract class ElementPseudoPropertyBase : ElementObjectPropertyBase
    {
        internal ElementPseudoPropertyBase(object value, Type type, ElementMarkupObject obj) : base(obj)
        {
            _value = value;
            _type = type;
        }

        public override Type PropertyType
        {
            get { return _type; }
        }

        public override object Value
        {
            get { return ElementProperty.CheckForMarkupExtension(PropertyType, _value, Context, true /*convertEnums*/); }
        }

        public override AttributeCollection Attributes
        {
            get { return AttributeCollection.Empty; }
        }

        public override IEnumerable<Type> TypeReferences
        {
            get { return Array.Empty<Type>(); }
        }

        private object _value;
        private Type _type;
    }

    /// <summary>
    /// Pseudo-property for the key of a dictionary element
    /// </summary>
    internal class ElementKey : ElementPseudoPropertyBase
    {
        internal ElementKey(object value, Type type, ElementMarkupObject obj) : base(value, type, obj) { }

        public override string Name
        {
            get { return "Key"; }
        }

        public override bool IsKey
        {
            get { return true; }
        }
    }

    /// <summary>
    /// Pseudo-property for constructor arguments
    /// </summary>
    internal class ElementConstructorArgument : ElementPseudoPropertyBase
    {
        internal ElementConstructorArgument(object value, Type type, ElementMarkupObject obj) : base(value, type, obj) { }

        public override string Name
        {
            get { return "Argument"; }
        }

        public override bool IsConstructorArgument
        {
            get { return true;  }
        }
    }

    /// <summary>
    /// Pseudo-property for IEnumerable implementations
    /// </summary>
    internal class ElementItemsPseudoProperty : ElementPseudoPropertyBase
    {
        internal ElementItemsPseudoProperty(IEnumerable value, Type type, ElementMarkupObject obj) : base(value, type, obj) 
        {
            _value = value;
        }

        public override string Name
        {
            get { return "Items"; }
        }

        public override bool IsContent
        {
            get { return true; }
        }

        public override bool IsComposite
        {
            get { return true; }
        }

        public override IEnumerable<MarkupObject> Items
        {
            get 
            {
                foreach (object instance in _value)
                {
                    yield return new ElementMarkupObject(instance, Manager);
                }
            }
        }

        IEnumerable _value;
    }

    /// <summary>
    /// Pseudo-property for IDictionary implementation
    /// </summary>
    internal class ElementDictionaryItemsPseudoProperty : ElementPseudoPropertyBase
    {
        internal ElementDictionaryItemsPseudoProperty(IDictionary value, Type type, ElementMarkupObject obj) : base(value, type, obj) 
        {
            _value = value;
        }

        public override string Name
        {
            get { return "Entries"; }
        }

        public override bool IsContent
        {
            get { return true; }
        }

        public override bool IsComposite
        {
            get { return true; }
        }

        public override IEnumerable<MarkupObject> Items
        {
            get
            {
                foreach (DictionaryEntry entry in _value)
                {
                    ElementMarkupObject item = new ElementMarkupObject(entry.Value, Manager);
                    item.SetKey(new ElementKey(entry.Key, typeof(object), item));
                    yield return item;
                }
            }
        }

        IDictionary _value;
    }

    internal class ValueSerializerContextWrapper : IValueSerializerContext
    {
        IValueSerializerContext _baseContext;

        public ValueSerializerContextWrapper(IValueSerializerContext baseContext)
        {
            _baseContext = baseContext;
        }

        public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor)
        {
            if (_baseContext != null)
                return _baseContext.GetValueSerializerFor(descriptor);
            else
                return null;
        }

        public ValueSerializer GetValueSerializerFor(Type type)
        {
            if (_baseContext != null)
                return _baseContext.GetValueSerializerFor(type);
            else
                return null;
        }

        public IContainer Container
        {
            get
            {
                if (_baseContext != null)
                    return _baseContext.Container;
                else
                    return null;
            }
        }

        public object Instance
        {
            get
            {
                if (_baseContext != null)
                    return _baseContext.Instance;
                else
                    return null;
            }
        }

        public void OnComponentChanged()
        {
            if (_baseContext != null)
                _baseContext.OnComponentChanged();
        }

        public bool OnComponentChanging()
        {
            if (_baseContext != null)
                return _baseContext.OnComponentChanging();
            else
                return true;
        }

        public PropertyDescriptor PropertyDescriptor
        {
            get 
            {
                if (_baseContext != null)
                    return _baseContext.PropertyDescriptor;
                else
                    return null;
            }
        }

        public object GetService(Type serviceType)
        {
            if (_baseContext != null)
                return _baseContext.GetService(serviceType);
            else
                return null;
        }
    }
}
