#region COPYRIGHT
//
//     THIS IS GENERATED BY TEMPLATE
//     
//     AUTHOR  :     ROYE
//     DATE       :     2010
//
//     COPYRIGHT (C) 2010, TIANXIAHOTEL TECHNOLOGIES CO., LTD. ALL RIGHTS RESERVED.
//
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Reflection.Emit
{
    /// <summary>
    /// 
    /// </summary>
    internal class MapEmitter : BaseEmitter
    {
        private readonly Type sourceType;
        private readonly MemberTypes sourceMemberTypes;
        private readonly MemberTypes targetMemberTypes;
        private readonly string[] names;

        /// <summary>
        /// 初始化实例 <see cref="MapEmitter"/> class.
        /// </summary>
        /// <param name="sourceType">Type of the source.</param>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="sourceMemberTypes">The source member types.</param>
        /// <param name="targetMemberTypes">The target member types.</param>
        /// <param name="bindingFlags">The binding flags.</param>
        /// <param name="names">The names.</param>
        public MapEmitter(Type sourceType, Type targetType, MemberTypes sourceMemberTypes, MemberTypes targetMemberTypes, Flags bindingFlags, params string[] names)
            : base(new CallInfo(targetType,
                // Auto-apply IgnoreCase if we're mapping from one membertype to another
                Flags.SetIf(bindingFlags, Flags.IgnoreCase, (sourceMemberTypes & targetMemberTypes) != sourceMemberTypes),
                MemberTypes.Custom,
                "Fasterflect_Map",
                Type.EmptyTypes,
                null))
        {
            this.sourceType = sourceType;
            this.sourceMemberTypes = sourceMemberTypes;
            this.targetMemberTypes = targetMemberTypes;
            this.names = names;
        }

        /// <summary>
        /// Gets the cache key.
        /// </summary>
        /// <returns></returns>
        protected internal override int GetCacheKey()
        {
            int key = ((sourceType.GetHashCode() << 32) + CallInfo.TargetType.GetHashCode()) ^
                           (CallInfo.BindingFlags.GetHashCode() ^ sourceMemberTypes.GetHashCode() ^ targetMemberTypes.GetHashCode());
            if (names != null && names.Length > 0)
            {
                for (int index = 0; index < names.Length; index++)
                {
                    var name = names[index];
                    key += name.GetHashCode();
                }
            }
            return key;
        }

        /// <summary>
        /// Creates the dynamic method.
        /// </summary>
        /// <returns></returns>
        protected internal override DynamicMethod CreateDynamicMethod()
        {
            return CreateDynamicMethod(sourceType.Name, sourceType, null, new[] { Constants.ObjectType, Constants.ObjectType });
        }

        /// <summary>
        /// Creates the delegate.
        /// </summary>
        /// <returns></returns>
        protected internal override Delegate CreateDelegate()
        {
            bool handleInnerStruct = CallInfo.ShouldHandleInnerStruct;
            if (handleInnerStruct)
            {
                Generator.ldarg_1.end();                     // load arg-1 (target)
                Generator.DeclareLocal(CallInfo.TargetType); // TargetType localStr;
                Generator
                    .castclass(Constants.StructType) // (ValueTypeHolder)wrappedStruct
                    .callvirt(StructGetMethod) // <stack>.get_Value()
                    .unbox_any(CallInfo.TargetType) // unbox <stack>
                    .stloc(0); // localStr = <stack>
            }

            foreach (var pair in GetMatchingMembers())
            {
                if (handleInnerStruct)
                    Generator.ldloca_s(0).end(); // load &localStr
                else
                    Generator.ldarg_1.castclass(CallInfo.TargetType).end(); // ((TargetType)target)
                Generator.ldarg_0.castclass(sourceType);
                GenerateGetMemberValue(pair.Key);
                GenerateSetMemberValue(pair.Value);
            }

            if (handleInnerStruct)
            {
                StoreLocalToInnerStruct(1, 0);     // ((ValueTypeHolder)this)).Value = tmpStr
            }

            Generator.ret();
            return Method.CreateDelegate(typeof(ObjectMapper));
        }

        /// <summary>
        /// Generates the get member value.
        /// </summary>
        /// <param name="member">The member.</param>
        private void GenerateGetMemberValue(MemberInfo member)
        {
            if (member is FieldInfo)
            {
                Generator.ldfld((FieldInfo)member);
            }
            else
            {
                var method = ((PropertyInfo)member).GetGetMethod(true);
                Generator.callvirt(method, null);
            }
        }

        /// <summary>
        /// Generates the set member value.
        /// </summary>
        /// <param name="member">The member.</param>
        private void GenerateSetMemberValue(MemberInfo member)
        {
            if (member is FieldInfo)
            {
                Generator.stfld((FieldInfo)member);
            }
            else
            {
                var method = ((PropertyInfo)member).GetSetMethod(true);
                Generator.callvirt(method, null);
            }
        }

        /// <summary>
        /// Gets the matching members.
        /// </summary>
        /// <returns></returns>
        private IEnumerable<KeyValuePair<MemberInfo, MemberInfo>> GetMatchingMembers()
        {
            StringComparison comparison = CallInfo.BindingFlags.IsSet(Flags.IgnoreCase) ?
                StringComparison.OrdinalIgnoreCase :
                StringComparison.Ordinal;
            var query = from s in sourceType.Members(sourceMemberTypes, CallInfo.BindingFlags, names)
                        from t in CallInfo.TargetType.Members(targetMemberTypes, CallInfo.BindingFlags, names)
                        where s.Name.Equals(t.Name, comparison) &&
                              t.Type().IsAssignableFrom(s.Type()) &&
                              s.IsReadable() && t.IsWritable()
                        select new { Source = s, Target = t };
            return query.ToDictionary(k => k.Source, v => v.Target);
        }
    }
}
