// 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.Runtime.CompilerServices;

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

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

namespace IronPython.Runtime {
    public sealed partial class Method {
        Binding.FastBindResult<T> Binding.IFastInvokable.MakeInvokeBinding<T>(CallSite<T> site, Binding.PythonInvokeBinder binder, CodeContext context, object[] args) {
            // TODO: We can process any signature that isn't * or ** args for the 1st argument
            if (binder.Signature.IsSimple) {
                BaseMethodBinding binding = null;

                var selfBinder = GetSelfBinder(binder, context);

                if (args.Length == 0) {
                    binding = new MethodBinding(selfBinder);
                } else {
                    binding = GetMethodBinding<T>(selfBinder, GetTypeArgs<T>(), binding);
                }

                if (binding != null) {
                    return new FastBindResult<T>(
                        (T) (object) binding.GetSelfTarget(),
                        true
                    );
                }
            }

            return new Binding.FastBindResult<T>();
        }

        private static BaseMethodBinding GetMethodBinding<T>(Binding.PythonInvokeBinder binder, Type[] typeArgs, BaseMethodBinding binding) where T : class {
            #region Generated Python Selfless Method Caller Switch

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

            switch (typeArgs.Length) {
                case 1: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<>).MakeGenericType(typeArgs), binder); break;
                case 2: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,>).MakeGenericType(typeArgs), binder); break;
                case 3: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,>).MakeGenericType(typeArgs), binder); break;
                case 4: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,>).MakeGenericType(typeArgs), binder); break;
                case 5: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,>).MakeGenericType(typeArgs), binder); break;
                case 6: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 7: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 8: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 9: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 10: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 11: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,,,,,>).MakeGenericType(typeArgs), binder); break;
                case 12: binding = (BaseMethodBinding)Activator.CreateInstance(typeof(MethodBinding<,,,,,,,,,,,>).MakeGenericType(typeArgs), binder); break;
            }

            // *** END GENERATED CODE ***

            #endregion
            return binding;
        }

        private static Type[] GetTypeArgs<T>() where T : class {
            return ArrayUtils.ShiftLeft(ArrayUtils.ConvertAll(typeof(T).GetMethod("Invoke").GetParameters(), pi => pi.ParameterType), 3);
        }

        private static PythonInvokeBinder GetSelfBinder(Binding.PythonInvokeBinder binder, CodeContext context) {
            return context.LanguageContext.Invoke(
                new CallSignature(ArrayUtils.Insert(new Argument(ArgumentType.Simple), binder.Signature.GetArgumentInfos()))
            );
        }

        abstract class BaseMethodBinding {
            public abstract Delegate GetSelfTarget();
        }

        class MethodBinding : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, object>(SelfTarget);
            }
        }

        #region Generated Python Method Callers

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


        class MethodBinding<T0> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, object>> _site;

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

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
                }

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

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6, T7> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, object>> _site;

            public MethodBinding(PythonInvokeBinder binder) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, object>>.Create(binder);
            }

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, object>>)site).Update(site, context, target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
            }

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6, T7, T8> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, object>> _site;

            public MethodBinding(PythonInvokeBinder binder) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, object>>.Create(binder);
            }

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, object>>)site).Update(site, context, target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
            }

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, object>> _site;

            public MethodBinding(PythonInvokeBinder binder) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, object>>.Create(binder);
            }

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, object>>)site).Update(site, context, target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
            }

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, object>> _site;

            public MethodBinding(PythonInvokeBinder binder) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, object>>.Create(binder);
            }

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, object>>)site).Update(site, context, target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
            }

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, object>(SelfTarget);
            }
        }

        class MethodBinding<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> : BaseMethodBinding {
            private CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, object>> _site;

            public MethodBinding(PythonInvokeBinder binder) {
                _site = CallSite<Func<CallSite, CodeContext, object, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, object>>.Create(binder);
            }

            public object SelfTarget(CallSite site, CodeContext context, object target, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11) {
                if (target is Method self) {
                    return _site.Target(_site, context, self.__func__, self.__self__, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
                }

                return ((CallSite<Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, object>>)site).Update(site, context, target, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11);
            }

            public override Delegate GetSelfTarget() {
                return new Func<CallSite, CodeContext, object, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, object>(SelfTarget);
            }
        }

        // *** END GENERATED CODE ***

        #endregion
    }
}
