#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

import os, sys, platform, shutil
from . import _jcc3

from .cpp import PRIMITIVES, INDENT, HALF_INDENT
from .cpp import RENAME_METHOD_SUFFIX, RENAME_FIELD_SUFFIX
from .cpp import cppname, cppnames, absname, typename, findClass
from .cpp import line, signature, find_method, sortSignatures
from .cpp import Modifier, Class, Method
from .cpp import getActualTypeArguments, getTypeParameters
from .config import INCLUDES, CFLAGS, DEBUG_CFLAGS, LFLAGS, IMPLIB_LFLAGS, \
    SHARED, VERSION as JCC_VER

try:
    from .cpp import ParameterizedType, TypeVariable
except ImportError:
    pass

RESULTS = { 'boolean': 'Py_RETURN_BOOL(%s);',
            'byte': 'return PyLong_FromLong((long) %s);',
            'char': 'return c2p(%s);',
            'double': 'return PyFloat_FromDouble((double) %s);',
            'float': 'return PyFloat_FromDouble((double) %s);',
            'int': 'return PyLong_FromLong((long) %s);',
            'long': 'return PyLong_FromLongLong((PY_LONG_LONG) %s);',
            'short': 'return PyLong_FromLong((long) %s);',
            'java.lang.String': 'return j2p(%s);' }

CALLARGS = { 'boolean': ('O', '(%s ? Py_True : Py_False)', False),
             'byte': ('O', 'PyLong_FromLong(%s)', True),
             'char': ('O', 'c2p(%s);', True),
             'double': ('d', '(double) %s', False),
             'float': ('f', '(float) %s', False),
             'int': ('i', '(int) %s', False),
             'long': ('L', '(long long) %s', False),
             'short': ('i', '(int) %s', False),
             'java.lang.String': ('O', 'env->fromJString((jstring) %s, 0)', True) }

BOXED = { 'java.lang.Boolean': (True, True),
          'java.lang.Byte': (True, True),
          'java.lang.Character': (True, True),
          'java.lang.CharSequence': (True, False),
          'java.lang.Double': (True, True),
          'java.lang.Float': (True, True),
          'java.lang.Integer': (True, True),
          'java.lang.Long': (True, True),
          'java.lang.Number': (True, False),
          'java.lang.Short': (True, True),
          'java.lang.String': (True, True) }


def is_boxed(clsName):
    return BOXED.get(clsName, (False, False))[0]

def is_unboxed(clsName):
    return BOXED.get(clsName, (False, False))[1]


def parseArgs(params, current, generics, genericParams=None):

    def signature(cls, genericPT=None):
        if generics and TypeVariable.instance_(genericPT):
            if cls.getName() == 'java.lang.Object':
                gd = TypeVariable.cast_(genericPT).getGenericDeclaration()
                if gd == current:
                    for clsParam in getTypeParameters(gd):
                        if genericPT == clsParam:
                            return 'O'
        array = ''
        while cls.isArray():
            array += '['
            cls = cls.getComponentType()
        clsName = cls.getName()
        if cls.isPrimitive():
            return array + PRIMITIVES[clsName]
        if clsName == 'java.lang.String':
            return array + 's'
        if clsName == 'java.lang.Object':
            return array + 'o'
        if is_boxed(clsName):
            return array + 'O'
        if generics and getTypeParameters(cls):
            return array + 'K'
        else:
            return array + 'k'

    def checkarg(cls, genericPT=None):
        if generics and TypeVariable.instance_(genericPT):
            if cls.getName() == 'java.lang.Object':
                gd = TypeVariable.cast_(genericPT).getGenericDeclaration()
                if gd == current:
                    i = 0
                    for clsParam in getTypeParameters(gd):
                        if genericPT == clsParam:
                            return ', self->parameters[%d]' %(i)
                        i += 1
        while cls.isArray():
            cls = cls.getComponentType()
        clsName = cls.getName()
        if (cls.isPrimitive() or
            clsName in ('java.lang.String', 'java.lang.Object')):
            return ''
        if is_boxed(clsName):
            clsNames = clsName.split('.')
            return ', %s::PY_TYPE(%s)' %(absname(cppnames(clsNames[:-1])), cppname(clsNames[-1]))
        return ', %s::initializeClass' %(typename(cls, current, False))

    def callarg(cls, i):
        if generics:
            while cls.isArray():
                cls = cls.getComponentType()
            if getTypeParameters(cls):
                ns, sep, n = typename(cls, current, False).rpartition('::')
                return ', &a%d, &p%d, %s%st_%s::parameters_' %(i, i, ns, sep, n)
        return ', &a%d' %(i)

    if genericParams:
        sig = ''.join([signature(param, genericParam)
                       for param, genericParam in zip(params, genericParams)])
        chk = ''.join([checkarg(param, genericParam)
                       for param, genericParam in zip(params, genericParams)])
    else:
        sig = ''.join([signature(param) for param in params])
        chk = ''.join([checkarg(param) for param in params])

    return (sig, chk,
            ''.join([callarg(params[i], i) for i in range(len(params))]))


def declareVars(out, indent, params, current, generics, typeParams):

    for i in range(len(params)):
        param = params[i]
        line(out, indent, '%s a%d%s;',
             typename(param, current, False), i,
             not param.isPrimitive() and '((jobject) NULL)' or '')
        if generics:
            while param.isArray():
                param = param.getComponentType()
            if getTypeParameters(param):
                line(out, indent, 'PyTypeObject **p%d;', i)
                typeParams.add(i)


def construct(out, indent, cls, inCase, constructor, names, generics):

    if inCase:
        line(out, indent, '{')
        indent += 1

    params = constructor.getParameterTypes()
    if generics:
        typeParams = set()
    else:
        typeParams = None

    count = len(params)

    declareVars(out, indent, params, cls, generics, typeParams)
    line(out, indent, '%s object((jobject) NULL);', cppname(names[-1]))

    line(out)
    if count:
        line(out, indent, 'if (!parseArgs(args, "%s"%s%s))',
             *parseArgs(params, cls, generics))
        line(out, indent, '{')
        indent += 1

    line(out, indent, 'INT_CALL(object = %s(%s));',
         cppname(names[-1]), ', '.join(['a%d' %(i) for i in range(count)]))
    line(out, indent, 'self->object = object;')

    if generics:
        clsParams = getTypeParameters(cls)
        i = 0
        for clsParam in clsParams:
            if Class.instance_(clsParam):
                cls = Class.cast_(clsParam)
                if cls.isArray():
                    cls = cls.getComponentType()
                    if cls.isArray():
                        clsNames = 'java.lang.Object'.split('.')
                        clsArg = '%s::PY_TYPE(%s)' %(absname(cppnames(clsNames[:-1])), cppname(clsNames[-1]))
                    elif cls.isPrimitive():
                        clsArg = 'PY_TYPE(JArray%s)' %(cls.getName().capitalize())
                    else:
                        clsArg = 'PY_TYPE(JArrayObject)'
                else:
                    clsNames = cls.getName().split('.')
                    clsArg = '%s::PY_TYPE(%s)' %(absname(cppnames(clsNames[:-1])), cppname(clsNames[-1]))
                line(out, indent, 'self->parameters[%d] = %s;', i, clsArg)
            i += 1

    if inCase:
        line(out, indent, 'break;')

    if count:
        indent -= 1
        line(out, indent, '}')

    if inCase:
        indent -= 1
        line(out, indent, '}')


def fieldValue(cls, value, fieldType):

    if fieldType.isArray():
        fieldType = fieldType.getComponentType()
        if fieldType.isArray():
            result = 'JArray<jobject>(%s->this$).wrap(NULL)'
        elif fieldType.isPrimitive():
            result = '%s->wrap()'
        elif fieldType.getName() == 'java.lang.String':
            result = 'JArray<jstring>(%s->this$).wrap()'
        else:
            parts = typename(fieldType, cls, False).rpartition('::')
            result = 'JArray<jobject>(%%s->this$).wrap(%s%st_%s::wrap_jobject)' %(parts)

    elif fieldType.getName() == 'java.lang.String':
        result = 'j2p(*%s)'

    elif not fieldType.isPrimitive():
        parts = typename(fieldType, cls, False).rpartition('::')
        result = '%s%st_%s::wrap_Object(*%%s)' %(parts)

    else:
        return value

    return result %(value)


def returnValue(cls, returnType, value, genericRT=None, typeParams=None):

    result = RESULTS.get(returnType.getName())
    if result:
        return result %(value)

    if returnType.isArray():
        returnType = returnType.getComponentType()
        depth = 1
        while returnType.isArray():
            returnType = returnType.getComponentType()
            depth += 1
        if depth > 1:
            return 'return JArray<jobject>(%s.this$).wrap(NULL);' %(value)
        elif returnType.isPrimitive():
            return 'return %s.wrap();' %(value)
        elif returnType.getName() == 'java.lang.String':
            return 'return JArray<jstring>(%s.this$).wrap();' %(value)

        ns, sep, n = typename(returnType, cls, False).rpartition('::')
        return 'return JArray<jobject>(%s.this$).wrap(%s%st_%s::wrap_jobject);' %(value, ns, sep, n)

    ns, sep, n = typename(returnType, cls, False).rpartition('::')
    if genericRT is not None:
        if ParameterizedType.instance_(genericRT):
            genericRT = ParameterizedType.cast_(genericRT)
            clsArgs = []
            for clsArg in getActualTypeArguments(genericRT):
                if Class.instance_(clsArg):
                    clsArg = Class.cast_(clsArg)
                    if clsArg.isArray():
                        clsArg = Class.forName('java.lang.Object')
                    clsNames = clsArg.getName().split('.')
                    clsArg = '%s::PY_TYPE(%s)' %(absname(cppnames(clsNames[:-1])), cppname(clsNames[-1]))
                    clsArgs.append(clsArg)
                elif TypeVariable.instance_(clsArg):
                    gd = TypeVariable.cast_(clsArg).getGenericDeclaration()
                    if Class.instance_(gd):
                        i = 0
                        for clsParam in getTypeParameters(gd):
                            if clsArg == clsParam:
                                clsArgs.append('self->parameters[%d]' %(i))
                                break
                            i += 1
                        else:
                            break
                    else:
                        break
                else:
                    break
            else:
                return 'return %s%st_%s::wrap_Object(%s, %s);' %(ns, sep, n, value, ', '.join(clsArgs))

        elif TypeVariable.instance_(genericRT):
            gd = TypeVariable.cast_(genericRT).getGenericDeclaration()
            i = 0
            if Class.instance_(gd):
                for clsParam in getTypeParameters(gd):
                    if genericRT == clsParam:
                        return 'return self->parameters[%d] != NULL ? wrapType(self->parameters[%d], %s.this$) : %s%st_%s::wrap_Object(%s);' %(i, i, value, ns, sep, n, value)
                    i += 1
            elif Method.instance_(gd):
                for clsParam in getTypeParameters(gd):
                    if genericRT == clsParam and i in typeParams:
                        return 'return p%d != NULL && p%d[0] != NULL ? wrapType(p%d[0], %s.this$) : %s%st_%s::wrap_Object(%s);' %(i, i, i, value, ns, sep, n, value)
                    i += 1

    return 'return %s%st_%s::wrap_Object(%s);' %(ns, sep, n, value)


def call(out, indent, cls, inCase, method, names, cardinality, isExtension,
         generics):

    if inCase:
        line(out, indent, '{')
        indent += 1

    name = method.getName()
    modifiers = method.getModifiers()
    params = method.getParameterTypes()
    returnType = method.getReturnType()
    if generics:
        genericRT = method.getGenericReturnType()
        genericParams = method.getGenericParameterTypes()
        typeParams = set()
    else:
        genericRT = None
        genericParams = None
        typeParams = None
    count = len(params)

    declareVars(out, indent, params, cls, generics, typeParams)

    returnName = returnType.getName()
    if returnName != 'void':
        line(out, indent, '%s result%s;',
             typename(returnType, cls, False),
             not returnType.isPrimitive() and '((jobject) NULL)' or '')
        result = 'result = '
    else:
        result = ''

    if cardinality and (count or not inCase):
        s = cardinality > 1 and 's' or ''
        line(out)
        if isExtension and name == 'clone' and Modifier.isNative(modifiers):
            line(out, indent, 'if (arg)')
        else:
            line(out, indent, 'if (!parseArg%s(arg%s, "%s"%s%s))',
                 s, s, *parseArgs(params, cls, generics, genericParams))
        line(out, indent, '{')
        indent += 1

    name = cppname(name)
    if Modifier.isStatic(modifiers):
        line(out, indent, 'OBJ_CALL(%s%s::%s(%s));',
             result, absname(cppnames(names)), name,
             ', '.join(['a%d' %(i) for i in range(count)]))
    else:
        line(out, indent, 'OBJ_CALL(%sself->object.%s(%s));',
             result, name, ', '.join(['a%d' %(i) for i in range(count)]))

    if isExtension and name == 'clone' and Modifier.isNative(modifiers):
        line(out)
        line(out, indent, '%s object(result.this$);', typename(cls, cls, False))
        line(out, indent, 'if (PyObject_TypeCheck(arg, PY_TYPE(FinalizerProxy)) &&')
        line(out, indent, '    PyObject_TypeCheck(((t_fp *) arg)->object, Py_TYPE(self)))')
        line(out, indent, '{')
        line(out, indent + 1, 'PyObject *_arg = ((t_fp *) arg)->object;')
        line(out, indent + 1, '((t_JObject *) _arg)->object = object;')
        line(out, indent + 1, 'Py_INCREF(_arg);')
        line(out, indent + 1, 'object.pythonExtension((jlong) (Py_intptr_t) (void *) _arg);')
        line(out, indent + 1, 'Py_INCREF(arg);')
        line(out, indent + 1, 'return arg;')
        line(out, indent, '}')
        line(out, indent, 'return PyErr_SetArgsError("%s", arg);' %(name))
    elif returnName != 'void':
        line(out, indent, returnValue(cls, returnType, 'result',
                                      genericRT, typeParams))
    else:
        line(out, indent, 'Py_RETURN_NONE;')
    if cardinality and (count or not inCase):
        indent -= 1
        line(out, indent, '}')

    if inCase:
        indent -= 1
        line(out, indent, '}')


def methodargs(methods, superMethods):

    if len(methods) == 1 and methods[0].getName() not in superMethods:
        count = len(methods[0].getParameterTypes())
        if count == 0:
            return '', '', 0
        elif count == 1:
            return ', PyObject *arg', ', arg', 1

    return ', PyObject *args', ', args', 2


def jniname(cls):

    if cls.isPrimitive():
        name = cls.getName()
        if name != 'void':
            name = 'j' + name
    else:
        name = 'jobject'

    return name


def jniargs(params):

    count = len(params)
    decls = ', '.join(['%s a%d' %(jniname(params[i]), i)
                       for i in range(count)])
    if decls:
        return ', ' + decls

    return ''


def wrapper_typename(returnType, cls):

    if returnType.isArray():
        componentType = returnType.getComponentType()
        componentName = typename(componentType, cls, False)
        if componentType.isPrimitive():
            return "t_JArrayWrapper< %s >" %(componentName)

        ns, sep, n = componentName.rpartition('::')
        return "t_JArrayWrapper< jobject,%s%st_%s >" %(ns, sep, n)

    returnName = typename(returnType, cls, False)
    ns, sep, n = returnName.rpartition('::')
    return "%s%st_%s" %(ns, sep, n)


def extension(env, out, indent, cls, names, name, count, method, generics):

    line(out, indent, 'jlong ptr = jenv->CallLongMethod(jobj, %s::mids$[%s::mid_pythonExtension_%s]);',
         cppname(names[-1]), cppname(names[-1]), env.strhash('()J'))
    line(out, indent, 'PyObject *obj = (PyObject *) (Py_intptr_t) ptr;')

    if name == 'pythonDecRef':
        line(out)
        line(out, indent, 'if (obj != NULL)')
        line(out, indent, '{')
        line(out, indent + 1, 'jenv->CallVoidMethod(jobj, %s::mids$[%s::mid_pythonExtension_%s], (jlong) 0);',
             cppname(names[-1]), cppname(names[-1]), env.strhash('(J)V'))
        line(out, indent + 1, 'env->finalizeObject(jenv, obj);')
        line(out, indent, '}')
        return

    line(out, indent, 'PythonGIL gil(jenv);')

    returnType = method.getReturnType()
    returnName = returnType.getName()
    if returnName != 'void':
        line(out, indent, '%s value%s;',
             typename(returnType, cls, False),
             not returnType.isPrimitive() and '((jobject) NULL)' or '')

    sigs = []
    decrefs = []
    args = []
    i = 0
    for param in method.getParameterTypes():
        typeName = param.getName()
        if typeName in CALLARGS:
            sig, code, decref = CALLARGS[typeName]
        elif param.isArray():
            param = param.getComponentType()
            if param.isPrimitive():
                code = 'JArray<j%s>(%%s).wrap()' %(param.getName())
            elif param.isArray():
                code = 'JArray<jobject>(%s).wrap(NULL)'
            elif param.getName() == 'java.lang.String':
                code = 'JArray<jstring>(%s).wrap()'
            else:
                parts = typename(param, cls, False).rpartition('::')
                code = 'JArray<jobject>(%%s).wrap(%s%st_%s::wrap_jobject)' %(parts)
            sig, decref = 'O', True
        elif param.getName() == 'java.lang.String':
            sig, code, decref = 'O', 'j2p(%%s))', True
        else:
            parts = typename(param, cls, False).rpartition('::')
            sig, code, decref = 'O', '%s%st_%s::wrap_Object(%s%s%s(%%s))' %(parts*2), True
        if sig == 'O':
            line(out, indent, 'PyObject *o%d = %s;', i, code %('a%d' %(i)))
            args.append('o%d' %(i))
        else:
            args.append(code %('a%d' %(i)))
        sigs.append(sig)
        decrefs.append(decref)
        i += 1

    args = ', '.join(args)
    if args:
        args = ', ' + args
    line(out, indent, 'PyObject *result = PyObject_CallMethod(obj, "%s", "%s"%s);',
         name, ''.join(sigs), args)
    i = 0
    for decref in decrefs:
        if decref:
            line(out, indent, 'Py_DECREF(o%d);', i)
        i += 1
    line(out, indent, 'if (!result)')
    line(out, indent + 1, 'throwPythonError();')
    if returnName == 'void':
        line(out, indent, 'else')
        line(out, indent + 1, 'Py_DECREF(result);')
    else:
        signature, check, x = parseArgs([returnType], cls, False)
        line(out, indent, 'else if (parseArg(result, "%s"%s, &value))',
             signature, check)
        line(out, indent, '{')
        line(out, indent + 1, 'throwTypeError("%s", result);', name)
        line(out, indent + 1, 'Py_DECREF(result);')
        line(out, indent, '}')
        line(out, indent, 'else')
        line(out, indent, '{')
        if not returnType.isPrimitive():
            line(out, indent + 1, 'jobj = jenv->NewLocalRef(value.this$);')
        line(out, indent + 1, 'Py_DECREF(result);')
        if returnType.isPrimitive():
            line(out, indent + 1, 'return value;')
        else:
            line(out, indent + 1, 'return jobj;')
        line(out, indent, '}')
        line(out)
        if returnType.isPrimitive():
            line(out, indent, 'return (j%s) 0;', returnName)
        else:
            line(out, indent, 'return (jobject) NULL;')


def python(env, out_h, out, cls, superClasses, names, superNames,
           constructors, methods, protectedMethods,
           methodNames, fields, instanceFields,
           mapping, sequence, rename, declares, typeset, moduleName, generics,
           _dll_export):

    line(out_h)
    line(out_h, 0, '#include <Python.h>')
    line(out_h)

    indent = 0
    for name in names[:-1]:
        line(out_h, indent, 'namespace %s {', cppname(name))
        indent += 1
    line(out_h, indent, '%sextern PyType_Def PY_TYPE_DEF(%s);',
         _dll_export, names[-1])
    line(out_h, indent, '%sextern PyTypeObject *PY_TYPE(%s);',
         _dll_export, names[-1])

    if generics:
        clsParams = getTypeParameters(cls)
    else:
        clsParams = None

    line(out_h)
    line(out_h, indent, 'class %st_%s {', _dll_export, names[-1])
    line(out_h, indent, 'public:')
    line(out_h, indent + 1, 'PyObject_HEAD')
    line(out_h, indent + 1, '%s object;', cppname(names[-1]))
    if clsParams:
        line(out_h, indent + 1, 'PyTypeObject *parameters[%d];', len(clsParams))
        line(out_h, indent + 1, 'static PyTypeObject **parameters_(t_%s *self)',
             cppname(names[-1]))
        line(out_h, indent + 1, '{')
        line(out_h, indent + 2, 'return (PyTypeObject **) &(self->parameters);')
        line(out_h, indent + 1, '}')

    line(out_h, indent + 1, 'static PyObject *wrap_Object(const %s&);',
         cppname(names[-1]))
    line(out_h, indent + 1, 'static PyObject *wrap_jobject(const jobject&);')
    if clsParams:
        _clsParams = ', '.join(['PyTypeObject *'] * len(clsParams))
        line(out_h, indent + 1, 'static PyObject *wrap_Object(const %s&, %s);',
             cppname(names[-1]), _clsParams)
        line(out_h, indent + 1, 'static PyObject *wrap_jobject(const jobject&, %s);', _clsParams)
    line(out_h, indent + 1, 'static void install(PyObject *module);')
    line(out_h, indent + 1, 'static void initialize(PyObject *module);')
    line(out_h, indent, '};')

    if env.java_version >= '1.5':
        iterable = findClass('java/lang/Iterable')
        iterator = findClass('java/util/Iterator')
    else:
        iterable = iterator = None

    enumeration = findClass('java/util/Enumeration')

    while indent:
        indent -= 1
        line(out_h, indent, '}')

    line(out)
    line(out, 0, '#include "structmember.h"')
    line(out, 0, '#include "functions.h"')
    line(out, 0, '#include "macros.h"')

    for inner in cls.getDeclaredClasses():
        if inner in typeset and not inner in declares:
            if Modifier.isStatic(inner.getModifiers()):
                line(out, 0, '#include "%s.h"',
                     inner.getName().replace('.', '/'))

    for method in methods:
        if method.getName() == 'pythonExtension':
            isExtension = True
            break
    else:
        isExtension = False

    line(out)
    indent = 0
    for name in names[:-1]:
        line(out, indent, 'namespace %s {', cppname(name))
        indent += 1

    line(out, indent, 'static PyObject *t_%s_cast_(PyTypeObject *type, PyObject *arg);', names[-1])
    line(out, indent, 'static PyObject *t_%s_instance_(PyTypeObject *type, PyObject *arg);', names[-1])
    if clsParams:
        line(out, indent,
             'static PyObject *t_%s_of_(t_%s *self, PyObject *args);',
             names[-1], names[-1])

    if constructors:
        line(out, indent, 'static int t_%s_init_(t_%s *self, PyObject *args, PyObject *kwds);', names[-1], names[-1])
        constructorName = 't_%s_init_' %(names[-1])
    else:
        constructorName = 'abstract_init'

    if superClasses:
        superMethods = set([method.getName()
                            for superCls in superClasses
                            for method in superCls.getMethods()])
    else:
        superMethods = ()

    allMethods = {}
    extMethods = {}
    propMethods = {}

    if methods:
        for method in methods:
            modifiers = method.getModifiers()
            name = method.getName()
            params = method.getParameterTypes()
            superMethod = None
            isNative = Modifier.isNative(modifiers)
            isStatic = Modifier.isStatic(modifiers)

            if (isExtension and not isStatic and superClasses and isNative):
                superMethod = find_method(superClasses[0], name, params)

            if isExtension and isNative and not isStatic:
                extMethods.setdefault(name, []).append(method)

            if superMethod or not (isExtension and isNative and not isStatic):
                if isStatic:
                    if name in allMethods:
                        if Modifier.isStatic(allMethods[name][0].getModifiers()):
                            allMethods[name].append(method)
                        elif name + RENAME_METHOD_SUFFIX in allMethods:
                            allMethods[name + RENAME_METHOD_SUFFIX].append(method)
                        else:
                            print("  Warning: renaming static method '%s' on class %s to '%s%s' since it is shadowed by non-static method of same name." %(name, '.'.join(names), name, RENAME_METHOD_SUFFIX), file=sys.stderr)
                            allMethods[name + RENAME_METHOD_SUFFIX] = [method]
                    else:
                        allMethods[name] = [method]
                else:
                    if name in allMethods:
                        if Modifier.isStatic(allMethods[name][0].getModifiers()):
                            print("  Warning: renaming static method '%s' on class %s to '%s%s' since it is shadowed by non-static method of same name." %(name, '.'.join(names), name, RENAME_METHOD_SUFFIX), file=sys.stderr)
                            allMethods[name + RENAME_METHOD_SUFFIX] = allMethods[name]
                            allMethods[name] = [method]
                        else:
                            allMethods[name].append(method)
                    else:
                        allMethods[name] = [method]

            if not (isExtension and isNative):
                nameLen = len(name)
                paramsLen = len(params)
                if nameLen > 3 and paramsLen == 0 and name.startswith('get'):
                    if method.getReturnType().getName() != 'void':
                        propMethods.setdefault(name[3].lower() + name[4:],
                                               []).append(method)
                elif nameLen > 3 and paramsLen == 1 and name.startswith('set'):
                    propMethods.setdefault(name[3].lower() + name[4:],
                                           []).append(method)
                elif nameLen > 2 and paramsLen == 0 and name.startswith('is'):
                    if method.getReturnType().getName() != 'void':
                        propMethods.setdefault(name[2].lower() + name[3:],
                                               []).append(method)

    properties = set([name for name in propMethods.keys()
                      if name not in allMethods])
    propMethods = [(name, propMethods[name]) for name in properties]
    propMethods.sort(key=lambda x: x[0])

    extMethods = list(extMethods.items())
    extMethods.sort(key=lambda x: x[0])
    allMethods = list(allMethods.items())
    allMethods.sort(key=lambda x: x[0])

    iteratorMethod = None
    iteratorExt = False
    nextMethod = None
    nextExt = False
    nextElementMethod = None
    nextElementExt = False

    mappingMethod = None
    if mapping:
        mappingName, mappingSig = mapping.split(':')

    sequenceLenMethod = None
    sequenceGetMethod = None
    if sequence:
        sequenceLenName, sequenceLenSig = sequence[0].split(':')
        sequenceGetName, sequenceGetSig = sequence[1].split(':')

    for name, methods in allMethods:
        args, x, cardinality = methodargs(methods, superMethods)
        sortSignatures(methods)
        method = methods[0]
        modifiers = method.getModifiers()
        if name == 'iterator' and iteratorMethod is None:
            if (iterable is not None and
                not method.getParameterTypes() and
                iterable.isAssignableFrom(cls) and
                iterator.isAssignableFrom(method.getReturnType())):
                iteratorMethod = method
        elif name == 'next' and nextMethod is None:
            if (not method.getParameterTypes() and
                not method.getReturnType().isPrimitive()):
                nextMethod = method
        elif name == 'nextElement' and nextElementMethod is None:
            if (not method.getParameterTypes() and
                not method.getReturnType().isPrimitive()):
                nextElementMethod = method
        elif mapping and name == mappingName and mappingMethod is None:
            if signature(method) == mappingSig:
                mappingMethod = (method, cardinality)
        elif sequence and name == sequenceLenName and sequenceLenMethod is None:
            if signature(method) == sequenceLenSig:
                sequenceLenMethod = (method, cardinality)
        elif sequence and name == sequenceGetName and sequenceGetMethod is None:
            if signature(method) == sequenceGetSig:
                sequenceGetMethod = (method, cardinality)
        elif isExtension and name == 'clone' and Modifier.isNative(modifiers):
            args, x, cardinality = ', PyObject *arg', ', arg', 1

        if Modifier.isStatic(modifiers):
            line(out, indent, 'static PyObject *t_%s_%s(PyTypeObject *type%s);',
                 names[-1], name, args)
        else:
            line(out, indent, 'static PyObject *t_%s_%s(t_%s *self%s);',
                 names[-1], name, names[-1], args)

    for name, methods in extMethods:
        args, x, cardinality = methodargs(methods, superMethods)
        sortSignatures(methods)
        method = methods[0]
        modifiers = method.getModifiers()
        if name == 'iterator' and iteratorMethod is None:
            if (iterable is not None and
                not method.getParameterTypes() and
                iterable.isAssignableFrom(cls) and
                iterator.isAssignableFrom(method.getReturnType())):
                iteratorMethod = method
                iteratorExt = True
        elif name == 'next' and nextMethod is None:
            if (not method.getParameterTypes() and
                not method.getReturnType().isPrimitive()):
                nextMethod = method
                nextExt = True
        elif name == 'nextElement' and nextElementMethod is None:
            if (not method.getParameterTypes() and
                not method.getReturnType().isPrimitive()):
                nextElementMethod = method
                nextElementExt = True

    if isExtension:
        count = 0
        for name, methods in extMethods:
            for method in methods:
                line(out, indent,
                     'static %s JNICALL t_%s_%s%d(JNIEnv *jenv, jobject jobj%s);',
                     jniname(method.getReturnType()), names[-1], name, count,
                     jniargs(method.getParameterTypes()))
                count += 1
        line(out, indent, 'static PyObject *t_%s_get__self(t_%s *self, void *data);', names[-1], names[-1])

    if instanceFields:
        for field in instanceFields:
            fieldName = field.getName()
            if fieldName not in properties:
                line(out, indent, 'static PyObject *t_%s_get__%s(t_%s *self, void *data);',
                     names[-1], fieldName, names[-1])
                if not Modifier.isFinal(field.getModifiers()):
                    line(out, indent, 'static int t_%s_set__%s(t_%s *self, PyObject *arg, void *data);',
                         names[-1], field.getName(), names[-1])
        line(out)

    for fieldName, methods in propMethods:
        getter = False
        setter = False
        for method in methods:
            methodName = method.getName()
            if not getter and (methodName.startswith('get') or
                               methodName.startswith('is')):
                getter = True
                line(out, indent, 'static PyObject *t_%s_get__%s(t_%s *self, void *data);',
                     names[-1], fieldName, names[-1])
            elif not setter and methodName.startswith('set'):
                setter = True
                line(out, indent, 'static int t_%s_set__%s(t_%s *self, PyObject *arg, void *data);',
                     names[-1], fieldName, names[-1])
    if clsParams:
        line(out, indent, 'static PyObject *t_%s_get__parameters_(t_%s *self, void *data);', names[-1], names[-1])

    if instanceFields or propMethods or isExtension or clsParams:
        line(out, indent, 'static PyGetSetDef t_%s__fields_[] = {', names[-1])
        for field in instanceFields:
            fieldName = field.getName()
            if fieldName not in properties:
                if Modifier.isFinal(field.getModifiers()):
                    line(out, indent + 1, 'DECLARE_GET_FIELD(t_%s, %s),',
                         names[-1], fieldName)
                else:
                    line(out, indent + 1, 'DECLARE_GETSET_FIELD(t_%s, %s),',
                         names[-1], fieldName)
        for fieldName, methods in propMethods:
            getter = False
            setter = False
            for method in methods:
                methodName = method.getName()
                if not getter and (methodName.startswith('get') or
                                   methodName.startswith('is')):
                    getter = True
                elif not setter and methodName.startswith('set'):
                    setter = True
                if getter and setter:
                    op = 'GETSET'
                elif getter:
                    op = 'GET'
                elif setter:
                    op = 'SET'
            line(out, indent + 1, 'DECLARE_%s_FIELD(t_%s, %s),',
                 op, names[-1], fieldName)
        if isExtension:
            line(out, indent + 1, 'DECLARE_GET_FIELD(t_%s, self),', names[-1])
        if clsParams:
            line(out, indent + 1, 'DECLARE_GET_FIELD(t_%s, parameters_),',
                 names[-1])

        line(out, indent + 1, '{ NULL, NULL, NULL, NULL, NULL }')
        line(out, indent, '};')

    line(out)
    line(out, indent, 'static PyMethodDef t_%s__methods_[] = {', names[-1])

    line(out, indent + 1,
         'DECLARE_METHOD(t_%s, cast_, METH_O | METH_CLASS),', names[-1])
    line(out, indent + 1,
         'DECLARE_METHOD(t_%s, instance_, METH_O | METH_CLASS),', names[-1])
    if clsParams:
        line(out, indent + 1,
             'DECLARE_METHOD(t_%s, of_, METH_VARARGS),', names[-1])

    for name, methods in allMethods:
        modifiers = methods[0].getModifiers()
        if len(methods) == 1 and not name in superMethods:
            count = len(methods[0].getParameterTypes())
            if count == 0:
                args = 'METH_NOARGS'
            elif count == 1:
                args = 'METH_O'
            else:
                args = 'METH_VARARGS'
        elif isExtension and name == 'clone' and Modifier.isNative(modifiers):
            args = 'METH_O'
        else:
            args = 'METH_VARARGS'
        if Modifier.isStatic(modifiers):
            args += ' | METH_CLASS'

        line(out, indent + 1, 'DECLARE_METHOD(t_%s, %s, %s),',
             names[-1], name, args)
    line(out, indent + 1, '{ NULL, NULL, 0, NULL }')
    line(out, indent, '};')

    if instanceFields or propMethods or isExtension or clsParams:
        tp_getset = 't_%s__fields_' %(names[-1])
    else:
        tp_getset = None

    if iteratorMethod:
        if iteratorExt:
            tp_iter = 'get_extension_iterator'
        else:
            tp_iter = '((PyObject *(*)(t_%s *)) get_%siterator< t_%s >)' %(names[-1], clsParams and 'generic_' or '', names[-1])
        tp_iternext = '0'
    elif nextMethod and iterable is not None and iterator.isAssignableFrom(cls):
        tp_iter = 'PyObject_SelfIter'
        if nextExt:
            tp_iternext = 'get_extension_next'
        else:
            tp_iternext = '((PyObject *(*)(::java::util::t_Iterator *)) get_%siterator_next< ::java::util::t_Iterator,%s >)' %(clsParams and 'generic_' or '', wrapper_typename(nextMethod.getReturnType(), cls))
    elif nextElementMethod and enumeration.isAssignableFrom(cls):
        tp_iter = 'PyObject_SelfIter'
        returnName = typename(nextElementMethod.getReturnType(), cls, False)
        ns, sep, n = returnName.rpartition('::')
        if nextElementExt:
            tp_iternext = 'get_extension_nextElement'
        else:
            tp_iternext = '((PyObject *(*)(::java::util::t_Enumeration *)) get_%senumeration_next< ::java::util::t_Enumeration,%s >)' %(clsParams and 'generic_' or '', wrapper_typename(nextElementMethod.getReturnType(), cls))
    elif nextMethod:
        tp_iter = 'PyObject_SelfIter'
        if nextExt:
            tp_iternext = 'get_extension_next'
        else:
            returnType = nextMethod.getReturnType()
            tp_iternext = '((PyObject *(*)(t_%s *)) get_%snext< t_%s,%s,%s >)' %(names[-1], clsParams and 'generic_' or '', names[-1], wrapper_typename(returnType, cls), typename(returnType, cls, False))
    else:
        tp_iter = None
        tp_iternext = None

    if mappingMethod:
        method, cardinality = mappingMethod
        if cardinality > 1:
            mp_subscript = 't_%s_%s_map_' %(names[-1], method.getName())
            line(out, indent, 'static PyObject *%s(t_%s *self, PyObject *key);',
                 mp_subscript, names[-1])
        else:
            mp_subscript = 't_%s_%s' %(names[-1], method.getName())
    else:
        mp_subscript = None

    if sequenceLenMethod:
        method, cardinality = sequenceLenMethod
        sq_length = 't_%s_%s_seq_' %(names[-1], method.getName())
        line(out, indent, 'static int %s(t_%s *self);',
             sq_length, names[-1])
    else:
        sq_length = None

    if sequenceGetMethod:
        method, cardinality = sequenceGetMethod
        sq_item = 't_%s_%s_seq_' %(names[-1], method.getName())
        line(out, indent, 'static PyObject *%s(t_%s *self, int n);',
             sq_item, names[-1])
    else:
        sq_item = None

    line(out)
    line(out, indent, 'static PyType_Slot PY_TYPE_SLOTS(%s)[] = {', names[-1])
    line(out, indent + 1, '{ Py_tp_methods, t_%s__methods_ },', names[-1])
    line(out, indent + 1, '{ Py_tp_init, (void *) %s },', constructorName)
    if tp_getset is not None:
        line(out, indent + 1, '{ Py_tp_getset, %s },', tp_getset)
    if tp_iter is not None:
        line(out, indent + 1, '{ Py_tp_iter, (void *) %s },', tp_iter)
        line(out, indent + 1, '{ Py_tp_iternext, (void *) %s },', tp_iternext)
    if mp_subscript is not None:
        line(out, indent + 1, '{ Py_mp_subscript, (void *) %s },', mp_subscript)
    if sq_length is not None:
        line(out, indent + 1, '{ Py_sq_length, (void *) %s },', sq_length)
    if sq_item is not None:
        line(out, indent + 1, '{ Py_sq_item, (void *) %s },', sq_item)
    line(out, indent + 1, '{ 0, NULL }')
    line(out, indent, '};')

    line(out)
    line(out, indent, 'static PyType_Def *PY_TYPE_BASES(%s)[] = {', names[-1])
    for superName in superNames:
        if len(superName) > 1:
            base = '::'.join((absname(cppnames(superName[:-1])), superName[-1]))
        else:
            base = superName[-1]
        line(out, indent + 1, '&PY_TYPE_DEF(%s),', base)
    line(out, indent + 1, 'NULL')
    line(out, indent, '};')

    line(out)
    line(out, indent, 'DEFINE_TYPE(%s, t_%s, %s);',
         names[-1], names[-1], cppname(names[-1]))

    if clsParams:
        clsArgs = []
        i = 0
        for clsParam in clsParams:
            clsArgs.append("PyTypeObject *p%d" %(i))
            i += 1
        line(out, indent,
             "PyObject *t_%s::wrap_Object(const %s& object, %s)",
             cppname(names[-1]), names[-1], ', '.join(clsArgs))
        line(out, indent, "{")
        line(out, indent + 1, "PyObject *obj = t_%s::wrap_Object(object);",
             names[-1])
        line(out, indent + 1, "if (obj != NULL && obj != Py_None)")
        line(out, indent + 1, "{")
        line(out, indent + 2, "t_%s *self = (t_%s *) obj;",
             names[-1], names[-1])
        i = 0;
        for clsParam in clsParams:
            line(out, indent + 2, "self->parameters[%d] = p%d;",
                 i, i)
            i += 1
        line(out, indent + 1, "}")
        line(out, indent + 1, "return obj;");
        line(out, indent, "}")

        line(out)
        line(out, indent,
             "PyObject *t_%s::wrap_jobject(const jobject& object, %s)",
             cppname(names[-1]), ', '.join(clsArgs))
        line(out, indent, "{")
        line(out, indent + 1, "PyObject *obj = t_%s::wrap_jobject(object);",
             names[-1])
        line(out, indent + 1, "if (obj != NULL && obj != Py_None)")
        line(out, indent + 1, "{")
        line(out, indent + 2, "t_%s *self = (t_%s *) obj;",
             names[-1], names[-1])
        i = 0;
        for clsParam in clsParams:
            line(out, indent + 2, "self->parameters[%d] = p%d;",
                 i, i)
            i += 1
        line(out, indent + 1, "}")
        line(out, indent + 1, "return obj;");
        line(out, indent, "}")

    line(out)
    line(out, indent, 'void t_%s::install(PyObject *module)', names[-1])
    line(out, indent, '{')
    line(out, indent + 1,
         'installType(&PY_TYPE(%s), &PY_TYPE_DEF(%s), module, "%s", %d);',
         names[-1], names[-1], rename or names[-1], isExtension and 1 or 0)
    for inner in cls.getDeclaredClasses():
        if inner in typeset:
            if Modifier.isStatic(inner.getModifiers()):
                innerName = inner.getName().split('.')[-1]
                line(out, indent + 1, 'PyObject_SetAttrString((PyObject *) PY_TYPE(%s), "%s", make_descriptor(&PY_TYPE_DEF(%s)));',
                     names[-1], innerName[len(names[-1])+1:], innerName)
    line(out, indent, '}')

    line(out)
    line(out, indent, 'void t_%s::initialize(PyObject *module)', names[-1])
    line(out, indent, '{')
    line(out, indent + 1, 'PyObject_SetAttrString((PyObject *) PY_TYPE(%s), "class_", make_descriptor(%s::initializeClass, %s));',
         names[-1], cppname(names[-1]), generics and 1 or 0)

    if is_unboxed(cls.getName()):
        wrapfn_ = "unbox%s" %(names[-1])
        boxfn_ = "box%s" %(names[-1])
    else:
        wrapfn_ = "t_%s::wrap_jobject" %(names[-1])
        boxfn_ = "boxObject"

    line(out, indent + 1, 'PyObject_SetAttrString((PyObject *) PY_TYPE(%s), "wrapfn_", make_descriptor(%s));', names[-1], wrapfn_)
    line(out, indent + 1, 'PyObject_SetAttrString((PyObject *) PY_TYPE(%s), "boxfn_", make_descriptor(%s));', names[-1], boxfn_)

    if isExtension:
        line(out, indent + 1, 'jclass cls = env->getClass(%s::initializeClass);',
             cppname(names[-1]))
    elif fields:
        line(out, indent + 1, 'env->getClass(%s::initializeClass);', cppname(names[-1]))

    if isExtension:
        count = 0
        line(out, indent + 1, 'JNINativeMethod methods[] = {')
        for name, methods in extMethods:
            for method in methods:
                line(out, indent + 2, '{ "%s", "%s", (void *) t_%s_%s%d },',
                     name, signature(method), names[-1], name, count)
                count += 1
        line(out, indent + 1, '};')
        line(out, indent + 1, 'env->registerNatives(cls, methods, %d);',
             count)

    for field in fields:
        fieldType = field.getType()
        fieldName = field.getName()
        cppFieldName = cppname(fieldName)
        if cppFieldName in methodNames:
            fieldName += RENAME_FIELD_SUFFIX
            cppFieldName += RENAME_FIELD_SUFFIX
        value = '%s::%s' %(cppname(names[-1]), cppFieldName)
        value = fieldValue(cls, value, fieldType)
        line(out, indent + 1, 'PyObject_SetAttrString((PyObject *) PY_TYPE(%s), "%s", make_descriptor(%s));',
             names[-1], fieldName, value)
    line(out, indent, '}')

    line(out)
    line(out, indent, 'static PyObject *t_%s_cast_(PyTypeObject *type, PyObject *arg)', names[-1])
    line(out, indent, '{')
    line(out, indent + 1, 'if (!(arg = castCheck(arg, %s::initializeClass, 1)))', cppname(names[-1]))
    line(out, indent + 2, 'return NULL;')
    line(out, indent + 1, 'return t_%s::wrap_Object(%s(((t_%s *) arg)->object.this$));', names[-1], cppname(names[-1]), names[-1])
    line(out, indent, '}')

    line(out, indent, 'static PyObject *t_%s_instance_(PyTypeObject *type, PyObject *arg)', names[-1])
    line(out, indent, '{')
    line(out, indent + 1, 'if (!castCheck(arg, %s::initializeClass, 0))', cppname(names[-1]))
    line(out, indent + 2, 'Py_RETURN_FALSE;')
    line(out, indent + 1, 'Py_RETURN_TRUE;')
    line(out, indent, '}')

    if clsParams:
        line(out)
        line(out, indent,
             'static PyObject *t_%s_of_(t_%s *self, PyObject *args)',
             names[-1], names[-1])
        line(out, indent, '{')
        line(out, indent + 1,
             'if (!parseArg(args, "T", %d, &(self->parameters)))',
             len(clsParams))
        line(out, indent + 2, 'Py_RETURN_SELF;');
        line(out, indent + 1,
             'return PyErr_SetArgsError((PyObject *) self, "of_", args);')
        line(out, indent, '}')

    if constructors:
        line(out)
        line(out, indent, 'static int t_%s_init_(t_%s *self, PyObject *args, PyObject *kwds)', names[-1], names[-1])
        line(out, indent, '{')
        if len(constructors) > 1:
            currLen = -1
            line(out, indent + 1, 'switch (PyTuple_GET_SIZE(args)) {')
            withErr = False
            for constructor in constructors:
                params = constructor.getParameterTypes()
                if len(params) != currLen:
                    if currLen > 0:
                        withErr = True
                        line(out, indent + 2, 'goto err;')
                    currLen = len(params)
                    line(out, indent + 1, '%scase %d:', HALF_INDENT, currLen)
                construct(out, indent + 2, cls, True, constructor, names,
                          generics)
            line(out, indent + 1, '%sdefault:', HALF_INDENT)
            if withErr:
                line(out, indent + 1, '%serr:', HALF_INDENT)
            line(out, indent + 2, 'PyErr_SetArgsError((PyObject *) self, "__init__", args);')
            line(out, indent + 2, 'return -1;')
            line(out, indent + 1, '}')
        else:
            construct(out, indent + 1, cls, False, constructors[0], names,
                      generics)
            if constructors[0].getParameterTypes():
                line(out, indent + 1, 'else')
                line(out, indent + 1, '{')
                line(out, indent + 2, 'PyErr_SetArgsError((PyObject *) self, "__init__", args);')
                line(out, indent + 2, 'return -1;')
                line(out, indent + 1, '}')

        if isExtension:
            line(out)
            line(out, indent + 1, 'Py_INCREF((PyObject *) self);')
            line(out, indent + 1, 'self->object.pythonExtension((jlong) (Py_intptr_t) (void *) self);')

        line(out)
        line(out, indent + 1, 'return 0;')
        line(out, indent , '}')

    for name, methods in allMethods:
        line(out)
        modifiers = methods[0].getModifiers()

        if isExtension and name == 'clone' and Modifier.isNative(modifiers):
            declargs, args, cardinality = ', PyObject *arg', ', arg', 1
        else:
            declargs, args, cardinality = methodargs(methods, superMethods)

        static = Modifier.isStatic(modifiers)
        if static:
            line(out, indent, 'static PyObject *t_%s_%s(PyTypeObject *type%s)',
                 names[-1], name, declargs)
        else:
            line(out, indent, 'static PyObject *t_%s_%s(t_%s *self%s)',
                 names[-1], name, names[-1], declargs)

        line(out, indent, '{')
        if len(methods) > 1:
            currLen = -1
            line(out, indent + 1, 'switch (PyTuple_GET_SIZE(args)) {')
            for method in methods:
                params = method.getParameterTypes()
                if len(params) != currLen:
                    if currLen >= 0:
                        line(out, indent + 2, 'break;')
                    currLen = len(params)
                    line(out, indent + 1, '%scase %d:', HALF_INDENT, currLen)
                call(out, indent + 2, cls, True, method, names, cardinality,
                     isExtension, generics)
            line(out, indent + 1, '}')
        else:
            call(out, indent + 1, cls, False, methods[0], names, cardinality,
                 isExtension, generics)

        if args:
            line(out)
            if name in superMethods:
                if static:
                    line(out, indent + 1, 'return callSuper(type, "%s"%s, %d);',
                         name, args, cardinality)
                else:
                    line(out, indent + 1, 'return callSuper(PY_TYPE(%s), (PyObject *) self, "%s"%s, %d);',
                         names[-1], name, args, cardinality)
            else:
                line(out, indent + 1, 'PyErr_SetArgsError(%s, "%s"%s);',
                     static and 'type' or '(PyObject *) self', name, args)
                line(out, indent + 1, 'return NULL;')

        line(out, indent, '}')

    if isExtension:
        count = 0
        for name, methods in extMethods:
            for method in methods:
                line(out)
                line(out, indent,
                     'static %s JNICALL t_%s_%s%d(JNIEnv *jenv, jobject jobj%s)',
                     jniname(method.getReturnType()), names[-1], name, count,
                     jniargs(method.getParameterTypes()))
                count += 1
                line(out, indent, '{')
                extension(env, out, indent + 1, cls, names, name, count, method,
                          generics)
                line(out, indent, '}')
        line(out)
        line(out, indent, 'static PyObject *t_%s_get__self(t_%s *self, void *data)',
             names[-1], names[-1])
        line(out, indent, '{')
        indent += 1
        line(out, indent, 'jlong ptr;')
        line(out, indent, 'OBJ_CALL(ptr = self->object.pythonExtension());')
        line(out, indent, 'PyObject *obj = (PyObject *) (Py_intptr_t) ptr;')
        line(out)
        line(out, indent, 'if (obj != NULL)')
        line(out, indent, '{')
        line(out, indent + 1, 'Py_INCREF(obj);')
        line(out, indent + 1, 'return obj;')
        line(out, indent, '}')
        line(out, indent, 'else')
        line(out, indent + 1, 'Py_RETURN_NONE;')
        indent -= 1
        line(out, indent, '}')

    if clsParams:
        line(out, indent, 'static PyObject *t_%s_get__parameters_(t_%s *self, void *data)', names[-1], names[-1])
        line(out, indent, '{')
        line(out, indent + 1, 'return typeParameters(self->parameters, sizeof(self->parameters));')
        line(out, indent, '}')

    if instanceFields:
        for field in instanceFields:
            fieldName = field.getName()
            if fieldName not in properties:
                line(out)
                fieldType = field.getType()
                typeName = typename(fieldType, cls, False)
                line(out, indent, 'static PyObject *t_%s_get__%s(t_%s *self, void *data)',
                     names[-1], fieldName, names[-1])
                line(out, indent, '{')
                line(out, indent + 1, '%s value%s;', typeName,
                     not fieldType.isPrimitive() and '((jobject) NULL)' or '')
                line(out, indent + 1, 'OBJ_CALL(value = self->object._get_%s());',
                     fieldName)
                line(out, indent + 1, returnValue(cls, fieldType, 'value'))
                line(out, indent, '}')

                if not Modifier.isFinal(field.getModifiers()):
                    line(out, indent, 'static int t_%s_set__%s(t_%s *self, PyObject *arg, void *data)',
                         names[-1], fieldName, names[-1])
                    line(out, indent, '{')
                    line(out, indent + 1, '%s value%s;', typeName,
                         not fieldType.isPrimitive() and '((jobject) NULL)' or '')
                    sig, check, x = parseArgs([fieldType], cls, False)
                    line(out, indent + 1, 'if (!parseArg(arg, "%s"%s, &value))',
                         sig, check)
                    line(out, indent + 1, '{')
                    line(out, indent + 2, 'INT_CALL(self->object._set_%s(value));',
                         fieldName)
                    line(out, indent + 2, 'return 0;')
                    line(out, indent + 1, '}')
                    line(out, indent + 1, 'PyErr_SetArgsError((PyObject *) self, "%s", arg);',
                         fieldName)
                    line(out, indent + 1, 'return -1;')
                    line(out, indent, '}')

    if propMethods:
        for fieldName, methods in propMethods:
            line(out)
            getter = None
            setters = []
            methods.sort(key=lambda x: x.getName())
            for method in methods:
                methodName = method.getName()
                if not getter and (methodName.startswith('get') or
                                   methodName.startswith('is')):
                    getter = method
                elif methodName.startswith('set'):
                    setters.append(method)

            if getter:
                methodName = getter.getName()
                returnType = getter.getReturnType()
                typeName = typename(returnType, cls, False)
                line(out, indent, 'static PyObject *t_%s_get__%s(t_%s *self, void *data)',
                     names[-1], fieldName, names[-1])
                line(out, indent, '{')
                line(out, indent + 1, '%s value%s;', typeName,
                     not returnType.isPrimitive() and '((jobject) NULL)' or '')
                line(out, indent + 1, 'OBJ_CALL(value = self->object.%s());',
                     methodName)
                line(out, indent + 1, returnValue(cls, returnType, 'value'))
                line(out, indent, '}')

            if setters:
                line(out, indent, 'static int t_%s_set__%s(t_%s *self, PyObject *arg, void *data)',
                     names[-1], fieldName, names[-1])
                line(out, indent, '{')
                methodName = setters[0].getName()
                for method in setters:
                    argType = method.getParameterTypes()[0]
                    typeName = typename(argType, cls, False)
                    line(out, indent + 1, '{')
                    line(out, indent + 2, '%s value%s;', typeName,
                         not argType.isPrimitive() and '((jobject) NULL)' or '')
                    sig, check, x = parseArgs([argType], cls, False)
                    line(out, indent + 2, 'if (!parseArg(arg, "%s"%s, &value))',
                         sig, check)
                    line(out, indent + 2, '{')
                    line(out, indent + 3, 'INT_CALL(self->object.%s(value));',
                         methodName)
                    line(out, indent + 3, 'return 0;')
                    line(out, indent + 2, '}')
                    line(out, indent + 1, '}')
                line(out, indent + 1, 'PyErr_SetArgsError((PyObject *) self, "%s", arg);',
                     fieldName)
                line(out, indent + 1, 'return -1;')
                line(out, indent, '}')

    if mappingMethod:
        method, cardinality = mappingMethod
        if cardinality > 1:
            methodName = method.getName()
            getName = 't_%s_%s_map_' %(names[-1], methodName)
            line(out)
            line(out, indent, 'static PyObject *%s(t_%s *self, PyObject *arg)',
                 getName, names[-1])
            line(out, indent, '{')
            call(out, indent + 1, cls, False, method, names, 1, isExtension,
                 generics)
            line(out)
            line(out, indent + 1, 'PyErr_SetArgsError((PyObject *) self, "%s", arg);',
                 methodName)
            line(out, indent + 1, 'return NULL;')
            line(out, indent, '}')

    if sequenceLenMethod:
        method, cardinality = sequenceLenMethod
        methodName = method.getName()
        lenName = 't_%s_%s_seq_' %(names[-1], methodName)
        line(out)
        line(out, indent, 'static int %s(t_%s *self)', lenName, names[-1])
        line(out, indent, '{')
        line(out, indent + 1, '%s len;',
             typename(method.getReturnType(), cls, False))
        line(out, indent + 1, 'INT_CALL(len = self->object.%s());', methodName)
        line(out, indent + 1, 'return (int) len;')
        line(out, indent, '}')

    if sequenceGetMethod:
        method, cardinality = sequenceGetMethod
        methodName = method.getName()
        returnType = method.getReturnType()
        getName = 't_%s_%s_seq_' %(names[-1], methodName)
        line(out)
        line(out, indent, 'static PyObject *%s(t_%s *self, int n)', getName, names[-1])
        line(out, indent, '{')
        line(out, indent + 1, '%s result%s;',
             typename(returnType, cls, False),
             not returnType.isPrimitive() and '((jobject) NULL)' or '')
        line(out, indent + 1, 'OBJ_CALL(result = self->object.%s((%s) n));',
             methodName, typename(method.getParameterTypes()[0], cls, False))
        if generics:
            line(out, indent + 1, returnValue(cls, returnType, 'result',
                                              method.getGenericReturnType()))
        else:
            line(out, indent + 1, returnValue(cls, returnType, 'result'))
        line(out, indent, '}')

    while indent:
        indent -= 1
        line(out, indent, '}')


def package(out, allInOne, cppdir, namespace, names, use_full_names):

    if not allInOne:
        out = open(os.path.join(os.path.join(cppdir, *names),
                                '__init__.cpp'), 'w')

    if allInOne and not names or not allInOne:
        line(out, 0, '#include <jni.h>')
        line(out, 0, '#include <Python.h>')
        line(out, 0, '#include "JCCEnv.h"')
        line(out, 0, '#include "functions.h"')

    if not names or not allInOne:
        line(out)
        line(out, 0, 'PyObject *initVM(PyObject *module, PyObject *args, PyObject *kwds);')
        if use_full_names:
            line(out, 0, 'PyObject *getJavaModule(PyObject *module, const char *parent, const char *name);')

    packages = []
    types = []

    namespaces = list(namespace.items())
    namespaces.sort(key=lambda x: x[0])
    for name, entries in namespaces:
        if entries is True:
            if names:
                line(out, 0, '#include "%s/%s.h"', '/'.join(names), name)
            else:
                line(out, 0, '#include "%s.h"', name)
            types.append(name)
        else:
            packages.append((name, entries))

    indent = 0
    if names:
        line(out)
        for name in names:
            line(out, indent, 'namespace %s {', cppname(name))
            indent += 1

    line(out);
    for name, entries in packages:
        line(out, indent, 'namespace %s {', cppname(name))
        line(out, indent + 1, 'void __install__(PyObject *module);')
        line(out, indent + 1, 'void __initialize__(PyObject *module);')
        line(out, indent, '}')

    line(out)
    line(out, indent, 'void __install__(PyObject *module)')
    line(out, indent, '{')

    if use_full_names and names:
        parent_name = '.'.join(names[:-1])
        line(out, indent + 1, 'module = getJavaModule(module, "%s", "%s");',
             parent_name, names[-1])
        line(out)

    for name in types:
        line(out, indent + 1, 't_%s::install(module);', name)
    for name, entries in packages:
        line(out, indent + 1, '%s::__install__(module);', cppname(name))
    line(out, indent, '}')

    line(out)
    if not names:
        line(out, indent, 'PyObject *__initialize__(PyObject *module, PyObject *args, PyObject *kwds)')
        line(out, indent, '{')
        line(out, indent + 1, 'PyObject *env = initVM(module, args, kwds);')
        line(out)
        line(out, indent + 1, 'if (env == NULL)')
        line(out, indent + 2, 'return NULL;')
        line(out)
        line(out, indent + 1, 'try {');
        indent += 1
    else:
        line(out, indent, 'void __initialize__(PyObject *module)')
        line(out, indent, '{')

    if use_full_names and names:
        line(out, indent + 1, 'module = getJavaModule(module, "%s", "%s");',
             parent_name, names[-1])
        line(out)

    for name in types:
        line(out, indent + 1, 't_%s::initialize(module);', name)
    for name, entries in packages:
        line(out, indent + 1, '%s::__initialize__(module);', cppname(name))
    if not names:
        line(out, indent + 1, 'return env;')
        indent -= 1
        line(out, indent + 1, '} catch (int e) {')
        line(out, indent + 2, 'switch(e) {')
        line(out, indent + 2, '  case _EXC_JAVA:')
        line(out, indent + 3, 'return PyErr_SetJavaError();')
        line(out, indent + 2, '  default:')
        line(out, indent + 3, 'throw;')
        line(out, indent + 2, '}')
        line(out, indent + 1, '}')

    line(out, indent, '}')

    while indent:
        indent -= 1
        line(out, indent, '}')

    if not allInOne:
        out.close()
    else:
        line(out)

    for name, entries in packages:
        package(out, allInOne, cppdir, entries, names + (name,), use_full_names)


def module(out, allInOne, classes, imports, cppdir, moduleName,
           shared, generics, use_full_names):

    extname = '_%s' %(moduleName)
    line(out, 0, '#include <Python.h>')
    line(out, 0, '#include "macros.h"')
    line(out, 0, '#include "jccfuncs.h"')

    if allInOne:
        out_init = open(os.path.join(cppdir, '__init__.cpp'), 'w')
    namespaces = {}
    for cls in classes:
        for importset in imports.values():
            if cls in importset:
                break
        else:
            namespace = namespaces
            classNames = cls.getName().split('.')
            for className in classNames[:-1]:
                namespace = namespace.setdefault(className, {})
            namespace[classNames[-1]] = True
    if allInOne:
        package(out_init, True, cppdir, namespaces, (), use_full_names)
        out_init.close()
    else:
        package(None, False, cppdir, namespaces, (), use_full_names)

    line(out)
    line(out, 0, 'PyObject *initJCC(PyObject *module);')
    line(out, 0, 'void __install__(PyObject *module);')
    line(out, 0, 'extern PyTypeObject *PY_TYPE(JObject), *PY_TYPE(ConstVariableDescriptor), *PY_TYPE(FinalizerClass), *PY_TYPE(FinalizerProxy);')
    line(out, 0, 'extern PyType_Def PY_TYPE_DEF(JObject);')
    line(out, 0, 'extern void _install_jarray(PyObject *);')
    line(out)
    line(out, 0, 'extern "C" {')

    line(out, 1, 'static struct PyModuleDef %s_def = {', extname)
    line(out, 2, 'PyModuleDef_HEAD_INIT,')
    line(out, 2, '"%s",', extname);
    line(out, 2, '"%s module",', extname);
    line(out, 2, '0,')
    line(out, 2, 'jcc_funcs,');
    line(out, 1, '};')
    line(out)
    line(out, 1, 'PyObject *PyInit_%s(void)', extname)
    line(out, 1, '{')
    line(out, 2, 'PyObject *module = PyModule_Create(&%s_def);', extname);
    line(out)
    line(out, 2, 'initJCC(module);')
    line(out)
    line(out, 2, 'INSTALL_STATIC_TYPE(JObject, module);')
    line(out, 2, 'PY_TYPE_DEF(JObject).type = PY_TYPE(JObject);')
    line(out, 2, 'INSTALL_STATIC_TYPE(ConstVariableDescriptor, module);')
    line(out, 2, 'INSTALL_STATIC_TYPE(FinalizerClass, module);')
    line(out, 2, 'INSTALL_STATIC_TYPE(FinalizerProxy, module);')
    line(out, 2, '__install__(module);')
    line(out, 2, '_install_jarray(module);')
    line(out)
    line(out, 2, 'return module;')
    line(out, 1, '}')
    line(out, 0, '}')


def compile(env, jccPath, output, moduleName,
            generate, install, dist, debug, jars,
            version, prefix, root, install_dir, home_dir, use_distutils,
            shared, compiler, modules, wheel, wininst, find_jvm_dll, arch,
            generics, resources, imports, use_full_names, egg_info,
            builtin_vmargs, extra_setup_args):
    try:
        if use_distutils:
            raise ImportError
        from setuptools import setup, Extension
        with_setuptools = True
        if shared and not SHARED:
            raise NotImplementedError("JCC was not built with --shared mode support, see JCC's INSTALL file for more information")
    except ImportError:
        if shared:
            raise ImportError('setuptools is required when using --shared')
        from distutils.core import setup, Extension
        with_setuptools = False

    extname = '_%s' %(moduleName)
    if not os.path.isdir(os.path.join(output, extname)):
        raise FileNotFoundError(
            "%s directory not found, module %s needs to be compiled first" %(
                os.path.join(output, extname), extname))
    
    modulePath = os.path.join(output, moduleName)
    if not os.path.isdir(modulePath):
        os.makedirs(modulePath)

    out = open(os.path.join(modulePath, '__init__.py'), 'w')
    line(out)
    line(out, 0, "import os")
    if find_jvm_dll:
        line(out, 0, "import sys")
        line(out, 0, "if sys.platform == 'win32':")
        line(out, 1, "from .windows import add_jvm_dll_directory_to_path")
        line(out, 1, "add_jvm_dll_directory_to_path('%s')" %(find_jvm_dll))
        modules.append(os.path.join(
            os.path.dirname(os.path.realpath(__file__)), 'windows.py'))
    line(out, 0, "from . import %s", extname)
    line(out)
    line(out, 0, '__module_dir__ = os.path.abspath(os.path.dirname(__file__))')

    package_data = []
    for jar in jars:
        shutil.copy2(jar, modulePath)
        package_data.append(os.path.basename(jar))
    if resources:
        def copytree(src, dst):
            _dst = os.path.join(modulePath, dst)
            if not os.path.exists(_dst):
                os.mkdir(_dst)
            for name in os.listdir(src):
                if name.startswith('.'):
                    continue
                _src = os.path.join(src, name)
                if os.path.islink(_src):
                    continue
                _dst = os.path.join(dst, name)
                if os.path.isdir(_src):
                    copytree(_src, _dst)
                else:
                    shutil.copy2(_src, os.path.join(modulePath, _dst))
                    package_data.append(_dst)
        for resource in resources:
            copytree(resource, os.path.split(resource)[-1])

    packages = [moduleName]
    package = [moduleName]
    if modules:
        for module in modules:
            if os.path.isdir(module):
                def copytree(src, dst, is_package):
                    if is_package:
                        packages.append('.'.join(package))
                    if not os.path.exists(dst):
                        os.mkdir(dst)
                    for name in os.listdir(src):
                        if name.startswith('.'):
                            continue
                        _src = os.path.join(src, name)
                        if os.path.islink(_src):
                            continue
                        _dst = os.path.join(dst, name)
                        if os.path.isdir(_src):
                            package.append(os.path.basename(_src))
                            copytree(_src, _dst, os.path.exists(os.path.join(_src, '__init__.py')))
                            package.pop()
                        elif not is_package or name.endswith('.py'):
                            shutil.copy2(_src, _dst)
                dst = modulePath
                if os.path.exists(os.path.join(module, '__init__.py')):
                    dst = os.path.join(modulePath, os.path.basename(module))
                    package.append(os.path.basename(module))
                    copytree(module, dst, True)
                    package.pop()
                else:
                    copytree(module, dst, False)
            else:
                shutil.copy2(module.rpartition('.')[0] + '.py', modulePath)

    line(out)
    line(out, 0, 'class JavaError(Exception):')
    line(out, 1, 'def getJavaException(self):')
    line(out, 2, 'return self.args[0]')
    line(out, 1, 'def __str__(self):')
    line(out, 2, 'writer = StringWriter()')
    line(out, 2, 'self.getJavaException().printStackTrace(PrintWriter(writer))')
    line(out, 2, 'return "\\n".join((str(super(JavaError, self)), "    Java stacktrace:", str(writer)))')
    line(out)
    line(out, 0, 'class InvalidArgsError(Exception):')
    line(out, 1, 'pass')
    line(out)
    line(out, 0, '%s._set_exception_types(JavaError, InvalidArgsError)',
         extname)

    if version:
        line(out)
        line(out, 0, 'VERSION = "%s"', version)

    line(out, 0, 'CLASSPATH = [%s]' %(', '.join(['os.path.join(__module_dir__, "%s")' %(os.path.basename(jar)) for jar in jars])))
    line(out, 0, 'CLASSPATH = os.pathsep.join(CLASSPATH)')
    line(out, 0, '%s.CLASSPATH = CLASSPATH', extname)
    line(out, 0, '%s._set_function_self(%s.initVM, %s)',
         extname, extname, extname)

    line(out)
    for import_ in imports:
        line(out, 0, 'from %s._%s import *', import_.__name__, import_.__name__)
    line(out, 0, 'from .%s import *', extname)
    if use_full_names:
        line(out, 0, 'from java.io import PrintWriter, StringWriter')

    line(out)
    if builtin_vmargs != []:
        line(out, 0, 'builtin_vmargs = [%s]' % ', '.join(['r"%s"' % arg for arg in builtin_vmargs]))
        line(out, 0, 'def initVM(**kwargs):')
        line(out, 1, 'vmargs=kwargs.pop("vmargs", [])')
        line(out, 1, 'return %s.initVM(vmargs=builtin_vmargs+vmargs, **kwargs)', extname)
    out.close()

    includes = [os.path.join(output, extname),
                os.path.join(jccPath, 'sources')]
    for import_ in imports:
        includes.append(os.path.join(import_.__module_dir__, 'include'))

    sources = ['JObject.cpp', 'JArray.cpp', 'functions.cpp', 'types.cpp']
    if not shared:
        sources.append('jcc.cpp')
        sources.append('JCCEnv.cpp')
    for source in sources:
        shutil.copy2(os.path.join(jccPath, 'sources', source),
                     os.path.join(output, extname))

    if shared:
        def copytree(src, dst):
            _dst = os.path.join(modulePath, dst)
            if not os.path.exists(_dst):
                os.mkdir(_dst)
            for name in os.listdir(src):
                if name.startswith('.'):
                    continue
                _src = os.path.join(src, name)
                if os.path.islink(_src):
                    continue
                _dst = os.path.join(dst, name)
                if os.path.isdir(_src):
                    copytree(_src, _dst)
                elif name.endswith('.h'):
                    shutil.copy2(_src, os.path.join(modulePath, _dst))
                    package_data.append(_dst)
        copytree(os.path.join(output, extname), 'include')

    sources = []
    for path, dirs, names in os.walk(os.path.join(output, extname)):
        for name in names:
            if name.endswith('.cpp'):
                sources.append(os.path.join(path, name))

    if egg_info:
        script_args = ['egg_info']
    else:
        script_args = ['build_ext']

    includes[0:0] = INCLUDES
    compile_args = CFLAGS
    link_args = LFLAGS

    defines=[('PYTHON', None),
             ('JCC_VER', '"%s"' %(JCC_VER))]
    if shared:
        defines.append(('_jcc_shared', None))
    if generics:
        defines.append(('_java_generics', None))

    if compiler:
        script_args.append('--compiler=%s' %(compiler))

    if debug:
        script_args.append('--debug')
        compile_args += DEBUG_CFLAGS
    elif sys.platform == 'win32':
        pass
    elif sys.platform == 'sunos5':
        link_args.append('-Wl,-s')
    else:
        link_args.append('-Wl,-S')

    if install:
        script_args.append('install')
    if prefix:
        script_args.append('--prefix=%s' % prefix)
    if root:
        script_args.append('--root=%s' % root)
    if install_dir:
        script_args.append('--install-lib=%s' % install_dir)
    if home_dir:
        script_args.append('--home=%s' % home_dir)

    if dist:
        if wininst:
            script_args.append('bdist_wininst')
        elif with_setuptools:
            script_args.append('bdist_wheel' if wheel else 'bdist_egg')
        else:
            script_args.append('bdist')

    args = {
        'extra_compile_args': compile_args,
        'extra_link_args': link_args,
        'include_dirs': includes,
        'sources': sources,
        'define_macros': defines
    }

    if shared:
        shlibdir = os.path.dirname(os.path.dirname(_jcc3.__file__))
        if sys.platform == 'darwin':   # distutils no good with -R
            machine = platform.machine()
            if machine.startswith('iPod') or machine.startswith('iPhone'):
                args['extra_link_args'] += ['-L' + shlibdir]
            else:
                args['extra_link_args'] += ['-Wl,-rpath', shlibdir]
            args['library_dirs'] = [shlibdir]
            args['libraries'] = ['jcc3']
        elif sys.platform == 'linux':  # distutils no good with -R
            args['extra_link_args'] += ['-Wl,-rpath', shlibdir]
            args['library_dirs'] = [shlibdir]
            args['libraries'] = ['jcc3']
            args['extra_link_args'] += [
                getattr(import_, "_%s" %(import_.__name__)).__file__
                for import_ in imports
            ]
        elif sys.platform == 'win32':
            _d = debug and '_d' or ''
            libdir = os.path.join(modulePath, 'lib')
            if not os.path.exists(libdir):
                os.mkdir(libdir)
            extlib = os.path.join('lib', "%s%s.lib" %(extname, _d))
            package_data.append(extlib)
            args['extra_link_args'] += [
                os.path.join(shlibdir, 'jcc3', 'jcc3%s.lib' %(_d)),
                ' '.join(IMPLIB_LFLAGS) %(os.path.join(modulePath, extlib))
            ]
            args['libraries'] = [
                os.path.join(import_.__module_dir__, 'lib',
                             '_%s%s' %(import_.__name__, _d))
                for import_ in imports
            ]
            args['define_macros'] += [
                ("_dll_%s" %(import_.__name__), '__declspec(dllimport)')
                for import_ in imports
            ] + [("_dll_%s" %(moduleName), '__declspec(dllexport)')]
        else:
            raise NotImplementedError("shared mode on %s" %(sys.platform))

    if arch and sys.platform == 'darwin':
        from distutils import sysconfig

        config_vars = sysconfig.get_config_vars()
        cflags = config_vars['CFLAGS'].split(' ')
        count = len(cflags)
        i = 0
        while i < count - 1:
            if cflags[i] == '-arch' and cflags[i + 1] not in arch:
                del cflags[i:i+2]
                count -= 2
            else:
                i += 1
        config_vars['CFLAGS'] = ' '.join(cflags)

    if generate:
        out = open(os.path.join(".", "setup.py"), "w")
        line(out, 0, 'from setuptools import setup, Extension')
        line(out, 0, 'setup(')
        line(out, 1, 'name="%s",', moduleName)
        line(out, 1, 'packages=%s,', packages)
        line(out, 1, 'package_dir={"%s": r"%s"},', moduleName, modulePath)
        line(out, 1, 'package_data={"%s": [', moduleName)
        for pd in package_data:
            line(out, 2, 'r"%s",', pd)
        line(out, 1, ']},')
        line(out, 1, 'ext_modules=[Extension("%s.%s",', moduleName, extname)
        for arg, value in args.items():
            if arg in ('define_macros', 'libraries'):
                line(out, 2, '%s=%s,', arg, value)
            else:
                line(out, 2, '%s=[', arg)
                for v in value:
                    line(out, 3, 'r"%s",', v)
                line(out, 2, '],')
        line(out, 1, ")],")
        if version:
            line(out, 1, 'version="%s",', version)
        line(out, 1, 'zip_safe=False,')
        line(out, 0, ')')
        out.close()
    else:
        print("extension args = %s" % args)
        extensions = [Extension('.'.join([moduleName, extname]), **args)]
        script_args.extend(extra_setup_args)

        setup_args = {
            'name': moduleName,
            'packages': packages,
            'package_dir': {moduleName: modulePath},
            'package_data': {moduleName: package_data},
            'ext_modules': extensions,
            'script_args': script_args
        }
        if version:
            setup_args['version'] = version
        if with_setuptools:
            setup_args['zip_safe'] = False

            print("setup args = %s" % setup_args)
            setup(**setup_args)
