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

namespace ConsoleApp1
{
    // для работы с таблицами
    public class TableMapper<T>
    {
        private readonly Dictionary<string, PropertyHelper> props;

        public TableMapper()
        {
            var props = typeof(T).GetProperties();
            this.props = new Dictionary<string, PropertyHelper>(props.Length);
            foreach (var prop in props)
            {
                var helper = new PropertyHelper(prop);
                this.props.Add(helper.Id, helper);
            }

        }

        public int UpdateObjectFromTable(ref T obj, Table table, int rowIndex)
        {
            var updatesCount = 0;
            if (table.Rows.Count() <= rowIndex)
                return 0;

            var row = table.Rows.ElementAt(rowIndex);
            var headerIndex = 0;

            foreach (var header in table.Headers)
            {
                var headerId = PropertyHelper.MakeIdentity(header.PropertyType.Name, header.Name);
                if (props.ContainsKey(headerId))
                {
                    props[headerId].Set(obj, row.ElementAt(headerIndex));
                    updatesCount++;
                }
                headerIndex++;
            }
            return updatesCount;
        }
    }

    /// <summary>
    /// Ускоренный обработчик свойства
    /// </summary>
    class PropertyHelper
    {
        public PropertyHelper(PropertyInfo property)
        {
            PropertyName = property.Name;
            PropertyType = property.PropertyType;
            Id = MakeIdentity(property.PropertyType.Name, property.Name);

            var setter = property.SetMethod;
            var getter = property.GetMethod;

            var instanceExpression = Expression.Parameter(typeof(object), "instance");
            var argumentsExpression = Expression.Parameter(typeof(object[]), "arguments");
            var argumentExpressions = new List<Expression> { Expression.Convert(Expression.ArrayIndex(argumentsExpression, Expression.Constant(0)), PropertyType) };
            var callExpression = Expression.Call(Expression.Convert(instanceExpression, setter.ReflectedType), setter, argumentExpressions);
            Setter = Expression.Lambda<SetterDelegate>(callExpression, instanceExpression, argumentsExpression).Compile();

            callExpression = Expression.Call(Expression.Convert(instanceExpression, getter.ReflectedType), getter);
            Getter = Expression.Lambda<GetterDelegate>(Expression.Convert(callExpression, typeof(object)), instanceExpression).Compile();
        }

        public string PropertyName { get; }
        public Type PropertyType { get; }
        public string Id { get; }

        delegate void SetterDelegate(object instanse, object[] values);

        delegate object GetterDelegate(object instanse);

        SetterDelegate Setter { get; }
        GetterDelegate Getter { get; }

        void SetValues(object instance, params object[] values)
            => Setter(instance, values);

        public void Set(object instance, object value)
            => SetValues(instance, value);

        public object Get(object instance)
            => Getter(instance);

        public static string MakeIdentity(string propertyTypeName, string propertyName) => $"{propertyTypeName}_{propertyName}".ToLower();
    }
}