/* **********************************************************************************
 *
 * 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.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.ComponentModel;
using System.Threading; 

using System.Resources;
using System.Diagnostics;
using System.Diagnostics.SymbolStore;

using System.Reflection;
using System.Reflection.Emit;

using IronPython.Runtime;

//  The IronPython.NewTypes psuedocode below is included just to show the type emitted for a user type object
#if PSUEDOCODE

namespace IronPython.NewTypes {

    // This is a dummy type declaration which shows what the type emitted for a user type object looks like.
    // One of these is created for every unique TypeInfo. This means that all pure Python types (those
    // that do not inherit from a CLI type or implement a CLI interface) use a single generated type
    // which is named "IronPython.NewTypes.System.Object".

    public class __MangledName :
        object, // [or CLI base type]
        // [CLI interfaces ... ,]
        ISuperDynamicObject,
        IDynamicObject,
        ICustomTypeDescriptor,
        IWeakReferenceable,
        IPythonEquality {
        // Points to the corresponding UserType that represents the Python type
        public UserType __class__;

        public IDictionary<SymbolID, object> __dict__;

        // #VTableNames# - generated by InitializeVTableStrings()
        public static string[] VTableNames = new string[] { "Equals", "GetHashCode", "_object__Finalize" };

        //
        // ISuperDynamicObject members generated by NewTypeMaker.ImplementDynamicObject()
        //

        #region ISuperDynamicObject members
        public IDictionary<SymbolID, object> GetDict() { return __dict__; }
        public void SetDict(IDictionary<SymbolID, object> dict) { __dict__ = dict; }
        public IDictionary<object, object> GetObjectKeyedDict() { return (IDictionary<object, object>)__dict__; }
        public void SetDynamicType(UserType newType) { __class__ = newType; }
        #endregion

        //
        // IDynamicObject members generated by NewTypeMaker.ImplementDynamicObject()
        //

#region IDynamicObject members
        public DynamicType GetDynamicType() { return __class__; }
#endregion

        //
        // ICustomTypeDescriptor members generated by NewTypeMaker.ImplementCustomTypeDescriptor()
        //

#region ICustomTypeDescriptor members
        public AttributeCollection GetAttributes() { return CustomTypeDescHelpers.GetAttributes(this); }
        public string GetClassName() { return CustomTypeDescHelpers.GetClassName(this); }
        public string GetComponentName() { return CustomTypeDescHelpers.GetComponentName(this); }
        public TypeConverter GetConverter() { return CustomTypeDescHelpers.GetConverter(this); }
        public EventDescriptor GetDefaultEvent() { return CustomTypeDescHelpers.GetDefaultEvent(this); }
        public PropertyDescriptor GetDefaultProperty() { return CustomTypeDescHelpers.GetDefaultProperty(this); }
        public object GetEditor(Type editorBaseType) { return CustomTypeDescHelpers.GetEditor(this, editorBaseType); }
        public EventDescriptorCollection GetEvents() { return CustomTypeDescHelpers.GetEvents(this); }
        public EventDescriptorCollection GetEvents(Attribute[] attributes) { return CustomTypeDescHelpers.GetEvents(this, attributes); }
        public PropertyDescriptorCollection GetProperties() { return CustomTypeDescHelpers.GetProperties(this); }
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes) { return CustomTypeDescHelpers.GetProperties(this, attributes); }
        public object GetPropertyOwner(PropertyDescriptor pd) { return CustomTypeDescHelpers.GetPropertyOwner(this, pd); }
#endregion

        //
        // IWeakReferenceable members generated by NewTypeMaker.ImplementWeakReference()
        //

#region IWeakReferenceable members
        public WeakRefTracker GetWeakRef() { return UserType.GetWeakRefHelper(this); }
        public void SetWeakRef(WeakRefTracker value) { UserType.SetWeakRefHelper(this, value); }
#endregion

        //
        // IPythonEquality members generated by NewTypeMaker.ImplementPythonEquals()
        //

#region IPythonEquality members
        public object PythonGetHashCode() { return UserType.PythonGetHashCodeHelper(this); }
        public object PythonEquals(object other) { return UserType.PythonEqualsHelper(this, other); }
#endregion

        //
        // Public constructor overrides of the CLI base type generated in NewTypeMaker.OverrideConstructor()
        //

#region Constructors
        __MangledName(UserType userType /* , <base ctor arguments> */)
            // : base(<base ctor arguments>)
        { __class__ = userType; }

        // Generated by GenerateOptimizedMaker. This is actually named MakeNew$$
        public static object MakeNew(object cls) {
            if (!(cls is UserType))
                throw Ops.InvalidType(cls, typeof(UserType).TypeHandle);
            return new __MangledName((UserType)cls /* , <base ctor arguments> */ );
        }
#endregion

        //
        // OverrideVirtualMethods
        //

#region Base virtual method overrides

        // CreateVirtualMethodOverride
        public override string ToString() {
            if (__class__.__str__F.IsBuiltinMethod()) 
                return (string)__class__.__str__F.Invoke(this);
            return IronPython.Compiler.NewTypeMaker.ToStringHelper(this);
        }
        public override bool Equals(object obj) {
            object value;
            if (__class__.GetNamespaceDictionary().TryGetNonInheritedValue(0, out value))
                return Ops.IsTrue(Ops.InvokeMethod(value, this.__class__, this, obj));
            return base.Equals(obj);
        }
        public override int GetHashCode() {
            object value;
            if (__class__.GetNamespaceDictionary().TryGetNonInheritedValue(1, out value))
                return (int)Ops.InvokeMethod(value, this.__class__, this);
            return base.GetHashCode();
        }
        ~__MangledName() {
            object value;
            if (__class__.GetNamespaceDictionary().TryGetNonInheritedValue(2, out value)) {
                Ops.IsTrue(Ops.InvokeMethod(value, this.__class__, this));
                return;
            }
        }

        // CreateVirtualMethodHelper. These are really named as #base#ToString, #base#Equals, etc.
        public string base_ToString() { return base.ToString(); }
        public bool base_Equals(__MangledName obj) { return base.Equals(obj); }
        public int base_GetHashCode() { return base.GetHashCode(); }
        public void base_Finalize() { /* base.Finalize(); */ }

#endregion
    }
}

#endif

namespace IronPython.Compiler {
    // Python class hierarchy is represented using the __class__ field in the object. It does not 
    // use the CLI type system for pure Python types. However, Python types which inherit from a 
    // CLI type, or from a builtin Python type which is implemented in the engine by a CLI type,
    // do have to use the CLI type system to interoperate with the CLI world. This means that 
    // objects of different Python types, but with the same CLI base type, can use the same CLI type - 
    // they will just have different values for the __class__ field.
    // TypeInfo captures the minimal CLI information required for a Python object.

    class TypeInfo {
        // The CLI base-type.
        private Type baseType;

        private IList<Type> interfaceTypes;
        private IList<OldClass> oldClassTypes;

        public TypeInfo(Type baseType, IList<Type> interfaceTypes, IList<OldClass> oldClasses) {
            this.baseType = baseType;
            this.interfaceTypes = interfaceTypes;
            this.oldClassTypes = oldClasses;
        }

        public Type BaseType {
            get { return baseType; }
        }

        public IEnumerable<Type> InterfaceTypes {
            get { return interfaceTypes; }
        }

        public IEnumerable<OldClass> OldClassTypes {
            get {
                return oldClassTypes;
            }
        }

        public override int GetHashCode() {
            return baseType.GetHashCode(); //??? might help hash to use interfaces too
        }

        public override bool Equals(object obj) {
            TypeInfo other = obj as TypeInfo;
            if (other == null) return false;

            if (baseType.Equals(other.baseType) && 
                interfaceTypes.Count == other.interfaceTypes.Count &&
                oldClassTypes.Count == other.oldClassTypes.Count) {
                for (int i = 0; i < interfaceTypes.Count; i++) {
                    if (!interfaceTypes[i].Equals(other.interfaceTypes[i])) return false;
                }

                for (int i = 0; i < oldClassTypes.Count; i++) {
                    if (!oldClassTypes[i].Equals(other.oldClassTypes[i])) return false;
                }

                return true;
            }
            return false;
        }
    }


    public class NewTypeMaker {
        public const string VtableNamesField = "#VTableNames#";
        public const string TypePrefix = "IronPython.NewTypes.";
        private static Publisher<TypeInfo, Type> newTypes = new Publisher<TypeInfo, Type>();
        private static int typeCount = 0;

        private TypeGen tg;
        private Slot typeField, dictField;
        private Type baseType;
        private IEnumerable<Type> interfaceTypes;
        private bool hasBaseTypeField = false;

        private Dictionary<string, VTableSlot> vtable = new Dictionary<string, VTableSlot>();

        public static Type GetNewType(string typeName, Tuple bases, IDictionary<object, object> dict) {
            // we're really only interested in the "correct" base type pulled out of bases
            // and any slot information contained in dict
            // other info might be used for future optimizations

            TypeInfo typeInfo = GetTypeInfo(typeName, bases);

            if (typeInfo.BaseType.IsSealed || typeInfo.BaseType.IsValueType)
                throw Ops.TypeError("cannot derive from sealed or value types");

            //!!! next collect any slot information, this might force a new Type creation

            return newTypes.GetOrCreateValue(typeInfo,
                delegate() {
                    // creation code
                    return new NewTypeMaker(typeName, typeInfo.BaseType, typeInfo.InterfaceTypes).CreateNewType();
                });
        }

        // "bases" contains a set of PythonTypes. These can include types defined in Python (say cpy1, cpy2),
        // CLI types (say cCLS1, cCLS2), and CLI interfaces (say iCLS1, iCLS2). Here are some
        // examples of how this works:
        //
        // (bases)                      => baseType,        {interfaceTypes}
        //
        // (cpy1)                       => System.Object,   {}
        // (cpy1, cpy2)                 => System.Object,   {}
        // (cpy1, cCLS1, iCLS1, iCLS2)  => cCLS1,           {iCLS1, iCLS2}
        // (cCLS1, cCLS2)               => error

        private static TypeInfo GetTypeInfo(string typeName, Tuple bases) {
            List<Type> interfaceTypes = new List<Type>();
            List<OldClass> oldClasses = new List<OldClass>();
            Type baseType = typeof(object); // Pure Python object instances inherit from System.Object
            PythonType baseTypeRt = null;

            foreach (object baseTypeObject in bases) {
                PythonType rt = baseTypeObject as PythonType;
                if (rt != null) {
                    Type ret = rt.GetTypeToExtend();
                    if (ret == null) throw Ops.TypeError(typeName + ": {0} is not an acceptable base type", rt);

                    if (ret == typeof(PythonType)) ret = typeof(UserType);
                    if (ret.IsInterface) {
                        interfaceTypes.Add(ret);
                    } else {
                        if (baseType != typeof(object)) {
                            throw Ops.TypeError(typeName + ": can only extend one CLI or builtin type, not both {0} (for {1}) and {2} (for {3})",
                                                baseType.FullName, baseTypeRt, ret.FullName, rt);
                        }
                        baseType = ret;
                        baseTypeRt = rt;
                    }
                    continue;
                } 
                
                OldClass oc = baseTypeObject as OldClass;
                if (oc != null) {
                    oldClasses.Add(oc);
                    continue;
                } 

                throw Ops.TypeError(typeName + ": unsupported base type for new-style class: " + baseType);                
            }
            return new TypeInfo(baseType, interfaceTypes, oldClasses);
        }

        private NewTypeMaker(String name, Type baseType, IEnumerable<Type> interfaceTypes) {
            this.baseType = baseType;
            this.interfaceTypes = interfaceTypes;
        }

        private static string GetBaseName(MethodInfo mi, Dictionary<string,bool> specialNames){
            Debug.Assert(mi.Name.StartsWith("#base#"));

            string newName = mi.Name.Substring(6);

            Debug.Assert(specialNames.ContainsKey(newName));

            if (specialNames[newName] == true) {
                if(newName == "get_Item") return "__getitem__";
                else if (newName == "set_Item") return "__setitem__";               
            }
            return newName;
        }

        // Coverts a well-known CLI name to its Python equivalent
        private static string NormalizeName(string name) {
            if (name == "ToString") return "__str__";
            return name;
        }

        // Build a name which is unique to this TypeInfo.
        string GetName() {
            StringBuilder name = new StringBuilder(baseType.FullName);
            foreach (Type interfaceType in interfaceTypes) {
                name.Append("#");
                name.Append(interfaceType.Name); 
            }

            name.Append("_");
            name.Append(System.Threading.Interlocked.Increment(ref typeCount));
            return name.ToString();
        }

        private Type CreateNewType() {
            AssemblyGen ag = OutputGenerator.Snippets;

            string name = GetName();
            tg = ag.DefinePublicType(TypePrefix + name, baseType);

            foreach (Type interfaceType in interfaceTypes) {
                tg.myType.AddInterfaceImplementation(interfaceType);
            }

            FieldInfo baseTypeField = baseType.GetField("__class__");
            if (baseTypeField == null) {
                typeField = tg.AddField(typeof(UserType), "__class__");
            } else {
                Debug.Assert(baseTypeField.FieldType == typeof(UserType));
                typeField = new FieldSlot(new ThisSlot(tg.myType), baseTypeField);
                hasBaseTypeField = true;
            }

            FieldInfo baseDictField = baseType.GetField("dict");
            if (baseDictField == null) {
                dictField = tg.AddField(typeof(IDictionary<SymbolId, object>), "__dict__"); 
            } else {
                dictField = new FieldSlot(new ThisSlot(tg.myType), baseDictField);
            }

            ImplementDynamicObject();

            ImplementCustomTypeDescriptor();

            ImplementPythonEquals();

            ImplementWeakReference();

            ConstructorInfo[] constructors;
            constructors = baseType.GetConstructors(BindingFlags.Public |
                                                    BindingFlags.NonPublic |
                                                    BindingFlags.Instance
                                                    );

            
            List<MethodTracker> newCtors = new List<MethodTracker>();
            foreach (ConstructorInfo ci in constructors) {
                if (!(ci.IsPublic || ci.IsFamily)) continue;

                ParameterInfo[] argTypes = new ParameterInfo[ci.GetParameters().Length + 1];
                
                argTypes[0] = new TrackingParamInfo(typeField.Type);
                Array.Copy(ci.GetParameters(), 0, argTypes, 1, argTypes.Length - 1);

                newCtors.Add(new MethodTracker(OverrideConstructor(ci), argTypes));
            }

            ReflectOptimizer.EmitFunction(tg, newCtors.ToArray());

            Dictionary<string, bool> specialNames = new Dictionary<string, bool>();

            OverrideVirtualMethods(baseType, specialNames);

            Dictionary<Type, bool> doneTypes = new Dictionary<Type, bool>();
            foreach (Type interfaceType in interfaceTypes) {                    
                DoInterfaceType(interfaceType, doneTypes, specialNames);
            }

            InitializeVTableStrings();

            //!!!Hashtable slots = collectSlots(dict, tg);
            //!!!if (slots != null) tg.createAttrMethods(slots);

            Type ret = tg.FinishType();

            AddBaseMethods(ret, specialNames);

            return ret;
        }

        private void AddBaseMethods(Type finishedType, Dictionary<string,bool> specialNames) {            
            // "Adds" base methods to super type (!!!should really add to the derived type)
            // this makes super(...).xyz to work - otherwise we'd return a function that
            // did a virtual call resulting in a stack overflow.
            ReflectedType rt = Ops.GetDynamicTypeFromType(baseType) as ReflectedType;
            rt.Initialize();

            foreach (MethodInfo mi in finishedType.GetMethods()) {
                string methodName = mi.Name;
                if (methodName.StartsWith("#base#")) {
                    string newName = GetBaseName(mi, specialNames);
                    rt.StoreReflectedBaseMethod(newName, mi, NameType.Method);                  
                }
            }
        }

        private void DoInterfaceType(Type interfaceType, Dictionary<Type, bool> doneTypes, Dictionary<string,bool> specialNames) {
            if (doneTypes.ContainsKey(interfaceType)) return;
            doneTypes.Add(interfaceType, true);
            OverrideVirtualMethods(interfaceType, specialNames);
            foreach (Type t in interfaceType.GetInterfaces()) {
                DoInterfaceType(t, doneTypes, specialNames);
            }
        }
        
        private ConstructorBuilder OverrideConstructor(ConstructorInfo parentConstructor) {
            ParameterInfo[] pis = parentConstructor.GetParameters();
            Type[] argTypes = new Type[pis.Length + 1];
            string[] paramNames = new string[pis.Length + 1];
            argTypes[0] = typeField.Type;
            paramNames[0] = "cls";
            for (int i = 0; i < pis.Length; i++) {
                argTypes[i + 1] = pis[i].ParameterType;
                paramNames[i + 1] = pis[i].Name;
            }
            int kwDict = -1, paramsArray = -1;

            ConstructorBuilder cb = tg.myType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, argTypes);

            cb.DefineParameter(1, ParameterAttributes.None, "userType");
            const int paramOffset = 2;  // +1 for user type, +1 for index from 1 of params.
            for (int i = 0; i < pis.Length; i++) {
                ParameterBuilder pb = cb.DefineParameter(i + paramOffset, pis[i].Attributes, pis[i].Name);

                if (pis[i].IsDefined(typeof(ParamArrayAttribute), false)) {
                    pb.SetCustomAttribute(new CustomAttributeBuilder(
                        typeof(ParamArrayAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
                    paramsArray = i + paramOffset;
                } else if (pis[i].IsDefined(typeof(ParamDictAttribute), false)) {
                    pb.SetCustomAttribute(new CustomAttributeBuilder(
                        typeof(ParamDictAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
                    kwDict = i + paramOffset;
                }
            }

            CodeGen cg = new CodeGen(tg, cb, cb.GetILGenerator(), argTypes);

            // <typeField> = <arg0>
            cg.EmitArgGet(0);
            typeField.EmitSet(cg); //???

            //!!! what about propagating custom attributes ?
            CallBaseConstructor(parentConstructor, argTypes, cg);
            return cb;
        }

        private static void CallBaseConstructor(ConstructorInfo parentConstructor, Type[] argTypes, CodeGen cg) {
            cg.EmitThis();
            //cg.emitArgGet(0);
            //cg.emitArgGet(1);

            for (int i = 1; i < argTypes.Length; i++) {
                cg.EmitArgGet(i);
            }
            cg.Emit(OpCodes.Call, parentConstructor);
            cg.Emit(OpCodes.Ret);
        }

        private void InitializeVTableStrings() {
            string[] names = new string[vtable.Count];
            foreach (VTableSlot slot in vtable.Values) {
                names[slot.index] = slot.name;
            }

            Slot namesField = tg.AddStaticField(typeof(string[]), VtableNamesField);

            CodeGen cg = tg.GetOrMakeInitializer();
            cg.EmitStringArray(names);
            namesField.EmitSet(cg);
        }

        private void ImplementCustomTypeDescriptor() {
            tg.myType.AddInterfaceImplementation(typeof(ICustomTypeDescriptor));

            foreach (MethodInfo m in typeof(ICustomTypeDescriptor).GetMethods()) {
                ImplementCTDOverride(m);
            }
        }

        private void ImplementCTDOverride(MethodInfo m) {
            CodeGen cg = tg.DefineExplicitInterfaceImplementation(m);
            cg.EmitThis();

            ParameterInfo[] pis = m.GetParameters();
            Type[] paramTypes = new Type[pis.Length + 1];
            paramTypes[0] = typeof(object);
            for (int i = 0; i < pis.Length; i++) {
                cg.EmitArgGet(i);
                paramTypes[i + 1] = pis[i].ParameterType;
            }

            cg.EmitCall(typeof(CustomTypeDescHelpers), m.Name, paramTypes);
            cg.EmitCastToObject(m.ReturnType);
            cg.EmitReturn();
            cg.Finish();
        }

        private void ImplementDynamicObject() {
            CodeGen cg;

            tg.myType.AddInterfaceImplementation(typeof(ISuperDynamicObject));

            cg = tg.DefineMethodOverride(typeof(ISuperDynamicObject).GetMethod("GetDict"));
            dictField.EmitGet(cg);
            cg.EmitReturn();
            cg.Finish();

            cg = tg.DefineMethodOverride(typeof(ISuperDynamicObject).GetMethod("SetDict"));
            cg.EmitArgGet(0);
            dictField.EmitSet(cg);
            cg.EmitReturn();
            cg.Finish();

            cg = tg.DefineMethodOverride(typeof(ISuperDynamicObject).GetMethod("GetObjectKeyedDict"));
            dictField.EmitGet(cg);
            cg.EmitCall(typeof(SymbolTable).GetMethod("FromSymbolIdKeysToObjectKeys"));
            cg.EmitReturn();
            cg.Finish();

            if (hasBaseTypeField) return;

            cg = tg.DefineMethodOverride(typeof(IDynamicObject).GetMethod("GetDynamicType"));
            typeField.EmitGet(cg);
            cg.EmitReturn();
            cg.Finish();

            cg = tg.DefineMethodOverride(typeof(ISuperDynamicObject).GetMethod("SetDynamicType"));
            cg.EmitArgGet(0);
            typeField.EmitSet(cg);
            cg.EmitReturn();
            cg.Finish();
        }

        /// <summary>
        /// Defines an interface on the type that forwards all calls
        /// to a helper method in UserType.  The method names all will
        /// have Helper appended to them to get the name for UserType.  The 
        /// UserType version should take 1 extra parameter (self).
        /// </summary>
        /// <param name="intf"></param>
        private void DefineHelperInterface(Type intf, bool fExplicit) {
            tg.myType.AddInterfaceImplementation(intf);
            MethodInfo[] mis = intf.GetMethods();

            foreach (MethodInfo mi in mis) {

                CodeGen cg = fExplicit ? tg.DefineExplicitInterfaceImplementation(mi) : tg.DefineMethodOverride(mi);
                cg.EmitThis();
                ParameterInfo[] pis = mi.GetParameters();
                for (int i = 0; i < pis.Length; i++) {
                    cg.EmitArgGet(i);
                }
                cg.EmitCall(typeof(UserType).GetMethod(mi.Name + "Helper"));
                cg.EmitReturn();
                cg.Finish();
            }
        }

        private void ImplementPythonEquals() {
            if (this.baseType.GetInterface("IPythonEquality") == null) {
                DefineHelperInterface(typeof(IPythonEquality), false);
            }
        }

        private void ImplementWeakReference() {
            DefineHelperInterface(typeof(IWeakReferenceable), true);
        }

        private void OverrideVirtualMethods(Type type, Dictionary<string, bool> specialNames) {
            foreach (MethodInfo mi in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)) {
                if (mi.IsPublic || mi.IsFamily || mi.IsFamilyOrAssembly) {
                    if (mi.IsSpecialName) {
                        OverrideSpecialName(mi, specialNames);
                    } else {
                        OverrideBaseMethod(mi, specialNames);
                    }
                }
            }
        }

        private static string[] SkipMethodNames = new string[] { "GetDynamicType", };

        private void OverrideSpecialName(MethodInfo mi, Dictionary<string, bool> specialNames) {
            if (mi == null || !mi.IsVirtual || mi.IsFinal) return;
            
            string name;
            PropertyInfo[] pis = mi.DeclaringType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            specialNames[mi.Name] = true;
            foreach (PropertyInfo pi in pis) {
                if (pi.GetIndexParameters().Length > 0) {
                    if (mi == pi.GetGetMethod(true)) {                        
                        Slot methField = GetOrMakeField("__getitem__");
                        CreateVirtualMethodOverride(mi, methField);
                        if (!mi.IsAbstract) CreateVirtualMethodHelper(mi);
                        break;
                    } else if (mi == pi.GetSetMethod(true)) {
                        Slot methField = GetOrMakeField("__setitem__");
                        CreateVirtualMethodOverride(mi, methField);
                        if (!mi.IsAbstract) CreateVirtualMethodHelper(mi);
                        break;
                    }
                }else  if (mi == pi.GetGetMethod(true)) {
                    if (NameConverter.TryGetName(Ops.GetDynamicTypeFromType(mi.DeclaringType), pi, mi, out name) == NameType.None) return;
                    Slot methField = GetOrMakeField(name);
                    CreateVTableGetterOverride(mi, methField as VTableSlot);
                    break;
                } else if (mi == pi.GetSetMethod(true)) {
                    if (NameConverter.TryGetName(Ops.GetDynamicTypeFromType(mi.DeclaringType), pi, mi, out name) == NameType.None) return;
                    Slot methField = GetOrMakeField(name);
                    CreateVTableSetterOverride(mi, methField as VTableSlot);
                    break;
                } 
            }
        }

        /// <summary>
        /// Object.ToString() displays the CLI type name.  But we want to display the class name (e.g.
        /// '<foo object at 0x000000000000002C>' unless we've overridden __repr__ but not __str__ in 
        /// which case we'll display the result of __repr__.
        /// </summary>
        public static string ToStringHelper(ISuperDynamicObject o) { 
            
            object ret;
            if (Ops.TryGetAttr(o, SymbolTable.Repr, out ret)) {
                return Converter.ConvertToString(Ops.Call(ret));
            }
            return PythonType.ReprMethod(o).ToString();
        }

        // Loads all the incoming arguments of cg and forwards them to mi which
        // has the same signature.
        // The return value (if any) is left on the IL stack.
        private void EmitBaseMethodDispatch(MethodInfo mi, CodeGen cg) {
            cg.EmitThis();
            for (int i = 0; i < mi.GetParameters().Length; i++)
                cg.EmitArgGet(i);
            cg.EmitCall(OpCodes.Call, mi, null); // base call must be non-virtual
        }

        private void OverrideBaseMethod(MethodInfo mi, Dictionary<string, bool> specialNames) {
            string name;
            if (mi == null ||
                !mi.IsVirtual ||
                mi.IsFinal ||
                Array.IndexOf(SkipMethodNames, mi.Name) != -1 ||
                NameConverter.TryGetName(Ops.GetDynamicTypeFromType(baseType), mi, out name) == NameType.None) {
                return;
            }

            specialNames[mi.Name] = false;
            Slot methField = GetOrMakeField(name);

            CreateVirtualMethodOverride(mi, methField);
            if (!mi.IsAbstract) CreateVirtualMethodHelper(mi);
        }

        private Slot GetVTableSlot(string name) {
            VTableSlot ret;
            if (vtable.TryGetValue(name, out ret)) return ret;

            ret = new VTableSlot(typeField, name, vtable.Count);
            vtable[name] = ret;
            return ret;
        }

        public Slot GetOrMakeField(string name) {
            name = NormalizeName(name);
            FieldInfo fi = typeof(PythonType).GetField(name + "F");
            if (fi != null) return new FieldSlot(typeField, fi);

            //return null;

            return GetVTableSlot(name);
        }
        private void CreateVTableMethodOverrideSimple(CodeGen cg, MethodInfo mi, VTableSlot methField) {
            cg.EmitThis();

            for (int i = 0; i < mi.GetParameters().Length; i++) {
                cg.EmitArgGet(i);
                cg.EmitCastToObject(mi.GetParameters()[i].ParameterType);
            }
            cg.EmitCall(typeof(Ops), "InvokeMethod", CompilerHelpers.MakeRepeatedArray(typeof(object), 3 + mi.GetParameters().Length));
        }
        private void CreateVTableMethodOverrideComplex(CodeGen cg, MethodInfo mi, VTableSlot methField) {
            cg.EmitInt(mi.GetParameters().Length + 1);
            cg.Emit(OpCodes.Newarr, typeof(object));

            cg.Emit(OpCodes.Dup);
            cg.EmitInt(0);
            cg.EmitThis();
            cg.Emit(OpCodes.Stelem_Ref);

            for (int i = 0; i < mi.GetParameters().Length; i++) {
                cg.Emit(OpCodes.Dup);
                cg.EmitInt(i + 1);
                cg.EmitArgGet(i);
                cg.EmitCastToObject(mi.GetParameters()[i].ParameterType);
                cg.Emit(OpCodes.Stelem_Ref);
            }

            cg.EmitCall(typeof(Ops), "InvokeMethod", new Type[] { typeof(object), typeof(object), typeof(object[]) });
        }

        private static void CreateVTableMethodOverrideParams(CodeGen cg, MethodInfo mi, VTableSlot methField, int paramsIndex) {
            cg.EmitArgGet(paramsIndex);
            cg.Emit(OpCodes.Ldlen);
            cg.EmitInt(mi.GetParameters().Length);
            cg.Emit(OpCodes.Add);
            Slot destArray = cg.GetLocalTmp(typeof(object[]));
            cg.Emit(OpCodes.Newarr, typeof(object));
            cg.Emit(OpCodes.Dup);
            destArray.EmitSet(cg);


            cg.EmitInt(0);
            cg.EmitThis();
            cg.Emit(OpCodes.Stelem_Ref);
            Slot tmp = cg.GetLocalTmp(typeof(int));
            cg.EmitInt(1);
            tmp.EmitSet(cg);

            for (int i = 0; i < mi.GetParameters().Length; i++) {
                if (i != paramsIndex) {
                    // non params argument, just stick it in our array...
                    destArray.EmitGet(cg);
                    tmp.EmitGet(cg);
                    cg.EmitArgGet(i);
                    cg.EmitCastToObject(mi.GetParameters()[i].ParameterType);
                    cg.Emit(OpCodes.Stelem_Ref);

                    // increment the write counter
                    tmp.EmitGet(cg);
                    cg.EmitInt(1);
                    cg.Emit(OpCodes.Add);
                    tmp.EmitSet(cg);
                } else {
                    // params index, copy contents of array.
                    cg.EmitArgGet(paramsIndex);
                    cg.EmitInt(0);
                    destArray.EmitGet(cg);
                    tmp.EmitGet(cg);
                    cg.EmitArgGet(paramsIndex);
                    cg.Emit(OpCodes.Ldlen);

                    cg.EmitCall(typeof(Array), "Copy", new Type[] { typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int) });

                    // update the write counter
                    tmp.EmitGet(cg);
                    cg.EmitArgGet(paramsIndex);
                    cg.Emit(OpCodes.Ldlen);
                    cg.Emit(OpCodes.Add);
                    tmp.EmitSet(cg);
                }
            }

            destArray.EmitGet(cg);
            cg.EmitCall(typeof(Ops), "InvokeMethod", new Type[] { typeof(object), typeof(object), typeof(object[]) });
        }

        private bool CanOverride(MethodInfo mi, out int paramsIndex) {
            bool fCanOverride = true;
            paramsIndex = -1;

            ParameterInfo[] pis = mi.GetParameters();
            for (int i = 0; i < pis.Length; i++) {
                if (pis[i].ParameterType.IsByRef) {
                    fCanOverride = false;
                } else {
                    object[] paramArr = pis[i].GetCustomAttributes(typeof(ParamArrayAttribute), false);
                    if (paramArr != null && paramArr.Length > 0) {
                        paramsIndex = i;
                    }
                }
            }

            return fCanOverride;
        }

        private void EmitBadCallThrow(CodeGen cg, MethodInfo mi, string reason) {
            cg.EmitString("Cannot override method from IronPython {0} because " + reason);
            cg.EmitInt(1);
            cg.Emit(OpCodes.Newarr, typeof(object));
            cg.Emit(OpCodes.Dup);
            cg.EmitInt(0);
            cg.EmitString(mi.Name);
            cg.Emit(OpCodes.Stelem_Ref);
            cg.EmitCall(typeof(Ops), "TypeError");
            cg.Emit(OpCodes.Throw);
        }

        /// <summary>
        /// Emits code to check if the instance has overriden this specific
        /// function from the base class.  For example:
        /// 
        /// a = MyDerivedType()
        /// a.SomeVirtualFunction = myFunction
        /// </summary>
        private void EmitInstanceCallCheck(CodeGen cg, MethodInfo mi, VTableSlot methField) {
            Slot tmp = cg.GetLocalTmp(typeof(object));
            Label notThere = cg.DefineLabel(), done = cg.DefineLabel();

            // first, see if we have a dict (usually we shouldn't)            
            dictField.EmitGet(cg);
            cg.Emit(OpCodes.Dup);
            cg.Emit(OpCodes.Ldnull);
            cg.Emit(OpCodes.Beq, notThere);

            // then see if the dict contains our entry (usually it won't)...
            cg.EmitSymbolId(methField.name);
            tmp.EmitGetAddr(cg);
            cg.EmitCall(typeof(IDictionary<SymbolId, object>), "TryGetValue");
            cg.Emit(OpCodes.Brfalse, done);
            
            // finally dispatch the call to function if it's there.

            // load the function
            tmp.EmitGet(cg);

            // allocate an array for the parameters
            ParameterInfo[] pis = mi.GetParameters();
            cg.EmitInt(pis.Length);
            cg.Emit(OpCodes.Newarr, typeof(object));
                        
            // the instance should be a bound parameter, so we
            // don't pass it in here.
            
            // store args...            
            for (int i = 0; i < pis.Length; i++) {
                cg.Emit(OpCodes.Dup);
                cg.EmitInt(i);
                cg.EmitArgGet(i);
                if (pis[i].IsOut || pis[i].ParameterType.IsByRef) {
                    cg.EmitLoadValueIndirect(pis[i].ParameterType.GetElementType());
                    cg.EmitCastToObject(pis[i].ParameterType.GetElementType());
                } else {
                    cg.EmitCastToObject(pis[i].ParameterType);
                }
                cg.Emit(OpCodes.Stelem_Ref);
            }

            // finally emit the call to Ops
            cg.EmitCall(typeof(Ops), "Call", new Type[] { typeof(object), typeof(object[]) });

            cg.EmitCastFromObject(mi.ReturnType);

            // and return whatever it returns.
            cg.Emit(OpCodes.Ret);

            // we have no dict
            cg.MarkLabel(notThere);
            cg.Emit(OpCodes.Pop);

            // we have a dict, but no overload.
            cg.MarkLabel(done);

            cg.FreeLocalTmp(tmp);
        }

        /// <summary>
        /// Emits code to check if the class has overriden this specific
        /// function.  For example:
        /// 
        /// MyDerivedType.SomeVirtualFunction = ...
        ///     or
        /// 
        /// class MyDerivedType(MyBaseType):
        ///     def SomeVirtualFunction(self, ...):
        /// 
        /// </summary>
        private Label EmitBaseClassCallCheck(CodeGen cg, VTableSlot methField) {
            Label baseCall = cg.DefineLabel();
            Slot resultOut = cg.GetLocalTmp(typeof(object));

            methField.EmitTryGetNonInheritedValue(cg, resultOut);
            cg.Emit(OpCodes.Brfalse, baseCall);

            resultOut.EmitGet(cg);

            return baseCall;
        }

        private static void EmitBaseClassCall(CodeGen cg, MethodInfo mi) {
            if (!mi.IsAbstract) {
                cg.EmitThis();
                for (int i = 0; i < mi.GetParameters().Length; i++) cg.EmitArgGet(i);
                cg.EmitCall(OpCodes.Call, mi, null); // base call must be non-virtual
                cg.EmitReturn();
            } else {
                cg.EmitThis();
                cg.EmitString(mi.Name);
                cg.EmitCall(typeof(Ops), "MissingInvokeMethodException");
                cg.Emit(OpCodes.Throw);
            }
        }

        private void CreateVTableGetterOverride(MethodInfo mi, VTableSlot methField) {
            CodeGen cg = tg.DefineMethodOverride(mi);
            Label baseCall = EmitBaseClassCallCheck(cg, methField);

            int paramsIndex;
            bool fCanOverride = CanOverride(mi, out paramsIndex);

            if (fCanOverride) {
                // check that the value we pulled out is a
                // descriptor and call __get__ on it for our
                // instance, or throw if the user assigned it to something else.
                cg.Emit(OpCodes.Isinst, typeof(IDescriptor));
                cg.Emit(OpCodes.Dup);
                cg.EmitPythonNone();
                cg.Emit(OpCodes.Ceq);
                Label notProp = cg.DefineLabel();

                cg.Emit(OpCodes.Brtrue, notProp);

                cg.EmitThis();
                cg.EmitPythonNone();
                cg.EmitCall(typeof(IDescriptor), "GetAttribute");

                cg.EmitCastFromObject(mi.ReturnType);

                cg.EmitReturn();

                cg.MarkLabel(notProp);

                EmitBadCallThrow(cg, mi, "is not a property");
            } else {
                EmitBadCallThrow(cg, mi, "it includes a by-ref parameter");
            }

            cg.MarkLabel(baseCall);
            EmitBaseClassCall(cg, mi);

            cg.Finish();
        }

        private void CreateVTableSetterOverride(MethodInfo mi, VTableSlot methField) {
            CodeGen cg = tg.DefineMethodOverride(mi);
            Label baseCall = EmitBaseClassCallCheck(cg, methField);

            int paramsIndex;
            bool fCanOverride = CanOverride(mi, out paramsIndex);

            if (fCanOverride) {
                cg.Emit(OpCodes.Isinst, typeof(IDataDescriptor));
                cg.Emit(OpCodes.Dup);
                cg.EmitPythonNone();
                cg.Emit(OpCodes.Ceq);
                Label notProp = cg.DefineLabel();

                cg.Emit(OpCodes.Brtrue, notProp);

                cg.EmitThis();
                cg.EmitArgGet(0);
                cg.EmitCastToObject(mi.GetParameters()[0].ParameterType);
                cg.EmitCall(typeof(IDataDescriptor), "SetAttribute");
                cg.Emit(OpCodes.Pop);

                cg.EmitReturn();

                cg.MarkLabel(notProp);

                EmitBadCallThrow(cg, mi, "is not a property");
            } else {
                EmitBadCallThrow(cg, mi, "it includes a by-ref parameter");
            }

            cg.MarkLabel(baseCall);
            EmitBaseClassCall(cg, mi);

            cg.Finish();

        }

        private void CreateVTableMethodOverride(MethodInfo mi, VTableSlot methField) {
            CodeGen cg = tg.DefineMethodOverride(mi);

            EmitInstanceCallCheck(cg, mi, methField);

            Label baseCall = EmitBaseClassCallCheck(cg, methField);

            typeField.EmitGet(cg);  // for InvokeMethod call, 2nd param (EmitCaseClassCallCheck emits method)

            int paramsIndex;
            bool fCanOverride = CanOverride(mi, out paramsIndex);

            if (fCanOverride) {
                if (paramsIndex != -1) {
                    CreateVTableMethodOverrideParams(cg, mi, methField, paramsIndex);
                } else if (mi.GetParameters().Length > 2) {
                    CreateVTableMethodOverrideComplex(cg, mi, methField);
                } else {
                    CreateVTableMethodOverrideSimple(cg, mi, methField);
                }
                cg.EmitCastFromObject(mi.ReturnType);

                cg.EmitReturn();
            } else {
                EmitBadCallThrow(cg, mi, "it includes a by-ref parameter");
            }

            cg.MarkLabel(baseCall);
            EmitBaseClassCall(cg, mi);

            cg.Finish();
        }

        private void CreateVirtualMethodOverride(MethodInfo mi, Slot methField) {
            if (methField is VTableSlot) {
                CreateVTableMethodOverride(mi, (VTableSlot)methField);
                return;
            }

            CodeGen cg = tg.DefineMethodOverride(mi);
            Label baseCall = cg.DefineLabel();

            methField.EmitGet(cg);
            cg.EmitCall(typeof(MethodWrapper), "IsBuiltinMethod");
            cg.Emit(OpCodes.Brtrue, baseCall);

            methField.EmitGet(cg);
            cg.EmitThis();
            for (int i = 0; i < mi.GetParameters().Length; i++) {
                cg.EmitArgGet(i);
                cg.EmitCastToObject(mi.GetParameters()[i].ParameterType);
            }

            if (mi.GetParameters().Length > 3) {
                throw new NotImplementedException("OverrideBaseMethod: " + mi);
            }
            //Console.WriteLine("Invoke: " + mi + ", " + mi.GetParameters().Length);
            cg.EmitCall(typeof(MethodWrapper), "Invoke", CompilerHelpers.MakeRepeatedArray(typeof(object), 1 + mi.GetParameters().Length));
            cg.EmitCastFromObject(mi.ReturnType);
            cg.EmitReturn();

            cg.MarkLabel(baseCall);

            MethodInfo object_ToString = typeof(object).GetMethod("ToString");
            if (mi.MethodHandle == object_ToString.MethodHandle) {
                // object.ToString() displays the CLI type name. However, __class__ is the real type for Python type instances
                cg.EmitThis();
                cg.EmitCall(typeof(NewTypeMaker).GetMethod("ToStringHelper"));
                cg.EmitReturn();

            } else {
                if (mi.IsAbstract) {
                    EmitBadCallThrow(cg, mi, "must override abstract method"); //@todo better exception
                } else {
                    // Just forward to the base method implementation
                    EmitBaseMethodDispatch(mi, cg);
                    cg.EmitReturn();
                }
            }
            
            cg.Finish();
        }

        private void CreateVirtualMethodHelper(MethodInfo mi) {
            ParameterInfo[] parms = mi.GetParameters();
            Type[] types = CompilerHelpers.GetTypes(parms);
            string[] paramNames = new string[parms.Length];
            Type miType = mi.DeclaringType;
            for (int i = 0; i < types.Length; i++) {
                paramNames[i] = parms[i].Name;
                if (types[i] == miType) {
                    types[i] = tg.myType;
                }
            }
            CodeGen cg = tg.DefineMethod(MethodAttributes.Public | MethodAttributes.HideBySig,
                                         "#base#" + mi.Name, mi.ReturnType, types, paramNames);

            EmitBaseMethodDispatch(mi, cg);
            cg.EmitReturn();
            cg.Finish();
        }
    }
}
