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

namespace VCommon.Reflection
{
    public static class DynamicMemberAccess<TClass, TPropertyType>
    {
        private static readonly IDictionary<string, Func<TClass, TPropertyType>> CachedAccessors = new ConcurrentDictionary<string, Func<TClass, TPropertyType>>();
        private static readonly IDictionary<string, Func<TPropertyType>> CachedStaticAccessors = new ConcurrentDictionary<string, Func<TPropertyType>>();

        private static Func<TClass, TPropertyType> BuildInstanceAccessor(string memberName)
        {
            var pInstance = Expression.Parameter(typeof(TClass));
            var access = Expression.PropertyOrField(pInstance, memberName);
            var expression = Expression.Lambda(access, pInstance);

            var del = (Func<TClass, TPropertyType>)expression.Compile();
            return del;
        }

        private static Func<TPropertyType> BuildStaticAccessor(string memberName)
        {
            var member = (MemberInfo)typeof(TClass).GetProperty(memberName, BindingFlags.Static | BindingFlags.Public) ?? typeof(TClass).GetField(memberName, BindingFlags.Static | BindingFlags.Public);
            if (null == member) throw new ArgumentException("member not found of " + typeof(TClass).FullName, nameof(memberName));
            var access = Expression.MakeMemberAccess(null, member);
            var expression = Expression.Lambda(access);

            var del = (Func<TPropertyType>)expression.Compile();
            return del;
        }

        public static TPropertyType Read(TClass instance, string memberName)
        {
            if (false == CachedAccessors.TryGetValue(memberName, out var del))
            {
                del = BuildInstanceAccessor(memberName);
                CachedAccessors[memberName] = del;
            }

            return del(instance);
        }

        public static TPropertyType ReadStatic(string memberName)
        {
            if (false == CachedStaticAccessors.TryGetValue(memberName, out var del))
            {
                del = BuildStaticAccessor(memberName);
                CachedStaticAccessors[memberName] = del;
            }

            return del();
        }
    }
}
