/* **********************************************************************************
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *
 * This source code is subject to terms and conditions of the Shared Source License
 * for IronPython. A copy of the license can be found in the License.html file
 * at the root of this distribution. If you can not locate the Shared Source License
 * for IronPython, please send an email to ironpy@microsoft.com.
 * By using this source code in any fashion, you are agreeing to be bound by
 * the terms of the Shared Source License for IronPython.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * **********************************************************************************/

using System;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

using System.Diagnostics;
using IronPython.Compiler;
using IronPython.Modules;
using IronMath;

namespace IronPython.Runtime {
    /// <summary>
    /// ReflectedMethod's are unoptimized BuiltinFunction's.  They use late-bound
    /// invocation to call their target.
    /// 
    /// ReflectedMethod's also currently support certain functionality optimized
    /// methods don't. This includes:
    ///    
    /// context-aware calls
    /// generic-type binding
    /// TryCall
    /// 
    /// </summary>
    public partial class ReflectedMethodBase : BuiltinFunction, ITryCallable, ICallableWithCallerContext {
        protected BuiltinFunction optimizedTarget;

        #region Constructors

        protected ReflectedMethodBase() { }
        
        public ReflectedMethodBase(string name, MethodBase target)
            : base(name, new MethodBase[] { target }, FunctionType.PythonVisible) {
            UpdateFunctionInfo(target);
        }

        public ReflectedMethodBase(string name, MethodBase target, FunctionType functionType)
            : base(name, new MethodBase[] { target }, functionType) {
            UpdateFunctionInfo(target);
        }

        public ReflectedMethodBase(string name, MethodBase[] targets)
            : base(name, targets, FunctionType.None) {
            for (int i = 0; i < targets.Length; i++) {
                UpdateFunctionInfo(targets[i]);
            }
        }

        #endregion

        #region Public APIs
        public void AddMethod(MethodBase info) {
            if (targets != null) {
                MethodBase[] ni = new MethodBase[targets.Length + 1];
                targets.CopyTo(ni, 0);
                ni[targets.Length] = info;
                targets = ni;
            } else {
                targets = new MethodBase[] { info };
            }
            UpdateFunctionInfo(info);
        }

        public virtual bool TryCall(object[] args, out object ret) {
            return TryCallWorker(args, out ret);
        }
        
        #endregion

        #region Protected APIs

        protected virtual void OptimizeMethod() {
            BuiltinFunction optimized = ReflectOptimizer.MakeFunction(this);
            if (optimized != null) {
                Debug.Assert(optimized.Targets.Length == Targets.Length);

                ReflectedType declType = (ReflectedType)DeclaringType;
                declType.Initialize();

                object prevVal;
                SymbolId myId = SymbolTable.StringToId(Name);
                if (declType.dict.TryGetValue(myId, out prevVal)) {
                    ClassMethod cm;
                    if ((cm = prevVal as ClassMethod) != null) {
                        cm.func = optimized;
                    } else {
                        if (myId == SymbolTable.NewInst) declType.dict[myId] = declType.ctor = optimized;
                        else declType.dict[myId] = optimized.GetDescriptor();
                    }

                    optimizedTarget = optimized;
                }
            }
        }

        #endregion

        #region Callables

        [PythonName("__call__")]
        public override object Call(params object[] args) {
            object ret;
            if (TryCallWorker(args, out ret)) {
                return ret;
            }
            throw Ops.TypeError(string.Format("bad args to this method {0}", this));
        }


        [PythonName("__call__")]
        public override object Call(ICallerContext context, params object[] args) {
            if (IsContextAware) {
                object[] allArgs = new object[args.Length + 1];
                allArgs[0] = context;
                for (int i = 0; i < args.Length; i++) allArgs[i + 1] = args[i];
                return Call(allArgs);
            } else {
                return Call(args);
            }
        }

        #endregion

        #region Private implementation details
        private enum Better {
            None = 0,
            Right = 1,
            Left = -1,
        }

        private static object TryConvert(object value, Type to, out Conversion conversion) {
            if (to.IsByRef) {
                to = to.GetElementType();
            }
            return Converter.TryConvert(value, to, out conversion);
        }

        private static Better BetterBinding(ref MethodBinding l, ref MethodBinding r) {
            Better better = Better.None;

            if (l.ThisCallWithoutThis) {
                if (!r.ThisCallWithoutThis) {
                    return Better.Right;
                }
            } else {
                if (r.ThisCallWithoutThis) {
                    return Better.Left;
                }
            }

            if (l.instConversion > r.instConversion) {
                better = Better.Right;
            } else if (l.instConversion < r.instConversion) {
                better = Better.Left;
            }

            System.Diagnostics.Debug.Assert(l.conversions.Length == r.conversions.Length);

            ParameterInfo[] paramsL = null;
            ParameterInfo[] paramsR = null;
            for (int i = 0; i < l.conversions.Length; i++) {
                if (l.conversions[i] < r.conversions[i]) {
                    goto Better_Left;
                } else if (l.conversions[i] > r.conversions[i]) {
                    goto Better_Right;
                } else {
                    //
                    //  Only test types for implicit conversions
                    //
                    if (l.conversions[i] != Conversion.Implicit) {
                        continue;
                    }
                    if (i >= l.arguments.Length || i >= r.arguments.Length) {
                        continue;
                    }
                    if (paramsL == null) paramsL = l.method.GetParameters();
                    if (paramsR == null) paramsR = r.method.GetParameters();

                    Type typeL = paramsL[i].ParameterType;
                    Type typeR = paramsR[i].ParameterType;

                    if (typeL.IsAssignableFrom(typeR)) {
                        if (!typeR.IsAssignableFrom(typeL)) {
                            goto Better_Right;
                        }
                    } else {
                        if (typeR.IsAssignableFrom(typeL)) {
                            goto Better_Left;
                        }
                    }
                    // Still undecided
                    //
                    continue;
                }

            Better_Left:
                if (better == Better.Right) {
                    return Better.None;
                } else {
                    better = Better.Left;
                    continue;
                }
            Better_Right:
                if (better == Better.Left) {
                    return Better.None;
                } else {
                    better = Better.Right;
                    continue;
                }
            }

            //
            //  Method that did not pack parameters into parameter array wins
            //
            if (better == Better.None) {
                if ((l.flags & MethodBinding.MethodBindingSettings.ParamArray) != 0) {
                    if ((r.flags & MethodBinding.MethodBindingSettings.ParamArray) == 0) {
                        better = Better.Right;
                    }
                } else {
                    if ((r.flags & MethodBinding.MethodBindingSettings.ParamArray) != 0) {
                        better = Better.Left;
                    }
                }
            }

            return better;
        }

        private bool BindMethod(MethodBase method, object[] arguments, ref MethodBinding binding) {
            // Generic methods with unbound type parameters can't be the target of an invocation.
            if (method.ContainsGenericParameters)
                return false;
            
            ParameterInfo[] parameters = method.GetParameters();
            int argIndex = 0;

            binding.method = method;

            //
            //  Bind "this"
            //
            if (method.IsStatic || method.IsConstructor) {
                binding.instConversion = Conversion.Implicit;
                binding.instance = null;
            } else {
                object instance = Instance;
                if (instance == null) {
                    //
                    //  Use first argument as "this"
                    //
                    if (argIndex < arguments.Length) {
                        binding.flags |= MethodBinding.MethodBindingSettings.ArgIntoThis;
                        instance = arguments[argIndex++];
                    } else return false;
                }
                // InstanceArguments get used by BuiltinFunction's to disambiguate
                // between this & non-this calls.  We have enough context (Instance)
                // that we don't need it, so we'll just pull it out if one is there.
                InstanceArgument ia = instance as InstanceArgument;
                if (ia != null) instance = ia.ArgumentValue;

                binding.instance = TryConvert(instance, method.DeclaringType, out binding.instConversion);
                if (binding.instConversion == Conversion.None) {
                    return false;
                }

                if (method.IsVirtual &&
                    binding.instance.GetType().IsSubclassOf(method.DeclaringType)) {

                    // user is invoking a virtual method passing in a derived
                    // class but w/o a this pointer, eg:
                    // class foo(dict): 
                    //      def __setitem__(self, x, y): 
                    //          dict.__setitem__(self, x, y)

                    // we want to dispatch to the newtype's virtual base helper
                    // if possible.

                    ParameterInfo [] pis = method.GetParameters();
                    Type [] types = new Type[pis.Length];
                    for(int i = 0; i<pis.Length;i++){
                        types[i] = pis[i].ParameterType;
                    }
                    MethodInfo preferred = binding.instance.GetType().GetMethod("#base#"+method.Name, types);
                    if(preferred != null){
                        binding.method = preferred;
                    }
                }

                binding.flags |= MethodBinding.MethodBindingSettings.ThisCall;
            }

            //
            //  Bind remaining parameters
            //
            binding.arguments = new object[parameters.Length];
            binding.conversions = new Conversion[arguments.Length - argIndex];
            for (int parmIndex = 0, argConvIndex = 0; parmIndex < parameters.Length; parmIndex++) {
                ParameterInfo parameter = parameters[parmIndex];
                if (parameter.IsOut && !parameter.IsIn) {
                    continue;
                }
                if (ReflectionUtil.IsParamArray(parameter)) {

                    //
                    //  Pass the argument as is if:
                    //      - current argument is the last
                    //      - current argument type does matches the parameter array type
                    //
                    if (argIndex == arguments.Length - 1) {
                        binding.arguments[parmIndex] = TryConvert(arguments[argIndex], parameter.ParameterType,
                                                                  out binding.conversions[argConvIndex]);
                        if (binding.conversions[argConvIndex] <= Conversion.Implicit) {
                            argConvIndex++;
                            argIndex++;
                            continue;
                        }
                    }

                    //
                    //  Otherwise, pack the arguments into the array
                    //
                    Type elementType = parameter.ParameterType.GetElementType();
                    Array paramArray = Array.CreateInstance(elementType, arguments.Length - argIndex);
                    int convIndex = 0;
                    while (argIndex < arguments.Length) {
                        object converted = TryConvert(arguments[argIndex], elementType,
                                                      out binding.conversions[argConvIndex]);
                        if (binding.conversions[argConvIndex] == Conversion.None) {
                            return false;
                        }
                        paramArray.SetValue(converted, convIndex);
                        convIndex++;
                        argIndex++;
                        argConvIndex++;
                    }
                    binding.flags |= MethodBinding.MethodBindingSettings.ParamArray;
                    binding.arguments[parmIndex] = paramArray;
                } else {
                    if (argIndex >= arguments.Length) {
                        if (parameter.DefaultValue == DBNull.Value) return false;
                        binding.arguments[parmIndex] = parameter.DefaultValue;
                    } else {
                        binding.arguments[parmIndex] = TryConvert(arguments[argIndex], parameter.ParameterType,
                                                                  out binding.conversions[argConvIndex]);
                        if (binding.conversions[argConvIndex] == Conversion.None) return false;
                        argConvIndex++;
                        argIndex++;
                    }
                }
            }

            if (argIndex < arguments.Length) {
                return false;
            }
            return true;
        }

        private bool TryOptimizedCall(object[] args, out object ret) {
            // create the target if it doesn't already exist.  We cache the 
            // target incase the user stores the reflected method somewhere
            // and continues to call the unoptimized version.
            if (optimizedTarget == null) {
                OptimizeMethod();
            }

            if (optimizedTarget != null) {
                BuiltinFunction optimized = optimizedTarget;
                if (HasInstance) {
                    optimized = optimized.Clone();
                    optimized.inst = inst;
                }

                ret = optimized.Call(args);
                return true;
            }

            ret = null;
            return false;
        }

        private bool TryCallWorker(object[] args, out object ret) {
            // check to see if we can lazily optimize this function,
            // and if optimize it and call the optimized version.
            if ((FunctionType & FunctionType.OptimizeChecked) == 0) {
                if (TryOptimizedCall(args, out ret)) {
                    return true;
                }

                FunctionType |= FunctionType.OptimizeChecked;
            }

            PerfTrack.NoteEvent(PerfTrack.Categories.Methods, this);

            MethodBinding[] bindings = new MethodBinding[targets.Length];

            int candidate = 0;
            for (int i = 0; i < targets.Length; i++) {
                MethodBase method = targets[i];
                if (BindMethod(method, args, ref bindings[candidate])) {
                    candidate++;
                } else {
                    bindings[candidate].arguments = null;
                    bindings[candidate].conversions = null;
                    bindings[candidate].instance = null;
                    bindings[candidate].instConversion = Conversion.None;
                    bindings[candidate].method = null;
                    bindings[candidate].flags = 0;
                }
            }

            if (candidate > 0) {
                int best = 0;
                int competitor = 1;
                while (competitor < candidate) {
                    switch (BetterBinding(ref bindings[best], ref bindings[competitor])) {
                        case Better.None:
                            break;
                        case Better.Right:
                            best = competitor;
                            break;
                        case Better.Left:
                            break;
                    }
                    competitor++;
                }

                if (best < candidate) {
                    ret = Invoke(bindings[best]);
                    return true;
                }
            }
            ret = null;
            return false;
        }

  
        #endregion
    }

    public class ReflectedConstructor : ReflectedMethodBase {
        public ReflectedConstructor(string name, ConstructorInfo info) : base(name, info) { }

        public override string ToString() {
            return string.Format("<constructor# for {0}>", targets[0].DeclaringType.FullName);
        }

        #region Protected overrides

        protected override void OptimizeMethod() {
            BuiltinFunction optimized = ReflectOptimizer.MakeFunction(this);
            if (optimized != null) {
                ReflectedType declType = (ReflectedType)DeclaringType;
                if (declType.dict == null) declType.dict = new Dict();

                object newmeth;
                NewMethod newMethAsMeth;
                if (declType.dict.TryGetValue(SymbolTable.NewInst, out newmeth) && (newMethAsMeth = newmeth as NewMethod) != null) {
                    newMethAsMeth.Optimize(optimized);
                } else {
                    declType.dict[SymbolTable.NewInst] = optimized;
                    declType.ctor = optimized;
                }

                optimizedTarget = optimized;
            }
        }

        protected override bool HasInstance {
            get {
                return false;
            }
        }

        protected override object Instance {
            get {
                return null;
            }
        }

        #endregion      

    }

    public class ReflectedMethod : ReflectedMethodBase, IMapping, IContextAwareMember {
        [PythonName("__new__")]
        public static object MakeNew(object cls, object callable, object instance){
            return Ops.GetDescriptor(callable, instance, null);
        }

        public ReflectedMethod() { }

        public ReflectedMethod(string name, MethodInfo info, NameType nt) :
            base(name, info, nt == NameType.PythonMethod ? FunctionType.PythonVisible | FunctionType.Method : FunctionType.Method) {
        }

        public ReflectedMethod(string name, MethodInfo info, FunctionType funcType)
            :
            base(name, info, funcType) {
        }

        protected ReflectedMethod(string name, MethodBase[] infos, object instance, FunctionType funcType)
            : base(name, infos) {
            base.inst = instance;
            FunctionType = funcType;
        }

        public override BuiltinFunction Clone() {
            return new ReflectedMethod(Name, targets, null, FunctionType);
        }
               

        #region IContextAwareMember Members

        bool IContextAwareMember.IsVisible(ICallerContext context) {
            return (context.ContextFlags & CallerContextFlags.ShowCls) != 0 ||
                IsPythonVisible;
        }

        #endregion
    }

    public class ReflectedUnboundMethod : ReflectedMethod {
        public ReflectedUnboundMethod() { }

        public ReflectedUnboundMethod(string name, MethodInfo info, NameType nt) : base(name, info, nt) { }

        public ReflectedUnboundMethod(string name, MethodInfo info, FunctionType funcType) : base(name, info, funcType) { }

        private ReflectedUnboundMethod(string name, MethodBase[] infos, object instance, FunctionType funcType) : base(name, infos, instance, funcType) { }

        //!!! performance improvements by handling IFastCallable
        public override bool TryCall(object[] args, out object ret) {
            if (Instance == null) return base.TryCall(args, out ret);

            object[] nargs = new object[args.Length + 1];
            nargs[0] = Instance;
            args.CopyTo(nargs, 1);

            return base.TryCall(nargs, out ret);
        }

        public override object Call(params object[] args) {
            if (Instance == null) return base.Call(args);

            object[] nargs = new object[args.Length + 1];
            nargs[0] = Instance;
            args.CopyTo(nargs, 1);

            return base.Call(nargs);
        }

        protected override bool HasInstance {
            get {
                return false;
            }
        }

        public override BuiltinFunction Clone() {
            return new ReflectedUnboundMethod(Name, targets, null, FunctionType);
        }
       
    }

    public class ReflectedUnboundReverseOp : ReflectedMethod {
        public ReflectedUnboundReverseOp() { }

        public ReflectedUnboundReverseOp(string name, MethodInfo info, NameType nt)
            : base(name, info, nt) {
        }

        private ReflectedUnboundReverseOp(string name, MethodBase[] infos, object instance, FunctionType funcType)
            : base(name, infos, instance, funcType) {
        }

        public override bool TryCall(object[] args, out object ret) {
            if (Instance == null) return base.TryCall(args, out ret);

            object[] nargs = new object[args.Length + 1];
            args.CopyTo(nargs, 0);
            nargs[args.Length] = Instance;

            return base.TryCall(nargs, out ret);
        }

        public override object Call(params object[] args) {
            if (Instance == null) {
                return base.Call(args);
            }
            object[] nargs = new object[args.Length + 1];
            args.CopyTo(nargs, 0);
            nargs[args.Length] = Instance;

            return base.Call(nargs);
        }

        protected override bool HasInstance {
            get {
                return false;
            }
        }

        public override BuiltinFunction Clone() {
            return new ReflectedUnboundReverseOp(Name, targets, null, FunctionType);
        }       

    }

    // Used to map signatures to specific targets on the embedded reflected method.
    public class BuiltinFunctionOverloadMapper {

        private BuiltinFunction function;

        public BuiltinFunctionOverloadMapper(BuiltinFunction builtinFunction) {
            this.function = builtinFunction;
        }

        public override string ToString() {
            Dict overloadList = new Dict();
            foreach (MethodBase mb in function.Targets) {
                string key = ReflectionUtil.CreateAutoDoc(mb);
                overloadList[key] = function;
            }
            return overloadList.ToString();
        }

        public object this[object key] {
            get {
                // Retrieve the signature from the index.
                Type[] sig;
                Tuple sigTuple = key as Tuple;

                if (sigTuple != null) {
                    sig = new Type[sigTuple.Count];
                    for (int i = 0; i < sig.Length; i++) {
                        sig[i] = Converter.ConvertToType(sigTuple[i]);
                    }
                } else {
                    sig = new Type[] { Converter.ConvertToType(key) };
                }

                // We can still end up with more than one target since generic and non-generic
                // methods can share the same name and signature. So we'll build up a new
                // reflected method with all the candidate targets. A caller can then index this
                // reflected method if necessary in order to provide generic type arguments and
                // fully disambiguate the target.
                ReflectedMethod rm = new ReflectedMethod();
                rm.Name = function.Name;
                rm.FunctionType = function.FunctionType|FunctionType.OptimizeChecked; // don't allow optimization that would whack the real entry
                rm.inst = function.inst;

                // Search for targets with the right number of arguments.
                int args = sig.Length;
                foreach (MethodBase mb in function.Targets) {
                    ParameterInfo[] pis = mb.GetParameters();
                    if (pis.Length != args)
                        continue;

                    // Check each parameter type for an exact match.
                    bool match = true;
                    for (int i = 0; i < args; i++)
                        if (pis[i].ParameterType != sig[i]) {
                            match = false;
                            break;
                        }
                    if (!match)
                        continue;

                    // Okay, we have a match, add it to the list.
                    rm.AddMethod(mb);
                }
                if (rm.Targets == null)
                    throw Ops.TypeError("No match found for the method signature {0}", key);

                return rm;
            }
            set {
                throw new NotImplementedException();
            }
        }
    }
}
