using System;
using System.Collections.Generic;
using Mono.Cecil;
using SpaceFlint.JavaBinary;

namespace SpaceFlint.CilToJava
{

    public static class InterfaceBuilder
    {

        public static int CastableInterfaceCount(List<CilInterface> allInterfaces)
        {
            // a castable interface is an interface with generic parameters,
            // except those that are already implemented by a base type,
            // because those were already counted as castable in the base type.
            // see also: InitInterfaceArrayField and BuildTryCastMethod.

            int num = 0;
            foreach (var ifc in allInterfaces)
            {
                if (ifc.GenericTypes != null && (! ifc.SuperImplements))
                    num++;
            }
            return num;
        }



        public static List<JavaClass> BuildProxyMethods(List<CilInterface> allInterfaces,
                                                        TypeDefinition fromType, CilType intoType,
                                                        JavaClass theClass)
        {
            //
            // process only if the class (or interface) has any methods or super interfaces
            //

            var classMethods = theClass.Methods;
            if (classMethods.Count == 0)
                return null;

            bool isInterface = intoType.IsInterface;
            if ((! isInterface) && theClass.Interfaces == null)
                return null;

            var theMethods = CilInterfaceMethod.CollectAll(fromType);

            //
            // if any interfaces are marked [RetainName], make sure that
            // all corresponding methods are also marked [RetainName]
            //

            CheckRetainNameMethods(theMethods, allInterfaces, intoType);

            //
            // if this is an abstract class but forced to an interface via [AddInterface]
            // decoration, then we need to remove all constructors generated for the class
            //

            if (intoType.IsInterface)
            {
                if (! fromType.IsInterface)
                {
                    for (int i = classMethods.Count; i-- > 0; )
                    {
                        if (classMethods[i].Name == "<init>")
                            classMethods.RemoveAt(i);
                    }
                }

                if (intoType.HasGenericParameters)
                {
                    // the RuntimeType constructor in baselib uses IGenericEntity
                    // marker interface to identify generic classes.  note that
                    // real generic types implement IGenericObject -> IGenericEntity.

                    theClass.AddInterface("system.IGenericEntity");
                }

                return null;
            }

            //
            // for each implemented interface, build proxy methods
            //

            List<JavaClass> output = null;

            int ifcNumber = 0;
            foreach (var ifc in allInterfaces)
            {
                if ((! ifc.DirectReference) && ifc.SuperImplements)
                {
                    // we don't have to build proxy for an interface if it is
                    // implemented by a super type and not by our primary type
                    continue;
                }
                if (ifc.GenericTypes == null)
                {
                    foreach (var ifcMethod in ifc.Methods)
                    {
                        // build proxy methods:  interface$method -> method
                        var newMethod = BuildPlainProxy(ifcMethod, intoType, theMethods);
                        if (newMethod != null)
                        {
                            newMethod.Class = theClass;
                            theClass.Methods.Add(newMethod);
                        }
                    }
                }
                else
                {
                    var ifcClass = CreateInnerClass(theClass, intoType, ++ifcNumber);
                    ifcClass.AddInterface(ifc.InterfaceType.JavaName);

                    if (output == null)
                    {
                        output = new List<JavaClass>();
                        CreateInterfaceArrayField(theClass);
                    }
                    output.Add(ifcClass);

                    // if the class implements a generic interface for multiple types,
                    // then we need a method suffix to differentiate between the methods.
                    // see also:  CilMethod::InsertMethodNamePrefix
                    string methodSuffix = "";
                    foreach (var genericType in ifc.GenericTypes)
                        methodSuffix += "--" + CilMethod.GenericParameterSuffixName(genericType);

                    foreach (var ifcMethod in ifc.Methods)
                    {
                        // build proxy classes:  proxy sub-class -> this class
                        BuildGenericProxy(ifcMethod, methodSuffix, intoType, theMethods, ifcClass);
                    }
                }
            }

            return output;

            JavaClass CreateInnerClass(JavaClass parentClass, CilType parentType, int ifcNumber)
            {
                // generic interfaces are implemented as proxy sub-classes which
                // call methods on the parent class object.  we need to define
                // an inner class.  this class has one instance field which is a
                // reference to the parent class.  the constructor takes this
                // reference as a parameter and initializes the instance field.

                var newClass = CilMain.CreateInnerClass(parentClass,
                                    parentClass.Name + "$$generic" + ifcNumber.ToString());

                var fld = new JavaField();
                fld.Name = ParentFieldName;
                fld.Type = parentType;
                fld.Class = newClass;
                fld.Flags = JavaAccessFlags.ACC_PRIVATE;
                newClass.Fields.Add(fld);

                var code = CilMain.CreateHelperMethod(newClass,
                                new JavaMethodRef("<init>", JavaType.VoidType, JavaType.ObjectType),
                                2, 2);
                code.Method.Flags &= ~JavaAccessFlags.ACC_BRIDGE;   // invalid for constructor

                code.NewInstruction(0x19 /* aload */, null, (int) 0);
                code.NewInstruction(0xB7 /* invokespecial */, JavaType.ObjectType,
                                                new JavaMethodRef("<init>", JavaType.VoidType));
                code.NewInstruction(0x19 /* aload */, null, (int) 0);
                code.NewInstruction(0x19 /* aload */, null, (int) 1);
                code.NewInstruction(0xC0 /* checkcast */, parentType, null);
                code.NewInstruction(0xB5 /* putfield */, new JavaType(0, 0, newClass.Name),
                                                    new JavaFieldRef(ParentFieldName, parentType));
                code.NewInstruction(JavaType.VoidType.ReturnOpcode, null, null);

                return newClass;
            }

            void CreateInterfaceArrayField(JavaClass parentClass)
            {
                // the parent class has a helper array field that is used to track
                // the proxy objects generated for implemented generic interfaces.
                // see also: InitInterfaceArrayField, below.

                var fld = new JavaField();
                fld.Name = InterfaceArrayField.Name;
                fld.Type = InterfaceArrayField.Type;
                fld.Class = parentClass;
                fld.Flags = JavaAccessFlags.ACC_PRIVATE;

                if (parentClass.Fields == null)
                    parentClass.Fields = new List<JavaField>(1);
                parentClass.Fields.Add(fld);
            }
        }



        //
        // InitInterfaceArrayField
        //

        public static void InitInterfaceArrayField(CilType toType, int numCastableInterfaces,
                                                  JavaCode code)
        {
            // if a type has castable interface (as counted by CastableInterfaceCount),
            // then we need to initialize the helper array field, for use by the
            // implementation of the IGenericObject.TryCast helper method

            if (numCastableInterfaces == 0)
                return;

            code.NewInstruction(0x19 /* aload */, null, (int) 0);
            code.StackMap.PushStack(toType);
            code.NewInstruction(0xBB /* new */, AtomicReferenceArrayType, null);
            code.StackMap.PushStack(AtomicReferenceArrayType);
            code.NewInstruction(0x59 /* dup */, null, null);
            code.StackMap.PushStack(AtomicReferenceArrayType);
            code.NewInstruction(0x12 /* ldc */, null, numCastableInterfaces);
            code.StackMap.PushStack(JavaType.IntegerType);
            code.NewInstruction(0xB7 /* invokespecial */, AtomicReferenceArrayType,
                                new JavaMethodRef("<init>", JavaType.VoidType, JavaType.IntegerType));
            code.NewInstruction(0xB5 /* putfield */, toType, InterfaceArrayField);
            code.StackMap.PopStack(CilMain.Where);
            code.StackMap.PopStack(CilMain.Where);
            code.StackMap.PopStack(CilMain.Where);
            code.StackMap.PopStack(CilMain.Where);
        }



        //
        // if any interfaces are marked [RetainName], make sure that
        // all corresponding methods are also marked [RetainName]
        //

        static void CheckRetainNameMethods(List<CilInterfaceMethod> theMethods,
                                           List<CilInterface> theInterfaces,
                                           CilType checkedType)
        {
            List<CilInterface> retainNameInterfaces = null;
            foreach (var myInterface in theInterfaces)
            {
                if (myInterface.InterfaceType.IsRetainName)
                {
                    if (retainNameInterfaces == null)
                        retainNameInterfaces = new List<CilInterface>();
                    retainNameInterfaces.Add(myInterface);
                }
            }

            if (retainNameInterfaces == null)
                return;

            foreach (var myMethod in theMethods)
            {
                // if the method was not declared on the type itself, skip it.

                if (myMethod.Method.DeclType != checkedType)
                    continue;

                // methods may be marked with [RetainName] to avoid shadow renaming
                // (via CilMethod::MethodIsShadowing), as well as to match methods
                // from [RetainName] interfaces.  so if the method is marked so,
                // then we can just skip it.

                if (myMethod.Method.IsRetainName)
                    continue;

                // if the method is an explicit method implementation, then it cannot
                // implement a method from a [RetainName] interface (checked in
                // CilMethod::InsertMethodNamePrefix) and cannot be marked [RetainName]
                // (checked in CilMethod::SetMethodType).

                if (myMethod.Method.IsExplicitImpl)
                    continue;

                // if the method is not marked [RetainName], then make sure it is not
                // overriding an interface method marked [RetainName]

                var (foundInterface, foundMethod) =
                                        FindMethod(retainNameInterfaces, myMethod);

                if (foundMethod != null)
                {
                    var interfaceName = foundInterface.InterfaceType.JavaName;

                    throw CilMain.Where.Exception(
                        $"method '{myMethod}' (for interface '{interfaceName}') "
                      + $"should be decorated with [java.attr.RetainName]");
                }
            }

            (CilInterface, CilInterfaceMethod) FindMethod(List<CilInterface> haystack,
                                                          CilInterfaceMethod needle)
            {
                foreach (var ifc in haystack)
                {
                    var mth = CilInterfaceMethod.FindMethod(ifc.Methods, needle);
                    if (mth != null)
                        return (ifc, mth);
                }
                return (null, null);
            }
        }



        public static JavaMethod BuildPlainProxy(CilInterfaceMethod ifcMethod, CilType intoType,
                                                 List<CilInterfaceMethod> classMethods)
        {
            CilMethod targetMethod = null;

            foreach (var clsMethod in classMethods)
            {
                if (clsMethod.Method.IsExplicitImpl)
                {
                    // no need for a proxy if we already have an override method,
                    // which has the same name as the proxy:  interface$method
                    if (ifcMethod.Method.Name == clsMethod.Method.Name)
                        return null;
                }
                else if (ifcMethod.PlainCompare(clsMethod))
                {
                    // more than one method may match, if a derived type overrides
                    // or hides a method that also exists in a base type.  but the
                    // derived (primary) type methods always come first.
                    if (targetMethod == null)
                        targetMethod = clsMethod.Method;
                }
            }

            if (targetMethod == null)
            {
                throw CilMain.Where.Exception(
                    $"missing method '{ifcMethod.Method}' "
                  + $"(for interface '{ifcMethod.Method.DeclType}')");
            }

            if (targetMethod.IsRetainName)
            {
                // method retains is name, so it doesn't require a proxy bridge
                return null;
            }

            //
            // create proxy method
            //

            var newMethod = new JavaMethod(null, targetMethod);
            newMethod.Name = ifcMethod.Method.Name;
            newMethod.Flags = JavaAccessFlags.ACC_PUBLIC | JavaAccessFlags.ACC_BRIDGE;

            var code = newMethod.Code = new JavaCode();
            code.Method = newMethod;
            code.Instructions = new List<JavaCode.Instruction>();

            //
            // push 'this' and all other parameters
            //

            code.NewInstruction(0x19 /* aload */, null, (int) 0);
            int numArgs = newMethod.Parameters.Count;
            int index = 1;
            for (int i = 0; i < numArgs; i++)
            {
                var arg = targetMethod.Parameters[i].Type;
                code.NewInstruction(arg.LoadOpcode, null, (int) index);
                index += arg.Category;
            }

            //
            // invoke proxy target method and return
            //

            code.NewInstruction(0xB6 /* invokevirtual */, intoType, targetMethod);
            code.NewInstruction(targetMethod.ReturnType.ReturnOpcode, null, null);

            code.MaxLocals = code.MaxStack = index;

            return newMethod;
        }



        public static void BuildGenericProxy(CilInterfaceMethod ifcMethod, string methodSuffix,
                                             CilType intoType, List<CilInterfaceMethod> classMethods,
                                             JavaClass ifcClass)
        {
            CilMethod targetMethod = null;

            //Console.WriteLine("\n***** LOOKING FOR INTERFACE METHOD " + ifcMethod + " (SUFFIX " + methodSuffix + ") AKA " + ifcMethod.Method);

            foreach (var clsMethod in classMethods)
            {
                /*Console.WriteLine("> LOOKING AT "
                        + (clsMethod.Method.IsExplicitImpl ? "EXPLICIT " : "")
                        + "CLASS METHOD " + clsMethod + " AKA " + clsMethod.Method);*/

                if (ifcMethod.GenericCompare(clsMethod))
                {
                    if (clsMethod.Method.IsExplicitImpl)
                    {
                        // a matching explicit override method is the best match,
                        // and we can immediately stop searching
                        targetMethod = clsMethod.Method;
                        break;
                    }
                    else
                    {
                        // more than one method may match, if a derived type overrides
                        // or hides a method that also exists in a base type.  but the
                        // derived (primary) type methods always come first.
                        if (targetMethod == null)
                            targetMethod = clsMethod.Method;
                    }
                }
            }

            if (targetMethod == null)
            {
                throw CilMain.Where.Exception(
                    $"missing method '{ifcMethod.Method}' "
                  + $"(for interface '{ifcMethod.Method.DeclType}')");
            }

            // Console.WriteLine("INTERFACE METHOD " + ifcMethod.Method + " TARGET " + targetMethod);

            BuildGenericProxy2(ifcMethod, targetMethod, true, intoType, ifcClass);
        }



        public static void BuildGenericProxy2(CilInterfaceMethod ifcMethod, CilMethod targetMethod,
                                              bool parentField, CilType intoType, JavaClass ifcClass)
        {
            //
            // create proxy method
            //

            var targetMethod2 = targetMethod.WithGenericParameters;
            var ifcMethod2 = ifcMethod.Method.WithGenericParameters;

            var newMethod = new JavaMethod(ifcClass, targetMethod2);
            newMethod.Name = ifcMethod2.Name;
            newMethod.Flags = JavaAccessFlags.ACC_PUBLIC | JavaAccessFlags.ACC_BRIDGE;

            var code = newMethod.Code = new JavaCode();
            code.Method = newMethod;
            code.Instructions = new List<JavaCode.Instruction>();

            //
            // push a reference to the parent object
            //

            code.NewInstruction(0x19 /* aload */, null, (int) 0);
            if (parentField)
            {
                code.NewInstruction(0xB4 /* getfield */, new JavaType(0, 0, ifcClass.Name),
                                                new JavaFieldRef(ParentFieldName, intoType));
            }

            //
            // push all other parameters
            //

            int numArgs = newMethod.Parameters.Count;
            int index = 1;
            int maxStack = 1;
            for (int i = 0; i < numArgs; i++)
            {
                var ifcArg = ifcMethod2.Parameters[i].Type;
                code.NewInstruction(ifcArg.LoadOpcode, null, (int) index);
                index += ifcArg.Category;

                var clsArg = (CilType) targetMethod2.Parameters[i].Type;
                if (JavaType.ObjectType.Equals(ifcArg))
                {
                    if (! clsArg.IsReference)
                    {
                        var boxedArg = new BoxedType(clsArg, false);
                        code.NewInstruction(0xC0 /* checkcast */, boxedArg, null);
                        boxedArg.GetValue(code);
                    }
                    else if (! JavaType.ObjectType.Equals(clsArg))
                    {
                        code.NewInstruction(0xC0 /* checkcast */, clsArg, null);
                    }
                    // a parameter in the target method may be a concrete type,
                    // but if it is a generic java.lang.Object in the interface,
                    // then it must be a generic java.lang.Object in the proxy
                    newMethod.Parameters[i] = new JavaFieldRef("", ifcArg);
                }
                maxStack += clsArg.Category;
            }

            //
            // invoke proxy target method
            //

            code.NewInstruction(0xB6 /* invokevirtual */, intoType, targetMethod2);

            //
            // return value from method
            //

            var clsRet = (CilType) targetMethod2.ReturnType;
            var ifcRet = ifcMethod2.ReturnType;
            if (JavaType.ObjectType.Equals(ifcRet))
            {
                if (! clsRet.IsReference)
                {
                    var boxedArg = new BoxedType(clsRet, false);
                    boxedArg.BoxValue(code);
                }
                // the return value in the target method may be a concrete type,
                // but if it is a generic java.lang.Object in the interface,
                // then it must also be a generic java.lang.Object in the proxy
                newMethod.ReturnType = ifcRet;
                code.NewInstruction(ifcRet.ReturnOpcode, null, null);
            }
            else
            {
                code.NewInstruction(clsRet.ReturnOpcode, null, null);
            }

            code.MaxLocals = index;
            code.MaxStack = maxStack;

            ifcClass.Methods.Add(newMethod);
        }



        public static void BuildOverloadProxy(TypeDefinition fromType, MethodDefinition fromMethod,
                                              CilMethod targetMethod, JavaClass intoClass)
        {
            // create a proxy bridge to forward invocations of a virtual method from
            // the base class, to an implementation in a derived class.  this is needed
            // where the base virtual method has generic parameters or return type,
            // for example:  virtual T SomeMethod(T arg)   will be translated as:
            //      java.lang.Object SomeMethod(-generic-$)(java.lang.Object arg)
            // and in a derived class that specializes T as String:
            //      java.lang.String SomeMethod(java.lang.String arg)
            // so the derived class must also include a proxy bridge method with the
            // same name as in the base class, and forward the call.

            //Console.WriteLine($"CHECK OVERRIDE {fromMethod}");
            int targetMethodCount = targetMethod.Parameters.Count;

            for (;;)
            {
                var baseType = fromType.BaseType;
                if (baseType == null)
                    break;
                fromType = CilType.AsDefinition(baseType);
                if (! baseType.IsGenericInstance)
                    continue;

                foreach (var fromMethod2 in fromType.Methods)
                {
                    //Console.WriteLine($"\tCOMPARE {fromMethod2} {fromMethod2.IsVirtual} {fromMethod2.Name == fromMethod.Name} {targetMethodCount == fromMethod2.Parameters.Count}");
                    if (    fromMethod2.IsVirtual && fromMethod2.Name == fromMethod.Name
                         && targetMethodCount == fromMethod2.Parameters.Count)
                    {
                        if (CilMethod.CompareMethods(fromMethod, fromMethod2))
                        {
                            //Console.WriteLine(">>>>>>>>>>>>> WARNING SAME: " + fromMethod.ToString() + " AND " + fromType);
                            continue;
                        }

                        var genericMark = CilMain.GenericStack.Mark();
                        CilMain.GenericStack.EnterType(baseType);
                        var baseMethod = new CilInterfaceMethod(
                                                CilMain.GenericStack.EnterMethod(fromMethod2));
                        CilMain.GenericStack.Release(genericMark);

                        //Console.WriteLine($"\twould compare with {fromMethod2} in class {baseType}");
                        //Console.WriteLine($"\t\t{baseMethod}");

                        if (targetMethodCount != baseMethod.Parameters.Count)
                            continue;
                        if (! IsGenericOrEqual(targetMethod.ReturnType, baseMethod.ReturnType))
                            continue;
                        bool sameParameters = true;
                        for (int i = 0; i < targetMethodCount; i++)
                        {
                            if (! IsGenericOrEqual(targetMethod.Parameters[i].Type,
                                                   baseMethod.Parameters[i]))
                            {
                                bool equalsAfterUnboxing = (
                                            targetMethod.Parameters[i].Type is BoxedType boxedType
                                         && boxedType.UnboxedType.Equals(baseMethod.Parameters[i]));

                                if (! equalsAfterUnboxing)
                                {
                                    //Console.WriteLine($"\tMISMATCH {targetMethod.Parameters[i].Type} vs {baseMethod.Parameters[i]}/{baseMethod.Parameters[i].IsGenericParameter}");
                                    sameParameters = false;
                                    break;
                                }
                            }
                        }
                        if (sameParameters)
                        {
                            if (baseMethod.Method.WithGenericParameters.ToString()
                                    != targetMethod.WithGenericParameters.ToString())
                            {
                                // the proxy method may have the same signature as the
                                // target method.  for example in a generic class that
                                // inherits from a generic class:
                                //     class A<T> { virtual T Method(T arg); }
                                // and class B<T> : A<T> { override T Method(T arg); }
                                // in such a case, we should not generate a specialized
                                // proxy for the generic method in class B.
                                //

                                //Console.WriteLine($"proxying {targetMethod} in class {targetMethod.DeclType}");
                                BuildGenericProxy2(baseMethod, targetMethod,
                                                   false, targetMethod.DeclType, intoClass);
                            }
                            return;
                        }
                    }
                }
            }

            static bool IsGenericOrEqual(JavaType pThis, CilType pBase)
                => pBase.IsGenericParameter || pThis.Equals(pBase);
        }



        //
        // implement the method IGenericObject.TryCast(Type)
        //

        public static void BuildTryCastMethod(List<CilInterface> allInterfaces, CilType intoType,
                                              int numCastableInterfaces, JavaClass intoClass)
        {
            var code = CilMain.CreateHelperMethod(intoClass,
                            new JavaMethodRef("system-IGenericObject-TryCast",
                                    JavaType.ObjectType, CilType.SystemTypeType),
                            3, 0);

            code.StackMap = new JavaStackMap();
            code.StackMap.SetLocal(0, intoType);
            code.StackMap.SetLocal(1, CilType.SystemTypeType);
            code.StackMap.SaveFrame(0, false, CilMain.Where);

            if (intoType.HasGenericParameters)
            {
                // if this type is a generic type, compare it to the input.
                // see also:  built GenericUtil::BuildGetTypeMethod()

                code.NewInstruction(0x19 /* aload */, null, (int) 0);
                code.NewInstruction(0xB4 /* getfield */,
                                    intoType, GenericUtil.ConcreteTypeField);
                code.NewInstruction(0x19 /* aload */, null, (int) 1);
                code.NewInstruction(0xA5 /* if_acmpeq */, null, (ushort) 0xFFF1);
                // branch to label 0xFFF1 to return 'this' reference

                if (code.MaxStack < 2)
                    code.MaxStack = 2;
            }

            if (intoType.SuperTypes[0].IsGenericThisOrSuper)
            {
                // if the base type has IGenericObject::TryCast, call that.

                code.NewInstruction(0x19 /* aload */, null, (int) 0);
                code.NewInstruction(0x19 /* aload */, null, (int) 1);
                code.NewInstruction(0xB7 /* invokespecial */,
                                    intoType.SuperTypes[0], code.Method);
                code.NewInstruction(0x59 /* dup */, null, null);
                code.NewInstruction(0xC7 /* ifnonnull */, null, (ushort) 0xFFF2);
                // branch to label 0xFFF2 to return result from super TryCast
                code.NewInstruction(0x57 /* pop */, null, null);

                if (code.MaxStack < 2)
                    code.MaxStack = 2;
            }

            if (numCastableInterfaces != 0)
            {
                // compare the input type to any implemented generic interface,
                // unless that interface is also implemented by a super type.

                int ifcNumber = 0;
                foreach (var ifc in allInterfaces)
                {
                    if (ifc.GenericTypes != null && (! ifc.SuperImplements))
                    {
                        // this is a generic interface, not implemented by a super.

                        if (ifcNumber == 0)
                        {
                            // before processing the first such interface, make an
                            // easy to access reference to the array of interface
                            // types (see also CreateInterfaceArrayField).

                            code.NewInstruction(0x19 /* aload */, null, (int) 0);
                            code.NewInstruction(0xB4 /* getfield */,
                                                intoType, InterfaceArrayField);
                            code.NewInstruction(0x3A /* astore */, null, (int) 2);
                        }

                        // set up parameters to call the GenericType::TryCast helper

                        code.NewInstruction(0x19 /* aload */, null, (int) 0);
                        code.NewInstruction(0x19 /* aload */, null, (int) 1);
                        code.NewInstruction(0x19 /* aload */, null, (int) 2);
                        code.NewInstruction(0x12 /* ldc */, null, (int) ifcNumber++);

                        var proxyClassName = intoType.JavaName
                                           + "$$generic" + ifcNumber.ToString();
                        code.NewInstruction(0x12 /* ldc */,
                                            new JavaType(0, 0, proxyClassName), null);

                        foreach (var inst in ifc.LoadTypeCode.Instructions)
                            code.Instructions.Add(inst);

                        code.NewInstruction(0xB8 /* invokestatic */,
                                            GenericUtil.SystemGenericType, TryCastHelperMethod);

                        code.NewInstruction(0x59 /* dup */, null, null);
                        code.NewInstruction(0xC7 /* ifnonnull */, null, (ushort) 0xFFF2);
                        code.NewInstruction(0x57 /* pop */, null, null);
                        // branch to label 0xFFF2 to return proxy object reference

                        int minStack = ifc.LoadTypeCode.MaxStack + 5;
                        if (code.MaxStack < minStack)
                            code.MaxStack = minStack;
                    }
                }
            }

            // exit points.  if control reaches this point, we return null,  because
            // no matching class or interface type was found.  otherwise if there was
            // a branch to label 0xFFF1, then we need to return the 'this' reference.
            // and finally, a branch to label 0xFFF2, with a reference on the stack.

            // label 0xFFF0 - return null
            code.StackMap.SaveFrame(0xFFF0, true, CilMain.Where);
            code.NewInstruction(0x01 /* aconst_null */, null, null, 0xFFF0);
            code.NewInstruction(JavaType.ObjectType.ReturnOpcode, null, null);

            // label 0xFFF1 - return 'this' reference
            code.StackMap.SaveFrame(0xFFF1, true, CilMain.Where);
            code.NewInstruction(0x19 /* aload */, null, (int) 0, 0xFFF1);

            // label 0xFFF2 - return reference on the stack
            code.StackMap.PushStack(JavaType.ObjectType);
            code.StackMap.SaveFrame(0xFFF2, true, CilMain.Where);
            code.NewInstruction(JavaType.ObjectType.ReturnOpcode, null, null, 0xFFF2);
        }



        //
        // static variables
        //



        internal static readonly JavaType AtomicReferenceArrayType =
                            new JavaType(0, 0, "java.util.concurrent.atomic.AtomicReferenceArray");

        internal static readonly JavaFieldRef InterfaceArrayField =
                                new JavaFieldRef("-generic-interfaces", AtomicReferenceArrayType);

        internal static readonly string ParentFieldName = "-generic-parent";

        internal static readonly JavaMethodRef TryCastHelperMethod;



        static InterfaceBuilder()
        {
            var parameters = new List<JavaFieldRef>(6);
            parameters.Add(new JavaFieldRef("", JavaType.ObjectType));
            parameters.Add(new JavaFieldRef("", CilType.SystemTypeType));
            parameters.Add(new JavaFieldRef("", AtomicReferenceArrayType));
            parameters.Add(new JavaFieldRef("", JavaType.IntegerType));
            parameters.Add(new JavaFieldRef("", JavaType.ClassType));
            parameters.Add(new JavaFieldRef("", CilType.SystemTypeType));
            TryCastHelperMethod = new JavaMethodRef("TryCast", JavaType.ObjectType, parameters);
        }

    }

}