//===============================================================================
// Microsoft patterns & practices Enterprise Library
// Core
//===============================================================================
// Copyright © Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Design;
using System.ComponentModel;

namespace Microsoft.Practices.EnterpriseLibrary.Common.Configuration
{
    /// <summary>
    /// Clones a <see cref="ConfigurationSection"/>.
    /// </summary>
    /// <remarks>
    /// This will perform a clone of a <see cref="ConfigurationSection"/> by evaluating each element in
    /// the <see cref="ElementInformation.Properties"/>.  If these properties are themselves <see cref="ConfigurationElement"/> they will 
    /// be cloned as well.
    /// 
    /// As <see cref="ConfigurationElementCollection"/> items do not expose the ability to add and remove, any configuration collections
    /// must implement <see cref="IMergeableConfigurationElementCollection"/> to be cloned.  If they do not implement this, they will be skipped
    /// during cloning. The enterprise library collections implement this interface and the cloner will properly handle the cloning 
    /// of <see cref="ConnectionStringSettingsCollection"/> and <see cref="KeyValueConfigurationCollection"/> with an internal wrapper that 
    /// implements <see cref="IMergeableConfigurationElementCollection"/> for these collections.
    /// </remarks>
    public class ConfigurationSectionCloner
    {
        ///<summary>
        /// Clones a <see cref="ConfigurationSection"/>
        ///</summary>
        ///<param name="section">The <see cref="ConfigurationSection"/> to clone.</param>
        ///<returns>A new, cloned <see cref="ConfigurationSection"/>.</returns>
        public ConfigurationSection Clone(ConfigurationSection section)
        {
            if (section == null) throw new ArgumentNullException("section");

            var clonedSection = (ConfigurationSection)Activator.CreateInstance(section.GetType());
            return (ConfigurationSection)CloneElement(section, clonedSection);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sourceElement"></param>
        /// <param name="targetElement"></param>
        /// <returns></returns>
        public static ConfigurationElement CloneElement(ConfigurationElement sourceElement, ConfigurationElement targetElement)
        {
            if (sourceElement is ICustomProviderData)
            {
                var targetAttributes = ((ICustomProviderData)targetElement).Attributes;
                var sourceAttributes = ((ICustomProviderData)sourceElement).Attributes;
                foreach (string key in sourceAttributes)
                {
                    targetAttributes.Add(key, sourceAttributes[key]);
                }
            }

            foreach (PropertyInformation property in sourceElement.ElementInformation.Properties)
            {
                
                if (property.ValueOrigin == PropertyValueOrigin.Default) continue;
                try
                {
                    if (property.Value == null) continue;
                }
                catch
                {
                    continue;
                }
                PropertyInformation targetProperty = targetElement.ElementInformation.Properties[property.Name];

                if (typeof(ConfigurationElement).IsAssignableFrom(property.Type) && typeof(ICloneableConfigurationElement).IsAssignableFrom(property.Type))
                {
                    targetProperty.Value = ((ICloneableConfigurationElement)property.Value).CreateFullClone();
                }
                if (typeof(ConfigurationElement).IsAssignableFrom(property.Type) && TypeDescriptor.GetAttributes(property.Type).OfType<CloneableConfigurationElementTypeAttribute>().Any())
                {
                    CloneableConfigurationElementTypeAttribute cloneableConfigurationElementTypeAttribute = TypeDescriptor.GetAttributes(property.Type).OfType<CloneableConfigurationElementTypeAttribute>().First();
                    ICloneableConfigurationElement cloneable = (ICloneableConfigurationElement)Activator.CreateInstance(cloneableConfigurationElementTypeAttribute.CloneableConfigurationElementType, property.Value);

                    targetProperty.Value = cloneable.CreateFullClone();
                }
                if (typeof(ConfigurationElementCollection).IsAssignableFrom(property.Type))
                {
                    ConfigurationElementCollection sourceCollection = (ConfigurationElementCollection)property.Value;
                    ConfigurationElementCollection targetCollection = (ConfigurationElementCollection)Activator.CreateInstance(sourceCollection.GetType());
                    targetCollection = CloneCollection(sourceCollection, targetCollection);

                    targetProperty.Value = targetCollection;
                }
                else if (typeof(ConfigurationElement).IsAssignableFrom(property.Type))
                {
                    ConfigurationElement sourceChildElement = (ConfigurationElement)property.Value;
                    ConfigurationElement targetChildElement = (ConfigurationElement)Activator.CreateInstance(sourceChildElement.GetType());

                    targetChildElement = CloneElement(sourceChildElement, targetChildElement);
                    targetProperty.Value = targetChildElement;
                }
                else
                {
                    targetProperty.Value = property.Value;
                }
            }

            return targetElement;
        }

        private static ConfigurationElementCollection CloneCollection(ConfigurationElementCollection sourceCollection, ConfigurationElementCollection targetCollection)
        {
            targetCollection = (ConfigurationElementCollection)CloneElement(sourceCollection, targetCollection);
            
            IMergeableConfigurationElementCollection mergeableSource = MergeableConfigurationCollectionFactory.GetCreateMergeableCollection(sourceCollection);
            IMergeableConfigurationElementCollection mergeableTarget = MergeableConfigurationCollectionFactory.GetCreateMergeableCollection(targetCollection);

            if (mergeableSource == null) return targetCollection;

            List<ConfigurationElement> targetCollectionContents = new List<ConfigurationElement>();

            foreach (ConfigurationElement sourceElement in sourceCollection)
            {
                ConfigurationElement targetElement;
                targetElement = CreateCopyOfCollectionElement(mergeableSource, sourceElement);
                targetElement = CloneElement(sourceElement, targetElement);

                targetCollectionContents.Add(targetElement);
            }

            mergeableTarget.ResetCollection(targetCollectionContents);

            targetCollection.EmitClear = sourceCollection.EmitClear;
            return targetCollection;
        }

        private static ConfigurationElement CreateCopyOfCollectionElement(IMergeableConfigurationElementCollection mergeableSource, ConfigurationElement sourceElement)
        {
            ConfigurationElement targetElement;
            Type sourceType = sourceElement.GetType();

            if (sourceElement is ICloneableConfigurationElement)
            {
                return ((ICloneableConfigurationElement)sourceElement).CreateFullClone();
            }
            if (TypeDescriptor.GetAttributes(sourceElement).OfType<CloneableConfigurationElementTypeAttribute>().Any())
            {
                CloneableConfigurationElementTypeAttribute cloneableConfigurationElementTypeAttribute = TypeDescriptor.GetAttributes(sourceElement).OfType<CloneableConfigurationElementTypeAttribute>().First();
                ICloneableConfigurationElement cloneable = (ICloneableConfigurationElement)Activator.CreateInstance(cloneableConfigurationElementTypeAttribute.CloneableConfigurationElementType, sourceElement);

                return cloneable.CreateFullClone();
            }

            targetElement = mergeableSource.CreateNewElement(sourceType);
            
            return targetElement;
        }
    }
}
