using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Volpe.Cafe.Initializers
{
    /// <summary>
    /// Provides methods for initializing data objects, using reflection for automated assignment of the object's
    /// properties based on specific dictionary values.
    /// </summary>
    /// <remarks>
    /// <b>NOTE</b>:
    /// If needed, this class can be extended to support automated field/variable assignments as well.
    /// </remarks>
    internal static class DataObjectInitializer
    {
        private const string PropertiesNullError = "The list of property infos cannot be null.";
        private const string ValuesNullError = "The dictionary of property initialization values cannot be null.";
        private const string PropertiesValuesCountMismatch =
            "The number of desired properties to set in the BaselineInfo object ({0}) " +
            "does not match the number of supplied initialization values ({1}).";
        private const string PropertyNotSetError = "The property \"{0}\" could not be set.";

        private const BindingFlags BindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;


        public static void SetPropertyValues(object obj, Dictionary<string, object> values)
        {
            Type type = obj.GetType();
            var properties = type
                .GetProperties(BindingAttr)
                .Where(p => p.DeclaringType.GetProperty(p.Name, BindingAttr)?.GetSetMethod(true) != null)
                .ToList();

            SetPropertyValues(obj, properties, values);
        }

        public static void SetPropertyValues(
            object obj, List<PropertyInfo> properties, Dictionary<string, object> values)
        {
            if (properties == null) {
                throw new ArgumentNullException(PropertiesNullError);
            }
            if (values == null) {
                throw new ArgumentNullException(ValuesNullError);
            }
            if (properties.Count != values.Count) {
                throw new ArgumentException(
                    string.Format(PropertiesValuesCountMismatch, properties.Count, values.Count),
                    "properties|values");
            }

            foreach (var prop in properties) {
                if (values.ContainsKey(prop.Name)) {
                    prop.DeclaringType.GetProperty(prop.Name, BindingAttr).SetValue(obj, values[prop.Name]);
                }
                else {
                    throw new ArgumentException(
                        string.Format(PropertyNotSetError, prop.Name), "properties|values");
                }
            }
        }

    }
}
