// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System.Linq.Expressions;

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;
using System.Runtime.CompilerServices;

using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

using IronPython.Runtime.Binding;
using IronPython.Runtime.Operations;

using AstUtils = Microsoft.Scripting.Ast.Utils;

namespace IronPython.Runtime.Types {
    public partial class PythonType {

        #region IFastInvokable Members

        /// <summary>
        /// Implements fast binding for user defined types.  This ensures that common highly dynamic
        /// scenarios will run fast (for instance creating new types repeatedly and only creating a limited
        /// number of instances of them).  It also gives better code sharing amongst different subclasses
        /// of the same types and improved startup time due to reduced code generation.
        /// </summary>
        FastBindResult<T> IFastInvokable.MakeInvokeBinding<T>(CallSite<T> site, PythonInvokeBinder binder, CodeContext context, object[] args) {
            ParameterInfo[] paramTypes = typeof(T).GetMethod("Invoke").GetParameters();
            if (paramTypes[2].ParameterType != typeof(object)) {
                // strongly typed to PythonType, we don't optimize these sites yet.
                return new FastBindResult<T>();
            }

            // common fast paths
            if (binder.Signature.IsSimple) {
                if (this == TypeCache.PythonType && args.Length == 1 && paramTypes[3].ParameterType == typeof(object)) {
                    return new FastBindResult<T>((T)(object)new Func<CallSite, CodeContext, object, object, object>(GetPythonType), true);
                } else if (this == TypeCache.Set && args.Length == 0) {
                    return new FastBindResult<T>((T)(object)new Func<CallSite, CodeContext, object, object>(EmptySet), true);
                } else if (this == TypeCache.Object && args.Length == 0) {
                    return new FastBindResult<T>((T)(object)new Func<CallSite, CodeContext, object, object>(NewObject), true);
                }
            }

            if (IsSystemType ||                          // limited numbers of these, just generate optimal code
                args.Length > 5 ||                       // and we only generate optimal code for a small number of calls.
                HasSystemCtor ||                         // __clrtype__ overridden and ctor doesn't take PythonType as 1st arg
                GetType() != typeof(PythonType) ||       // and we don't handle meta classes yet (they could override __call__)...
                HasAbstractMethods(context)) {           // presence of abstract methods always results in an exception
                return new FastBindResult<T>();
            }
            
            Type[] genTypeArgs = new Type[paramTypes.Length - 3]; // minus CallSite, CodeContext, type
            for (int i = 0; i < paramTypes.Length - 3; i++) {
                genTypeArgs[i] = paramTypes[i + 3].ParameterType;
            }

            FastBindingBuilderBase fastBindBuilder;
            if (genTypeArgs.Length == 0) {
                fastBindBuilder = new FastBindingBuilder(context, this, binder, typeof(T), genTypeArgs);
            } else {
                Type baseType;
                switch (genTypeArgs.Length) {
                    #region Generated Python Fast Type Caller Switch

                    // *** BEGIN GENERATED CODE ***
                    // generated by function: gen_fast_type_caller_switch from: generate_calls.py

                    case 1: baseType = typeof(FastBindingBuilder<>); break;
                    case 2: baseType = typeof(FastBindingBuilder<,>); break;
                    case 3: baseType = typeof(FastBindingBuilder<,,>); break;
                    case 4: baseType = typeof(FastBindingBuilder<,,,>); break;
                    case 5: baseType = typeof(FastBindingBuilder<,,,,>); break;

                    // *** END GENERATED CODE ***

                    #endregion
                    default:
                        throw new NotImplementedException();
                }

                fastBindBuilder = (FastBindingBuilderBase)Activator.CreateInstance(baseType.MakeGenericType(genTypeArgs), context, this, binder, typeof(T), genTypeArgs);
            }

            return new FastBindResult<T>((T)(object)fastBindBuilder.MakeBindingResult(), true);
        }

        private object GetPythonType(CallSite site, CodeContext context, object type, object instance) {
            if (type == TypeCache.PythonType) {
                if (instance?.GetType() == typeof(Int32)) {
                    return TypeCache.BigInteger;  // PEP 237: int/long unification (GH #52)
                } else {
                    return DynamicHelpers.GetPythonType(instance);
                }
            }

            return ((CallSite<Func<CallSite, CodeContext, object, object, object>>)site).Update(site, context, type, instance);
        }

        private object EmptySet(CallSite site, CodeContext context, object type) {
            if (type == TypeCache.Set) {
                return new SetCollection();
            }

            return ((CallSite<Func<CallSite, CodeContext, object, object>>)site).Update(site, context, type);
        }

        private object NewObject(CallSite site, CodeContext context, object type) {
            if (type == TypeCache.Object) {
                return new object();
            }

            return ((CallSite<Func<CallSite, CodeContext, object, object>>)site).Update(site, context, type);
        }

        /// <summary>
        /// Base class for doing fast type invoke binding.  Subclasses are created using
        /// reflection once during the binding.  The subclasses can then proceed to do
        /// the binding w/o using reflection.  Otherwise we'd have lots more reflection
        /// calls which would slow the binding up.
        /// </summary>
        abstract class FastBindingBuilderBase {
            private readonly CodeContext _context;
            private readonly PythonInvokeBinder _binder;
            private readonly PythonType _type;
            private readonly Type _siteType;
            private readonly Type[] _genTypeArgs;

            public FastBindingBuilderBase(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) {
                _context = context;
                _type = type;
                _binder = binder;
                _siteType = siteType;
                _genTypeArgs = genTypeArgs;
            }

            public virtual Delegate MakeBindingResult() {
                int version = _type.Version;

                PythonTypeSlot init, newInst;
                _type.TryResolveSlot(_context, "__new__", out newInst);
                _type.TryResolveSlot(_context, "__init__", out init);

                Delegate newDlg;
                
                if (newInst == InstanceOps.New) {
                    if (_genTypeArgs.Length > 0 && init == InstanceOps.Init) {
                        // new needs to report an error, we don't optimize for the error case.
                        return null;
                    }
                    newDlg = GetOrCreateFastNew();
                } else if (newInst.GetType() == typeof(staticmethod) && ((staticmethod)newInst)._func is PythonFunction) {
                    // need an extra argument to pass the class to __new__
                    newDlg = GetNewSiteDelegate(_binder.Context.Invoke(_binder.Signature.InsertArgument(Argument.Simple)), ((staticmethod)newInst)._func);
                } else {
                    // odd slot in __new__, we don't handle this.
                    newDlg = null;
                }

                if (newDlg == null) {
                    // can't optimize this method because we have no fast-new
                    return null;
                }

                return MakeDelegate(version, newDlg, _type.GetLateBoundInitBinder(_binder.Signature));
            }

            /// <summary>
            /// Gets or creates delegate for calling the constructor function.
            /// </summary>
            private Delegate GetOrCreateFastNew() {
                Delegate newDlg;
                lock (_fastBindCtors) {
                    Dictionary<Type, Delegate> fastBindCtors;
                    if (!_fastBindCtors.TryGetValue(_type.UnderlyingSystemType, out fastBindCtors)) {
                        fastBindCtors = _fastBindCtors[_type.UnderlyingSystemType] = new Dictionary<Type, Delegate>();
                    } else if (fastBindCtors.TryGetValue(_siteType, out newDlg)) {
                        return newDlg;
                    }

                    ConstructorInfo[] cis = _type.UnderlyingSystemType.GetConstructors();
                    if (cis.Length == 1 && cis[0].GetParameters().Length == 1 && cis[0].GetParameters()[0].ParameterType == typeof(PythonType)) {
                        ParameterExpression contextArg = Expression.Parameter(typeof(CodeContext));
                        ParameterExpression type = Expression.Parameter(typeof(object));
                        ParameterExpression[] args = new ParameterExpression[_genTypeArgs.Length + 2]; // CodeContext, callable func
                        args[0] = contextArg;
                        args[1] = type;

                        for (int i = 0; i < _genTypeArgs.Length; i++) {
                            // skip CallSite, CodeContext, target function when getting args.
                            args[i + 2] = Expression.Parameter(_genTypeArgs[i]);
                        }

                        newDlg = Expression.Lambda(
                            Expression.Convert(
                                Expression.New(cis[0], Expression.Convert(type, typeof(PythonType))),
                                typeof(object)
                            ),
                            args
                        ).Compile();

                        fastBindCtors[_siteType] = newDlg;
                    } else {
                        // we have overloads we could dispatch to, generate optimal code.
                        return null;
                    }
                }

                return newDlg;
            }

            protected abstract Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func);
            protected abstract Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder);
        }

        class FastBindingBuilder : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, object>(new NewSite(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, object>(
                            new FastTypeSite(
                                version,
                                (Func<CodeContext, object, object>)newDlg,
                                initBinder
                            ).CallTarget
                );
            }
        }

        class FastTypeSite {
            private readonly int _version;
            private readonly Func<CodeContext, object, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type);
                    _initSite.Target(_initSite, context, res);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, object>>)site).Update(site, context, type);
            }
        }

        class NewSite {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object type) {
                return _site.Target(_site, context, _target, type);
            }
        }

        #endregion

        #region Generated Python Fast Type Callers

        // *** BEGIN GENERATED CODE ***
        // generated by function: gen_fast_type_callers from: generate_calls.py


        class FastBindingBuilder<T0> : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, T0, object>(new NewSite<T0>(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, T0, object>(
                    new FastTypeSite<T0>(
                        version, 
                        (Func<CodeContext, object, T0, object>)newDlg,
                        initBinder
                    ).CallTarget
                );
            }
        }

        class FastTypeSite<T0> {
            private readonly int _version;
            private readonly Func<CodeContext, object, T0, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, T0, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, T0, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, T0, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type, T0 arg0) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type, arg0);
                    _initSite.Target(_initSite, context, res, arg0);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, object>>)site).Update(site, context, type, arg0);
            }
        }

        class NewSite<T0> {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, T0, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object typeOrInstance, T0 arg0) {
                return _site.Target(_site, context, _target, typeOrInstance, arg0);
            }
        }


        class FastBindingBuilder<T0, T1> : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, T0, T1, object>(new NewSite<T0, T1>(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, T0, T1, object>(
                    new FastTypeSite<T0, T1>(
                        version, 
                        (Func<CodeContext, object, T0, T1, object>)newDlg,
                        initBinder
                    ).CallTarget
                );
            }
        }

        class FastTypeSite<T0, T1> {
            private readonly int _version;
            private readonly Func<CodeContext, object, T0, T1, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, T0, T1, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, T0, T1, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, T0, T1, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type, T0 arg0, T1 arg1) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type, arg0, arg1);
                    _initSite.Target(_initSite, context, res, arg0, arg1);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, object>>)site).Update(site, context, type, arg0, arg1);
            }
        }

        class NewSite<T0, T1> {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, T0, T1, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object typeOrInstance, T0 arg0, T1 arg1) {
                return _site.Target(_site, context, _target, typeOrInstance, arg0, arg1);
            }
        }


        class FastBindingBuilder<T0, T1, T2> : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, T0, T1, T2, object>(new NewSite<T0, T1, T2>(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, object>(
                    new FastTypeSite<T0, T1, T2>(
                        version, 
                        (Func<CodeContext, object, T0, T1, T2, object>)newDlg,
                        initBinder
                    ).CallTarget
                );
            }
        }

        class FastTypeSite<T0, T1, T2> {
            private readonly int _version;
            private readonly Func<CodeContext, object, T0, T1, T2, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, T0, T1, T2, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type, T0 arg0, T1 arg1, T2 arg2) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type, arg0, arg1, arg2);
                    _initSite.Target(_initSite, context, res, arg0, arg1, arg2);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, object>>)site).Update(site, context, type, arg0, arg1, arg2);
            }
        }

        class NewSite<T0, T1, T2> {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object typeOrInstance, T0 arg0, T1 arg1, T2 arg2) {
                return _site.Target(_site, context, _target, typeOrInstance, arg0, arg1, arg2);
            }
        }


        class FastBindingBuilder<T0, T1, T2, T3> : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, T0, T1, T2, T3, object>(new NewSite<T0, T1, T2, T3>(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, object>(
                    new FastTypeSite<T0, T1, T2, T3>(
                        version, 
                        (Func<CodeContext, object, T0, T1, T2, T3, object>)newDlg,
                        initBinder
                    ).CallTarget
                );
            }
        }

        class FastTypeSite<T0, T1, T2, T3> {
            private readonly int _version;
            private readonly Func<CodeContext, object, T0, T1, T2, T3, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, T0, T1, T2, T3, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type, arg0, arg1, arg2, arg3);
                    _initSite.Target(_initSite, context, res, arg0, arg1, arg2, arg3);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, object>>)site).Update(site, context, type, arg0, arg1, arg2, arg3);
            }
        }

        class NewSite<T0, T1, T2, T3> {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object typeOrInstance, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
                return _site.Target(_site, context, _target, typeOrInstance, arg0, arg1, arg2, arg3);
            }
        }


        class FastBindingBuilder<T0, T1, T2, T3, T4> : FastBindingBuilderBase {
            public FastBindingBuilder(CodeContext context, PythonType type, PythonInvokeBinder binder, Type siteType, Type[] genTypeArgs) :
                base(context, type, binder, siteType, genTypeArgs) {
            }

            protected override Delegate GetNewSiteDelegate(PythonInvokeBinder binder, object func) {
                return new Func<CodeContext, object, T0, T1, T2, T3, T4, object>(new NewSite<T0, T1, T2, T3, T4>(binder, func).Call);
            }

            protected override Delegate MakeDelegate(int version, Delegate newDlg, LateBoundInitBinder initBinder) {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, object>(
                    new FastTypeSite<T0, T1, T2, T3, T4>(
                        version, 
                        (Func<CodeContext, object, T0, T1, T2, T3, T4, object>)newDlg,
                        initBinder
                    ).CallTarget
                );
            }
        }

        class FastTypeSite<T0, T1, T2, T3, T4> {
            private readonly int _version;
            private readonly Func<CodeContext, object, T0, T1, T2, T3, T4, object> _new;
            private readonly CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, object>> _initSite;

            public FastTypeSite(int version, Func<CodeContext, object, T0, T1, T2, T3, T4, object> @new, LateBoundInitBinder initBinder) {
                _version = version;
                _new = @new;
                _initSite = CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, object>>.Create(initBinder);
            }

            public object CallTarget(CallSite site, CodeContext context, object type, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
                PythonType pt = type as PythonType;
                if (pt != null && pt.Version == _version) {
                    object res = _new(context, type, arg0, arg1, arg2, arg3, arg4);
                    _initSite.Target(_initSite, context, res, arg0, arg1, arg2, arg3, arg4);

                    return res;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, object>>)site).Update(site, context, type, arg0, arg1, arg2, arg3, arg4);
            }
        }

        class NewSite<T0, T1, T2, T3, T4> {
            private readonly CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, object>> _site;
            private readonly object _target;

            public NewSite(PythonInvokeBinder binder, object target) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, object>>.Create(binder);
                _target = target;
            }

            public object Call(CodeContext context, object typeOrInstance, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
                return _site.Target(_site, context, _target, typeOrInstance, arg0, arg1, arg2, arg3, arg4);
            }
        }


        // *** END GENERATED CODE ***

        #endregion
    }

    /// <summary>
    /// Used when a type overrides __new__ with a Python function or other object
    /// that can return an arbitrary value.  If the return value is not the same type
    /// as the type which had __new__ then we need to lookup __init__ on the type
    /// and invoke it.  Also handles initialization for finalization when __del__
    /// is defined for the same reasons.
    /// </summary>
    class LateBoundInitBinder : DynamicMetaObjectBinder {
        private readonly PythonType _newType;           // the type __new__ was invoked on
        private readonly CallSignature _signature;      // the call signature for the call

        public LateBoundInitBinder(PythonType type, CallSignature signature) {
            _newType = type;
            _signature = signature;
        }

        /// <summary>
        /// target is the newly initialized value.
        /// args are the arguments to be passed to __init__
        /// </summary>
        public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args) {
            DynamicMetaObject codeContext = target;
            CodeContext context = (CodeContext)codeContext.Value;
            target = args[0];
            args = ArrayUtils.RemoveFirst(args);

            ValidationInfo valInfo = BindingHelpers.GetValidationInfo(target);

            Expression res;
            PythonType instType = DynamicHelpers.GetPythonType(target.Value);
            BindingRestrictions initRestrictions = BindingRestrictions.Empty;

            if (IronPython.Modules.Builtin.isinstance(target.Value, _newType) &&
                NeedsInitCall((CodeContext)codeContext.Value, instType, args.Length)) {

                // resolve __init__
                PythonTypeSlot init;
                instType.TryResolveSlot(context, "__init__", out init);

                if (init is PythonFunction) {
                    // avoid creating the bound method, just invoke it directly
                    Expression[] allArgs = new Expression[args.Length + 3];
                    allArgs[0] = codeContext.Expression;
                    allArgs[1] = AstUtils.WeakConstant(init);
                    allArgs[2] = target.Expression;
                    for (int i = 0; i < args.Length; i++) {
                        allArgs[3 + i] = args[i].Expression;
                    }

                    res = DynamicExpression.Dynamic(
                        context.LanguageContext.Invoke(_signature.InsertArgument(Argument.Simple)),
                        typeof(object),
                        allArgs
                    );
                } else if(init is BuiltinMethodDescriptor || init is BuiltinFunction) {
                    IList<MethodBase> targets;
                    if (init is BuiltinMethodDescriptor) {
                        targets = ((BuiltinMethodDescriptor)init).Template.Targets;
                    } else {
                        targets = ((BuiltinFunction)init).Targets;
                    }

                    PythonBinder binder = context.LanguageContext.Binder;

                    DynamicMetaObject initInvoke = binder.CallMethod(
                        new PythonOverloadResolver(
                            binder,
                            target,
                            args,
                            _signature,
                            codeContext.Expression
                        ),
                        targets,
                        BindingRestrictions.Empty
                    );

                    res = initInvoke.Expression;
                    initRestrictions = initInvoke.Restrictions;
                } else {
                    // some weird descriptor has been put in place for __init__, we need
                    // to call __get__ on it each time.
                    res = MakeDynamicInitInvoke(
                        context,
                        args,
                        Expression.Call(
                            typeof(PythonOps).GetMethod(nameof(PythonOps.GetInitSlotMember)),
                            codeContext.Expression,
                            Expression.Convert(AstUtils.WeakConstant(_newType), typeof(PythonType)),
                            Expression.Convert(AstUtils.WeakConstant(init), typeof(PythonTypeSlot)),
                            AstUtils.Convert(target.Expression, typeof(object))
                        ),
                        codeContext.Expression
                    );
                }
            } else {
                // returned something that isn't a subclass of the creating type
                // __init__ will not be run.
                res = AstUtils.Empty();
            }

            // check for __del__
            PythonTypeSlot delSlot;
            if (instType.TryResolveSlot(context, "__del__", out delSlot)) {
                res = Expression.Block(
                    res,
                    Expression.Call(
                        typeof(PythonOps).GetMethod(nameof(PythonOps.InitializeForFinalization)),
                        codeContext.Expression,
                        AstUtils.Convert(target.Expression, typeof(object))
                    )
                );
            }

            return BindingHelpers.AddDynamicTestAndDefer(
                this,
                new DynamicMetaObject(
                    Expression.Block(
                        res,
                        target.Expression
                    ),
                    target.Restrict(target.LimitType).Restrictions.Merge(initRestrictions)
                ),
                args,
                valInfo
            );
        }

        #region Generated Python Fast Init Max Args

        // *** BEGIN GENERATED CODE ***
        // generated by function: gen_fast_init_max_args from: generate_calls.py

        public const int MaxFastLateBoundInitArgs = 6;

        // *** END GENERATED CODE ***

        #endregion

        public override T BindDelegate<T>(CallSite<T> site, object[] args) {
            if (args.Length <= MaxFastLateBoundInitArgs) {
                CodeContext context = (CodeContext)args[0];
                object inst = args[1];
                PythonType instType = DynamicHelpers.GetPythonType(inst);

                PythonTypeSlot delSlot;
                PythonFunction initFunc = null;
                string callTarget = null;
                if (!instType.TryResolveSlot(context, "__del__", out delSlot)) {        // we don't have fast code for classes w/ finalizers

                    if (IronPython.Modules.Builtin.isinstance(inst, _newType) &&
                        NeedsInitCall((CodeContext)context, instType, args.Length)) {

                        PythonTypeSlot init;
                        instType.TryResolveSlot(context, "__init__", out init);

                        if (init is PythonFunction) {
                            // we can do a fast bind                        
                            callTarget = "CallTarget";
                            initFunc = (PythonFunction)init;
                        }
                    } else {
                        // we need no initialization, we can do a fast bind
                        callTarget = "EmptyCallTarget";
                    }
                }

                if (callTarget != null) {
                    int genArgCount = args.Length - 2;
                    PythonInvokeBinder binder = context.LanguageContext.Invoke(_signature.InsertArgument(Argument.Simple));
                    if (genArgCount == 0) {
                        FastInitSite fastSite = new FastInitSite(instType.Version, binder, initFunc);
                        if (callTarget == "CallTarget") {
                            return (T)(object)new Func<CallSite, CodeContext, object, object>(fastSite.CallTarget);
                        } else {
                            return (T)(object)new Func<CallSite, CodeContext, object, object>(fastSite.EmptyCallTarget);
                        }

                    } else {
                        Type[] genArgs = ArrayUtils.ConvertAll(typeof(T).GetMethod("Invoke").GetParameters(), x => x.ParameterType);
                        Type initSiteType;

                        switch (args.Length - 2) {
                            #region Generated Python Fast Init Switch

                            // *** BEGIN GENERATED CODE ***
                            // generated by function: gen_fast_init_switch from: generate_calls.py

                            case 1: initSiteType = typeof(FastInitSite<>); break;
                            case 2: initSiteType = typeof(FastInitSite<,>); break;
                            case 3: initSiteType = typeof(FastInitSite<,,>); break;
                            case 4: initSiteType = typeof(FastInitSite<,,,>); break;
                            case 5: initSiteType = typeof(FastInitSite<,,,,>); break;

                            // *** END GENERATED CODE ***

                            #endregion

                            default: throw new InvalidOperationException();
                        }

                        Type genType = initSiteType.MakeGenericType(ArrayUtils.ShiftLeft(genArgs, 3));
                        object initSiteInst = Activator.CreateInstance(genType, instType.Version, binder, initFunc);
                        return (T)(object)genType.GetMethod(callTarget).CreateDelegate(typeof(T), initSiteInst);
                    }
                }
            }

            return base.BindDelegate(site, args);
        }

        class FastInitSite {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, object>>)site).Update(site, context, inst);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object, object>>)site).Update(site, context, inst);
            }
        }


        #region Generated Python Fast Init Callers

        // *** BEGIN GENERATED CODE ***
        // generated by function: gen_fast_init_callers from: generate_calls.py


        class FastInitSite<T0> {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, T0, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object,  T0, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst, T0 arg0) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst, arg0);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, object>>)site).Update(site, context, inst, arg0);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst, T0 arg0) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, object>>)site).Update(site, context, inst, arg0);
            }
        }


        class FastInitSite<T0, T1> {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, T0, T1, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object,  T0, T1, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst, arg0, arg1);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, object>>)site).Update(site, context, inst, arg0, arg1);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, object>>)site).Update(site, context, inst, arg0, arg1);
            }
        }


        class FastInitSite<T0, T1, T2> {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, T0, T1, T2, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object,  T0, T1, T2, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst, arg0, arg1, arg2);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, object>>)site).Update(site, context, inst, arg0, arg1, arg2);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, object>>)site).Update(site, context, inst, arg0, arg1, arg2);
            }
        }


        class FastInitSite<T0, T1, T2, T3> {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, T0, T1, T2, T3, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object,  T0, T1, T2, T3, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst, arg0, arg1, arg2, arg3);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, T3, object>>)site).Update(site, context, inst, arg0, arg1, arg2, arg3);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, T3, object>>)site).Update(site, context, inst, arg0, arg1, arg2, arg3);
            }
        }


        class FastInitSite<T0, T1, T2, T3, T4> {
            private readonly int _version;
            private readonly PythonFunction _slot;
            private readonly CallSite<Func<CallSite, CodeContext, PythonFunction, object, T0, T1, T2, T3, T4, object>> _initSite;

            public FastInitSite(int version, PythonInvokeBinder binder, PythonFunction target) {
                _version = version;
                _slot = target;
                _initSite = CallSite<Func<CallSite, CodeContext, PythonFunction, object,  T0, T1, T2, T3, T4, object>>.Create(binder);
            }

            public object CallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
                IPythonObject pyObj = inst as IPythonObject;
                if (pyObj != null && pyObj.PythonType.Version == _version) {
                    _initSite.Target(_initSite, context, _slot, inst, arg0, arg1, arg2, arg3, arg4);
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, T3, T4, object>>)site).Update(site, context, inst, arg0, arg1, arg2, arg3, arg4);
            }

            public object EmptyCallTarget(CallSite site, CodeContext context, object inst, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
                IPythonObject pyObj = inst as IPythonObject;
                if ((pyObj != null && pyObj.PythonType.Version == _version) || DynamicHelpers.GetPythonType(inst).Version == _version) {
                    return inst;
                }

                return ((CallSite<Func<CallSite, CodeContext, object,  T0, T1, T2, T3, T4, object>>)site).Update(site, context, inst, arg0, arg1, arg2, arg3, arg4);
            }
        }


        // *** END GENERATED CODE ***

        #endregion


        private bool NeedsInitCall(CodeContext context, PythonType type, int argCount) {
            if (!type.HasObjectInit(context)) {
                return true;
            } else if (_newType.HasObjectNew(context)) {
                return argCount > 0;
            }

            return false;
        }

        private DynamicExpression MakeDynamicInitInvoke(CodeContext context, DynamicMetaObject[] args, Expression initFunc, Expression codeContext) {
            return DynamicExpression.Dynamic(
                context.LanguageContext.Invoke(_signature),
                typeof(object),
                ArrayUtils.Insert(
                    codeContext,
                    initFunc,
                    DynamicUtils.GetExpressions(args)
                )
            );
        }
    }
}
