using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Porta.Kundenzähler.Services.Extension
{
    public static class ConfigurationExtension
    {
        #region Configuration reading

        /// <summary>
        /// Read a section of the configuration and bind it to a corresponding object containing the same properties (Names must match)
        /// </summary>
        /// <typeparam name="T">The type of the config section object.</typeparam>
        /// <param name="configuration">The full configuration.</param>
        /// <param name="key">The key of the section.</param>
        /// <returns>The config section object.</returns>
        public static T GetSection<T>(this IConfiguration configuration, string key)
        {
            if (String.IsNullOrEmpty(key))
                throw new ArgumentException("The section key must not be NULL or empty.", key);

            // Create empty instance of config object type
            var optionsObj = Activator.CreateInstance<T>();

            var section = configuration.GetSection(key);

            if (section == null)
                throw new ArgumentException("The requested section cannot be found in configuration file.", key);

            // Map the object properties to the section properties
            MapSectionPropertiesToObject(configuration, optionsObj, section);

            return optionsObj;
        }

        /// <summary>
        /// Map the properties of a configuration object to the corresponding properties in the configuration section
        /// </summary>
        /// <param name="configuration">The full configuration.</param>
        /// <param name="optionsObj">The current options object.</param>
        /// <param name="currentSection">The current processing section.</param>
        private static void MapSectionPropertiesToObject(IConfiguration configuration, object optionsObj, 
            IConfigurationSection currentSection)
        {
            // Get all options object properties
            var properties = optionsObj.GetType().GetProperties();

            foreach (var propertyInfo in properties)
            {
                // Check non primitive, not value typed, only classed properties
                if (propertyInfo.PropertyType.IsClass
                        && !propertyInfo.PropertyType.IsArray
                        && !propertyInfo.PropertyType.IsValueType
                        && !propertyInfo.PropertyType.IsPrimitive
                        && propertyInfo.PropertyType.FullName != "System.String")
                {
                    // Get the section configuration value of the current property
                    var valueObj = currentSection.GetValue<object>(propertyInfo.Name);

                    // Proceed when value object can be created
                    if (valueObj != null)
                    {
                        // If the property is NULL, create an object for the property
                        if (propertyInfo.GetValue(optionsObj) == null)
                            propertyInfo.SetValue(optionsObj, Activator.CreateInstance(propertyInfo.PropertyType));

                        // Recursive call
                        MapSectionPropertiesToObject(configuration, propertyInfo.GetValue(optionsObj),
                            currentSection.GetSection(propertyInfo.Name));
                    }
                }
                else
                {
                    // Read the value from the current section
                    var valueObj = currentSection.GetValue<object>(propertyInfo.Name);

                    // If the value object is NULL then it might not be set or cannot be evaluated
                    if (valueObj != null)
                    {
                        // Serialize the value as a JSON string
                        var jsonStr = JsonConvert.SerializeObject(valueObj);

                        // Deserialize from the JSON string to prevent conversion issues
                        var value = JsonConvert.DeserializeObject(jsonStr, propertyInfo.PropertyType);

                        propertyInfo.SetValue(optionsObj, value);
                    }
                }
            }
        }

        #endregion
    }
}