// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Configuration.Internal;
using System.Reflection;
using System.Xml;

namespace System.Configuration
{
    internal sealed class RuntimeConfigurationRecord : BaseConfigurationRecord
    {
        private static readonly SimpleBitVector32 s_runtimeClassFlags = new SimpleBitVector32(
            ClassSupportsChangeNotifications
            | ClassSupportsRefresh
            | ClassSupportsImpersonation
            | ClassSupportsRestrictedPermissions
            | ClassSupportsDelayedInit);

        private RuntimeConfigurationRecord() { }

        protected override SimpleBitVector32 ClassFlags => s_runtimeClassFlags;

        internal static IInternalConfigRecord Create(
            InternalConfigRoot configRoot,
            IInternalConfigRecord parent,
            string configPath)
        {
            RuntimeConfigurationRecord configRecord = new RuntimeConfigurationRecord();
            configRecord.Init(configRoot, (BaseConfigurationRecord)parent, configPath, null);
            return configRecord;
        }

        // Create the factory that will evaluate configuration
        protected override object CreateSectionFactory(FactoryRecord factoryRecord)
        {
            return new RuntimeConfigurationFactory(this, factoryRecord);
        }

        // parentConfig contains the config that we'd merge with.
        protected override object CreateSection(bool inputIsTrusted, FactoryRecord factoryRecord,
            SectionRecord sectionRecord, object parentConfig, ConfigXmlReader reader)
        {
            // Get the factory used to create a section.
            RuntimeConfigurationFactory factory = (RuntimeConfigurationFactory)factoryRecord.Factory;

            // Use the factory to create a section.
            object config = factory.CreateSection(this, factoryRecord, sectionRecord, parentConfig,
                reader);

            return config;
        }

        protected override object UseParentResult(string configKey, object parentResult, SectionRecord sectionRecord)
        {
            return parentResult;
        }

        protected override object GetRuntimeObject(object result)
        {
            object runtimeObject;
            ConfigurationSection section = result as ConfigurationSection;
            if (section == null) runtimeObject = result;
            else
            {
                // Call into config section while impersonating process or UNC identity
                // so that the section could read files from disk if needed
                try
                {
                    runtimeObject = section.GetRuntimeObject();
                }
                catch (Exception e)
                {
                    throw new ConfigurationErrorsException(
                        SR.Format(SR.Config_exception_in_config_section_handler,
                            section.SectionInformation.SectionName), e);
                }
            }

            return runtimeObject;
        }

        private sealed class RuntimeConfigurationFactory
        {
            private ConstructorInfo _sectionCtor;
            private IConfigurationSectionHandler _sectionHandler;

            internal RuntimeConfigurationFactory(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord)
            {
                Init(configRecord, factoryRecord);
            }

            private void Init(RuntimeConfigurationRecord configRecord, FactoryRecord factoryRecord)
            {
                // Get the type of the factory
                Type type = TypeUtil.GetType(configRecord.Host, factoryRecord.FactoryTypeName,
                    true);

                // If the type is a ConfigurationSection, that's the type.
                if (typeof(ConfigurationSection).IsAssignableFrom(type))
                {
                    _sectionCtor = TypeUtil.GetConstructor(type, typeof(ConfigurationSection),
                        true);
                }
                else
                {
                    // Note: in v1, IConfigurationSectionHandler is in effect a factory that has a Create method
                    // that creates the real section object.

                    // throws if type does not implement IConfigurationSectionHandler
                    TypeUtil.VerifyAssignableType(typeof(IConfigurationSectionHandler), type, true);

                    // Create an instance of the handler
                    _sectionHandler =
                        (IConfigurationSectionHandler)TypeUtil.CreateInstance(type);
                }
            }

            // Throw an exception if an attribute within a legacy section is one of our
            // reserved locking attributes. We do not want admins to think they can lock
            // an attribute or element within a legacy section.
            private static void CheckForLockAttributes(string sectionName, XmlNode xmlNode)
            {
                XmlAttributeCollection attributes = xmlNode.Attributes;
                if (attributes != null)
                {
                    foreach (XmlAttribute attribute in attributes)
                        if (ConfigurationElement.IsLockAttributeName(attribute.Name))
                        {
                            throw new ConfigurationErrorsException(
                                SR.Format(SR.Config_element_locking_not_supported, sectionName), attribute);
                        }
                }

                foreach (XmlNode child in xmlNode.ChildNodes)
                    if (xmlNode.NodeType == XmlNodeType.Element) CheckForLockAttributes(sectionName, child);
            }

            internal object CreateSection(RuntimeConfigurationRecord configRecord,
                FactoryRecord factoryRecord, SectionRecord sectionRecord, object parentConfig, ConfigXmlReader reader)
            {
                object config;

                if (_sectionCtor != null)
                {
                    ConfigurationSection configSection =
                        (ConfigurationSection)_sectionCtor.Invoke(null);

                    configSection.SectionInformation.SetRuntimeConfigurationInformation(configRecord, factoryRecord,
                        sectionRecord);

                    configSection.CallInit();

                    ConfigurationSection parentSection = (ConfigurationSection)parentConfig;
                    configSection.Reset(parentSection);

                    if (reader != null) configSection.DeserializeSection(reader);

                    // throw if there are any cached errors
                    ConfigurationErrorsException errors = configSection.GetErrors();
                    if (errors != null) throw errors;

                    // don't allow changes to sections at runtime
                    configSection.SetReadOnly();

                    // reset the modified bit
                    configSection.ResetModified();

                    config = configSection;
                }
                else
                {
                    if (reader != null)
                    {
                        XmlNode xmlNode = ErrorInfoXmlDocument.CreateSectionXmlNode(reader);

                        CheckForLockAttributes(factoryRecord.ConfigKey, xmlNode);

                        // In v1, our old section handler expects a context that contains the virtualPath from the configPath
                        object configContext = configRecord.Host.CreateDeprecatedConfigContext(configRecord.ConfigPath);

                        config = _sectionHandler.Create(parentConfig, configContext, xmlNode);
                    }
                    else config = null;
                }

                return config;
            }
        }
    }
}
