# -*- Mode: Python; py-indent-offset: 4 -*-
import string
import keyword
import struct

py_ssize_t_clean = False

class ArgTypeError(Exception):
    pass

class ArgTypeNotFoundError(ArgTypeError):
    pass

class ArgTypeConfigurationError(ArgTypeError):
    pass


class VarList:
    """Nicely format a C variable list"""
    def __init__(self):
        self.vars = {}
    def add(self, ctype, name):
        if self.vars.has_key(ctype):
            self.vars[ctype] = self.vars[ctype] + (name,)
        else:
            self.vars[ctype] = (name,)
    def __str__(self):
        ret = []
        for type in self.vars.keys():
            ret.append('    ')
            ret.append(type)
            ret.append(' ')
            ret.append(string.join(self.vars[type], ', '))
            ret.append(';\n')
        if ret:
            ret.append('\n')
            return string.join(ret, '')
        return ''

class WrapperInfo:
    """A class that holds information about variable defs, code
    snippets, etcd for use in writing out the function/method
    wrapper."""
    def __init__(self):
        self.varlist = VarList()
        self.parsestr = ''
        self.parselist = ['', 'kwlist']
        self.codebefore = []
        self.codeafter = []
        self.arglist = []
        self.kwlist = []
    def get_parselist(self):
        return string.join(self.parselist, ', ')
    def get_codebefore(self):
        return string.join(self.codebefore, '')
    def get_codeafter(self):
        return string.join(self.codeafter, '')
    def get_arglist(self):
        return string.join(self.arglist, ', ')
    def get_varlist(self):
        return str(self.varlist)
    def get_kwlist(self):
        ret = '    static char *kwlist[] = { %s };\n' % \
              string.join(self.kwlist + [ 'NULL' ], ', ')
        if not self.get_varlist():
            ret = ret + '\n'
        return ret

    def add_parselist(self, codes, parseargs, keywords):
        self.parsestr = self.parsestr + codes
        for arg in parseargs:
            self.parselist.append(arg)
        for kw in keywords:
            if keyword.iskeyword(kw):
                kw = kw + '_'
            self.kwlist.append('"%s"' % kw)

class ArgType:
    def write_param(self, ptype, pname, pdflt, pnull, info):
        """Add code to the WrapperInfo instance to handle
        parameter."""
        raise RuntimeError, "write_param not implemented for %s" % \
              self.__class__.__name__
    def write_return(self, ptype, ownsreturn, info):
        """Adds a variable named ret of the return type to
        info.varlist, and add any required code to info.codeafter to
        convert the return value to a python object."""
        raise RuntimeError, "write_return not implemented for %s" % \
              self.__class__.__name__

class NoneArg(ArgType):
    def write_return(self, ptype, ownsreturn, info):
        info.codeafter.append('    Py_INCREF(Py_None);\n' +
                              '    return Py_None;')

class StringArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt != None:
            if pdflt != 'NULL': pdflt = '"' + pdflt + '"'
            info.varlist.add('char', '*' + pname + ' = ' + pdflt)
        else:
            info.varlist.add('char', '*' + pname)
        info.arglist.append(pname)
        if pnull:
            info.add_parselist('z', ['&' + pname], [pname])
        else:
            info.add_parselist('s', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        if ownsreturn:
            # have to free result ...
            info.varlist.add('gchar', '*ret')
            info.codeafter.append('    if (ret) {\n' +
                                  '        PyObject *py_ret = PyString_FromString(ret);\n' +
                                  '        g_free(ret);\n' +
                                  '        return py_ret;\n' +
                                  '    }\n' +
                                  '    Py_INCREF(Py_None);\n' +
                                  '    return Py_None;')
        else:
            info.varlist.add('const gchar', '*ret')
            info.codeafter.append('    if (ret)\n' +
                                  '        return PyString_FromString(ret);\n'+
                                  '    Py_INCREF(Py_None);\n' +
                                  '    return Py_None;')

class UCharArg(ArgType):
    # allows strings with embedded NULLs.
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('guchar', '*' + pname + ' = "' + pdflt + '"')
        else:
            info.varlist.add('guchar', '*' + pname)
        if py_ssize_t_clean:
            info.varlist.add('Py_ssize_t', pname + '_len')
        else:
            info.varlist.add('int', pname + '_len')
        info.arglist.append(pname)
        if pnull:
            info.add_parselist('z#', ['&' + pname, '&' + pname + '_len'],
                               [pname])
        else:
            info.add_parselist('s#', ['&' + pname, '&' + pname + '_len'],
                               [pname])

class CharArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('char', pname + " = '" + pdflt + "'")
        else:
            info.varlist.add('char', pname)
        info.arglist.append(pname)
        info.add_parselist('c', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('gchar', 'ret')
        info.codeafter.append('    return PyString_FromStringAndSize(&ret, 1);')
class GUniCharArg(ArgType):
    ret_tmpl = ('#if !defined(Py_UNICODE_SIZE) || Py_UNICODE_SIZE == 2\n'
                '    if (ret > 0xffff) {\n'
                '        PyErr_SetString(PyExc_RuntimeError, "returned character can not be represented in 16-bit unicode");\n'
                '        return NULL;\n'
                '    }\n'
                '#endif\n'
                '    py_ret = (Py_UNICODE)ret;\n'
                '    return PyUnicode_FromUnicode(&py_ret, 1);\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('gunichar', pname + " = '" + pdflt + "'")
        else:
            info.varlist.add('gunichar', pname)
        info.arglist.append(pname)
        info.add_parselist('O&', ['pyg_pyobj_to_unichar_conv', '&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('gunichar', 'ret')
        info.varlist.add('Py_UNICODE', 'py_ret')
        info.codeafter.append(self.ret_tmpl)


class IntArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('int', pname + ' = ' + pdflt)
        else:
            info.varlist.add('int', pname)
        info.arglist.append(pname)
        info.add_parselist('i', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('int', 'ret')
        info.codeafter.append('    return PyInt_FromLong(ret);')

class UIntArg(ArgType):
    dflt = ('    if (py_%(name)s) {\n'
            '        if (PyLong_Check(py_%(name)s))\n'
            '            %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n'
            '        else if (PyInt_Check(py_%(name)s))\n'
            '            %(name)s = PyInt_AsLong(py_%(name)s);\n'
            '        else\n'
            '            PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n'
            '        if (PyErr_Occurred())\n'
            '            return NULL;\n'
            '    }\n')
    before = ('    if (PyLong_Check(py_%(name)s))\n'
              '        %(name)s = PyLong_AsUnsignedLong(py_%(name)s);\n'
              '    else if (PyInt_Check(py_%(name)s))\n'
              '        %(name)s = PyInt_AsLong(py_%(name)s);\n'
              '    else\n'
              '        PyErr_SetString(PyExc_TypeError, "Parameter \'%(name)s\' must be an int or a long");\n'
              '    if (PyErr_Occurred())\n'
              '        return NULL;\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if not pdflt:
            pdflt = '0';

        info.varlist.add(ptype, pname + ' = ' + pdflt)
        info.codebefore.append(self.dflt % {'name':pname})
        info.varlist.add('PyObject', "*py_" + pname + ' = NULL')
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype, 'ret')
        info.codeafter.append('    return PyLong_FromUnsignedLong(ret);')

class SizeArg(ArgType):

    if struct.calcsize('P') <= struct.calcsize('l'):
        llp64 = True
    else:
        llp64 = False

    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add(ptype, pname + ' = ' + pdflt)
        else:
            info.varlist.add(ptype, pname)
        info.arglist.append(pname)
        if self.llp64:
            info.add_parselist('k', ['&' + pname], [pname])
        else:
            info.add_parselist('K', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype, 'ret')
        if self.llp64:
            info.codeafter.append('    return PyLong_FromUnsignedLongLong(ret);\n')
        else:
            info.codeafter.append('    return PyLong_FromUnsignedLong(ret);\n')

class SSizeArg(ArgType):

    if struct.calcsize('P') <= struct.calcsize('l'):
        llp64 = True
    else:
        llp64 = False

    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add(ptype, pname + ' = ' + pdflt)
        else:
            info.varlist.add(ptype, pname)
        info.arglist.append(pname)
        if self.llp64:
            info.add_parselist('l', ['&' + pname], [pname])
        else:
            info.add_parselist('L', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype, 'ret')
        if self.llp64:
            info.codeafter.append('    return PyLong_FromLongLong(ret);\n')
        else:
            info.codeafter.append('    return PyLong_FromLong(ret);\n')

class LongArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add(ptype, pname + ' = ' + pdflt)
        else:
            info.varlist.add(ptype, pname)
        info.arglist.append(pname)
        info.add_parselist('l', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype, 'ret')
        info.codeafter.append('    return PyInt_FromLong(ret);\n')

class BoolArg(IntArg):
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('int', 'ret')
        info.codeafter.append('    return PyBool_FromLong(ret);\n')

class TimeTArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('time_t', pname + ' = ' + pdflt)
        else:
            info.varlist.add('time_t', pname)
        info.arglist.append(pname)
        info.add_parselist('i', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('time_t', 'ret')
        info.codeafter.append('    return PyInt_FromLong(ret);')

class ULongArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('unsigned long', pname + ' = ' + pdflt)
        else:
            info.varlist.add('unsigned long', pname)
        info.arglist.append(pname)
        info.add_parselist('k', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype, 'ret')
        info.codeafter.append('    return PyLong_FromUnsignedLong(ret);\n')

class UInt32Arg(ULongArg):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        ULongArg.write_param(self, ptype, pname, pdflt, pnull, info)
        ## if sizeof(unsigned long) > sizeof(unsigned int), we need to
        ## check the value is within guint32 range
        if struct.calcsize('L') > struct.calcsize('I'):
            info.codebefore.append((
                '    if (%(pname)s > G_MAXUINT32) {\n'
                '        PyErr_SetString(PyExc_ValueError,\n'
                '                        "Value out of range in conversion of"\n'
                '                        " %(pname)s parameter to unsigned 32 bit integer");\n'
                '        return NULL;\n'
                '    }\n') % vars())

class Int64Arg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('gint64', pname + ' = ' + pdflt)
        else:
            info.varlist.add('gint64', pname)
        info.arglist.append(pname)
        info.add_parselist('L', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('gint64', 'ret')
        info.codeafter.append('    return PyLong_FromLongLong(ret);')

class UInt64Arg(ArgType):
    dflt = '    if (py_%(name)s)\n' \
           '        %(name)s = PyLong_AsUnsignedLongLong(py_%(name)s);\n'
    before = '    %(name)s = PyLong_AsUnsignedLongLong(py_%(name)s);\n'
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('guint64', pname + ' = ' + pdflt)
            info.codebefore.append(self.dflt % {'name':pname})
        else:
            info.varlist.add('guint64', pname)
            info.codebefore.append(self.before % {'name':pname})
        info.varlist.add('PyObject', "*py_" + pname + ' = NULL')
        info.arglist.append(pname)
        info.add_parselist('O!', ['&PyLong_Type', '&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('guint64', 'ret')
        info.codeafter.append('    return PyLong_FromUnsignedLongLong(ret);')


class DoubleArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('double', pname + ' = ' + pdflt)
        else:
            info.varlist.add('double', pname)
        info.arglist.append(pname)
        info.add_parselist('d', ['&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('double', 'ret')
        info.codeafter.append('    return PyFloat_FromDouble(ret);')

class FileArg(ArgType):
    nulldflt = ('    if (py_%(name)s == Py_None)\n'
                '        %(name)s = NULL;\n'
                '    else if (py_%(name)s && PyFile_Check(py_%(name)s)\n'
                '        %s = PyFile_AsFile(py_%(name)s);\n'
                '    else if (py_%(name)s) {\n'
                '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n'
                '        return NULL;\n'
                '    }')
    null = ('    if (py_%(name)s && PyFile_Check(py_%(name)s)\n'
            '        %(name)s = PyFile_AsFile(py_%(name)s);\n'
            '    else if (py_%(name)s != Py_None) {\n'
            '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a file object or None");\n'
            '        return NULL;\n'
            '    }\n')
    dflt = ('    if (py_%(name)s)\n'
            '        %(name)s = PyFile_AsFile(py_%(name)s);\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            if pdflt:
                info.varlist.add('FILE', '*' + pname + ' = ' + pdflt)
                info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
                info.codebefore.append(self.nulldflt % {'name':pname})
            else:
                info.varlist.add('FILE', '*' + pname + ' = NULL')
                info.varlist.add('PyObject', '*py_' + pname)
                info.codebefore.append(self.null & {'name':pname})
            info.arglist.appned(pname)
            info.add_parselist('O', ['&py_' + pname], [pname])
        else:
            if pdflt:
                info.varlist.add('FILE', '*' + pname + ' = ' + pdflt)
                info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
                info.codebefore.append(self.dflt % {'name':pname})
                info.arglist.append(pname)
            else:
                info.varlist.add('PyObject', '*' + pname)
                info.arglist.append('PyFile_AsFile(' + pname + ')')
            info.add_parselist('O!', ['&PyFile_Type', '&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('FILE', '*ret')
        info.codeafter.append('    if (ret)\n' +
                              '        return PyFile_FromFile(ret, "", "", fclose);\n' +
                              '    Py_INCREF(Py_None);\n' +
                              '    return Py_None;')

class EnumArg(ArgType):
    enum = ('    if (pyg_enum_get_value(%(typecode)s, py_%(name)s, (gpointer)&%(name)s))\n'
            '        return NULL;\n')
    def __init__(self, enumname, typecode):
        self.enumname = enumname
        self.typecode = typecode
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add(self.enumname, pname + ' = ' + pdflt)
        else:
            info.varlist.add(self.enumname, pname)
        info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
        info.codebefore.append(self.enum % { 'typecode': self.typecode,
                                             'name': pname})
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname]);
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('gint', 'ret')
        info.codeafter.append('    return pyg_enum_from_gtype(%s, ret);' % self.typecode)

class FlagsArg(ArgType):
    flag = ('    if (%(default)spyg_flags_get_value(%(typecode)s, py_%(name)s, (gpointer)&%(name)s))\n'
            '        return NULL;\n')
    def __init__(self, flagname, typecode):
        self.flagname = flagname
        self.typecode = typecode
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add(self.flagname, pname + ' = ' + pdflt)
            default = "py_%s && " % (pname,)
        else:
            info.varlist.add(self.flagname, pname)
            default = ""
        info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
        info.codebefore.append(self.flag % {'default':default,
                                            'typecode':self.typecode,
                                            'name':pname})
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('guint', 'ret')
        info.codeafter.append('    return pyg_flags_from_gtype(%s, ret);' % self.typecode)

class ObjectArg(ArgType):
    # should change these checks to more typesafe versions that check
    # a little further down in the class heirachy.
    nulldflt = ('    if ((PyObject *)py_%(name)s == Py_None)\n'
                '        %(name)s = NULL;\n'
                '    else if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n'
                '        %(name)s = %(cast)s(py_%(name)s->obj);\n'
                '    else if (py_%(name)s) {\n'
                '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
                '        return NULL;\n'
                '    }\n')
    null = ('    if (py_%(name)s && pygobject_check(py_%(name)s, &Py%(type)s_Type))\n'
            '        %(name)s = %(cast)s(py_%(name)s->obj);\n'
            '    else if ((PyObject *)py_%(name)s != Py_None) {\n'
            '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
            '        return NULL;\n'
            '    }\n')
    dflt = '    if (py_%(name)s)\n' \
           '        %(name)s = %(cast)s(py_%(name)s->obj);\n'
    def __init__(self, objname, parent, typecode):
        self.objname = objname
        self.cast = string.replace(typecode, '_TYPE_', '_', 1)
        self.parent = parent
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            if pdflt:
                info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
                info.varlist.add('PyGObject', '*py_' + pname + ' = NULL')
                info.codebefore.append(self.nulldflt % {'name':pname,
                                                        'cast':self.cast,
                                                        'type':self.objname})
            else:
                info.varlist.add(self.objname, '*' + pname + ' = NULL')
                info.varlist.add('PyGObject', '*py_' + pname)
                info.codebefore.append(self.null % {'name':pname,
                                                    'cast':self.cast,
                                                    'type':self.objname})
            if ptype.endswith('*'):
                typename = ptype[:-1]
                try:
                    const, typename = typename.split('const-')
                except ValueError:
                    const = ''
                if typename != ptype:
                    info.arglist.append('(%s *) %s' % (ptype[:-1], pname))
                else:
                    info.arglist.append(pname)

            info.add_parselist('O', ['&py_' + pname], [pname])
        else:
            if pdflt:
                info.varlist.add(self.objname, '*' + pname + ' = ' + pdflt)
                info.varlist.add('PyGObject', '*py_' + pname + ' = NULL')
                info.codebefore.append(self.dflt % {'name':pname,
                                                    'cast':self.cast})
                info.arglist.append(pname)
                info.add_parselist('O!', ['&Py%s_Type' % self.objname,
                                         '&py_' + pname], [pname])
            else:
                info.varlist.add('PyGObject', '*' + pname)
                info.arglist.append('%s(%s->obj)' % (self.cast, pname))
                info.add_parselist('O!', ['&Py%s_Type' % self.objname,
                                          '&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        if ptype.endswith('*'):
            typename = ptype[:-1]
            try:
                const, typename = typename.split('const-')
            except ValueError:
                const = ''
        info.varlist.add(typename, '*ret')
        if ownsreturn:
            info.varlist.add('PyObject', '*py_ret')
            info.codeafter.append('    py_ret = pygobject_new((GObject *)ret);\n'
                                  '    if (ret != NULL)\n'
                                  '        g_object_unref(ret);\n'
                                  '    return py_ret;')
        else:
            info.codeafter.append('    /* pygobject_new handles NULL checking */\n' +
                                  '    return pygobject_new((GObject *)ret);')

class BoxedArg(ArgType):
    # haven't done support for default args.  Is it needed?
    check = ('    if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n'
             '        %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n'
             '    else {\n'
             '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n'
             '        return NULL;\n'
             '    }\n')
    null = ('    if (pyg_boxed_check(py_%(name)s, %(typecode)s))\n'
            '        %(name)s = pyg_boxed_get(py_%(name)s, %(typename)s);\n'
            '    else if (py_%(name)s != Py_None) {\n'
            '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n'
            '        return NULL;\n'
            '    }\n')
    def __init__(self, ptype, typecode):
        self.typename = ptype
        self.typecode = typecode
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            info.varlist.add(self.typename, '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
            info.codebefore.append(self.null % {'name':  pname,
                                                'typename': self.typename,
                                                'typecode': self.typecode})
        else:
            info.varlist.add(self.typename, '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname)
            info.codebefore.append(self.check % {'name':  pname,
                                                 'typename': self.typename,
                                                 'typecode': self.typecode})
        if ptype[-1] == '*':
            typename = ptype[:-1]
            if typename[:6] == 'const-': typename = typename[6:]
            if typename != self.typename:
                info.arglist.append('(%s *)%s' % (ptype[:-1], pname))
            else:
                info.arglist.append(pname)
        else:
            info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    ret_tmpl = '    /* pyg_boxed_new handles NULL checking */\n' \
               '    return pyg_boxed_new(%(typecode)s, %(ret)s, %(copy)s, TRUE);'
    def write_return(self, ptype, ownsreturn, info):
        if ptype[-1] == '*':
            decl_type = self.typename
            ret = 'ret'
            if ptype[:6] == 'const-':
                decl_type = 'const ' + self.typename
                ret = '(%s*) ret' % (self.typename,)
            info.varlist.add(decl_type, '*ret')
        else:
            info.varlist.add(self.typename, 'ret')
            ret = '&ret'
            ownsreturn = 0 # of course it can't own a ref to a local var ...
        info.codeafter.append(self.ret_tmpl %
                              { 'typecode': self.typecode,
                                'ret': ret,
                                'copy': ownsreturn and 'FALSE' or 'TRUE'})

class CustomBoxedArg(ArgType):
    # haven't done support for default args.  Is it needed?
    null = ('    if (%(check)s(py_%(name)s))\n'
            '        %(name)s = %(get)s(py_%(name)s);\n'
            '    else if (py_%(name)s != Py_None) {\n'
            '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(type)s or None");\n'
            '        return NULL;\n'
            '    }\n')
    def __init__(self, ptype, pytype, getter, new):
        self.pytype = pytype
        self.getter = getter
        self.checker = 'Py' + ptype + '_Check'
        self.new = new
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            info.varlist.add(ptype[:-1], '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
            info.codebefore.append(self.null % {'name':  pname,
                                                'get':   self.getter,
                                                'check': self.checker,
                                                'type':  ptype[:-1]})
            info.arglist.append(pname)
            info.add_parselist('O', ['&py_' + pname], [pname])
        else:
            info.varlist.add('PyObject', '*' + pname)
            info.arglist.append(self.getter + '(' + pname + ')')
            info.add_parselist('O!', ['&' + self.pytype, '&' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add(ptype[:-1], '*ret')
        info.codeafter.append('    if (ret)\n' +
                              '        return ' + self.new + '(ret);\n' +
                              '    Py_INCREF(Py_None);\n' +
                              '    return Py_None;')

class PointerArg(ArgType):
    # haven't done support for default args.  Is it needed?
    check = ('    if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n'
             '        %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n'
             '    else {\n'
             '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s");\n'
             '        return NULL;\n'
             '    }\n')
    null = ('    if (pyg_pointer_check(py_%(name)s, %(typecode)s))\n'
            '        %(name)s = pyg_pointer_get(py_%(name)s, %(typename)s);\n'
            '    else if (py_%(name)s != Py_None) {\n'
            '        PyErr_SetString(PyExc_TypeError, "%(name)s should be a %(typename)s or None");\n'
            '        return NULL;\n'
            '    }\n')
    def __init__(self, ptype, typecode):
        self.typename = ptype
        self.typecode = typecode
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            info.varlist.add(self.typename, '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
            info.codebefore.append(self.null % {'name':  pname,
                                                'typename': self.typename,
                                                'typecode': self.typecode})
        else:
            info.varlist.add(self.typename, '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname)
            info.codebefore.append(self.check % {'name':  pname,
                                                 'typename': self.typename,
                                                 'typecode': self.typecode})
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        if ptype[-1] == '*':
            info.varlist.add(self.typename, '*ret')
            info.codeafter.append('    /* pyg_pointer_new handles NULL checking */\n' +
                                  '    return pyg_pointer_new(' + self.typecode + ', ret);')
        else:
            info.varlist.add(self.typename, 'ret')
            info.codeafter.append('    /* pyg_pointer_new handles NULL checking */\n' +
                                  '    return pyg_pointer_new(' + self.typecode + ', &ret);')

class AtomArg(IntArg):
    dflt = '    if (py_%(name)s) {\n' \
           '        %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n' \
           '        if (PyErr_Occurred())\n' \
           '            return NULL;\n' \
           '    }\n'
    atom = ('    %(name)s = pygdk_atom_from_pyobject(py_%(name)s);\n'
            '    if (PyErr_Occurred())\n'
            '        return NULL;\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pdflt:
            info.varlist.add('GdkAtom', pname + ' = ' + pdflt)
            info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
            info.codebefore.append(self.dflt % {'name': pname})
        else:
            info.varlist.add('GdkAtom', pname)
            info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
            info.codebefore.append(self.atom % {'name': pname})
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('GdkAtom', 'ret')
        info.varlist.add('PyObject *', 'py_ret')
        info.varlist.add('gchar *', 'name')
        info.codeafter.append('    name = gdk_atom_name(ret);\n'
                              '    py_ret = PyString_FromString(name);\n'
                              '    g_free(name);\n'
                              '    return py_ret;')

class GTypeArg(ArgType):
    gtype = ('    if ((%(name)s = pyg_type_from_object(py_%(name)s)) == 0)\n'
             '        return NULL;\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        info.varlist.add('GType', pname)
        info.varlist.add('PyObject', '*py_' + pname + ' = NULL')
        info.codebefore.append(self.gtype % {'name': pname})
        info.arglist.append(pname)
        info.add_parselist('O', ['&py_' + pname], [pname])
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('GType', 'ret')
        info.codeafter.append('    return pyg_type_wrapper_new(ret);')

# simple GError handler.
class GErrorArg(ArgType):
    handleerror = ('    if (pyg_error_check(&%(name)s))\n'
                   '        return NULL;\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        info.varlist.add('GError', '*' + pname + ' = NULL')
        info.arglist.append('&' + pname)
        info.codeafter.append(self.handleerror % { 'name': pname })

class GtkTreePathArg(ArgType):
    # haven't done support for default args.  Is it needed?
    normal = ('    %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n'
              '    if (!%(name)s) {\n'
              '        PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n'
              '        return NULL;\n'
              '    }\n')
    null = ('    if (py_%(name)s != Py_None) {\n'
            '        %(name)s = pygtk_tree_path_from_pyobject(py_%(name)s);\n'
            '        if (!%(name)s) {\n'
            '            PyErr_SetString(PyExc_TypeError, "could not convert %(name)s to a GtkTreePath");\n'
            '            return NULL;\n'
            '        }\n'
            '    }\n')
    freepath = ('    if (%(name)s)\n'
                '        gtk_tree_path_free(%(name)s);\n')
    def __init__(self):
        pass
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            info.varlist.add('GtkTreePath', '*' + pname + ' = NULL')
            info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
            info.codebefore.append(self.null % {'name':  pname})
            info.arglist.append(pname)
            info.add_parselist('O', ['&py_' + pname], [pname])
        else:
            info.varlist.add('GtkTreePath', '*' + pname)
            info.varlist.add('PyObject', '*py_' + pname)
            info.codebefore.append(self.normal % {'name': pname})
            info.arglist.append(pname)
            info.add_parselist('O', ['&py_' + pname], [pname])
        info.codeafter.append(self.freepath % {'name': pname})
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('GtkTreePath', '*ret')
        if ownsreturn:
            info.codeafter.append('    if (ret) {\n'
                                  '        PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n'
                                  '        gtk_tree_path_free(ret);\n'
                                  '        return py_ret;\n'
                                  '    }\n'
                                  '    Py_INCREF(Py_None);\n'
                                  '    return Py_None;')
        else:
            info.codeafter.append('    if (ret) {\n'
                                  '        PyObject *py_ret = pygtk_tree_path_to_pyobject(ret);\n'
                                  '        return py_ret;\n'
                                  '    }\n'
                                  '    Py_INCREF(Py_None);\n'
                                  '    return Py_None;')

class GdkRectanglePtrArg(ArgType):
    normal = ('    if (!pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s))\n'
              '        return NULL;\n')
    null =   ('    if (py_%(name)s == Py_None)\n'
              '        %(name)s = NULL;\n'
              '    else if (pygdk_rectangle_from_pyobject(py_%(name)s, &%(name)s_rect))\n'
              '        %(name)s = &%(name)s_rect;\n'
              '    else\n'
              '            return NULL;\n')
    def write_param(self, ptype, pname, pdflt, pnull, info):
        if pnull:
            info.varlist.add('GdkRectangle', pname + '_rect = { 0, 0, 0, 0 }')
            info.varlist.add('GdkRectangle', '*' + pname)
            info.varlist.add('PyObject', '*py_' + pname + ' = Py_None')
            info.add_parselist('O', ['&py_' + pname], [pname])
            info.arglist.append(pname)
            info.codebefore.append(self.null % {'name':  pname})
        else:
            info.varlist.add('GdkRectangle', pname + ' = { 0, 0, 0, 0 }')
            info.varlist.add('PyObject', '*py_' + pname)
            info.add_parselist('O', ['&py_' + pname], [pname])
            info.arglist.append('&' + pname)
            info.codebefore.append(self.normal % {'name':  pname})

class GdkRectangleArg(ArgType):
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add('GdkRectangle', 'ret')
        info.codeafter.append('    return pyg_boxed_new(GDK_TYPE_RECTANGLE, &ret, TRUE, TRUE);')

class PyObjectArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        info.varlist.add('PyObject', '*' + pname)
        info.add_parselist('O', ['&' + pname], [pname])
        info.arglist.append(pname)
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add("PyObject", "*ret")
        if ownsreturn:
            info.codeafter.append('    if (ret) {\n'
                                  '       return ret;\n'
                                  '    }\n'
                                  '    Py_INCREF(Py_None);\n'
                                  '    return Py_None;')
        else:
            info.codeafter.append('    if (!ret) ret = Py_None;\n'
                                  '    Py_INCREF(ret);\n'
                                  '    return ret;')

class CairoArg(ArgType):
    def write_param(self, ptype, pname, pdflt, pnull, info):
        info.varlist.add('PycairoContext', '*' + pname)
        info.add_parselist('O!', ['&PycairoContext_Type', '&' + pname], [pname])
        info.arglist.append('%s->ctx' % pname)
    def write_return(self, ptype, ownsreturn, info):
        info.varlist.add("cairo_t", "*ret")
        if ownsreturn:
            info.codeafter.append('    return PycairoContext_FromContext(ret, NULL, NULL);')
        else:
            info.codeafter.append('    cairo_reference(ret);\n'
                                  '    return PycairoContext_FromContext(ret, NULL, NULL);')


class ArgMatcher:
    def __init__(self):
        self.argtypes = {}
        self.reverse_argtypes = {}
        self.reverse_rettypes = {}

    def register(self, ptype, handler, overwrite=False):
        if not overwrite and ptype in self.argtypes:
            return
        self.argtypes[ptype] = handler
    def register_reverse(self, ptype, handler):
        self.reverse_argtypes[ptype] = handler
    def register_reverse_ret(self, ptype, handler):
        self.reverse_rettypes[ptype] = handler

    def register_enum(self, ptype, typecode):
        if typecode is None:
            self.register(ptype, IntArg())
        else:
            self.register(ptype, EnumArg(ptype, typecode))
    def register_flag(self, ptype, typecode):
        if typecode is None:
            self.register(ptype, IntArg())
        else:
            self.register(ptype, FlagsArg(ptype, typecode))
    def register_object(self, ptype, parent, typecode):
        oa = ObjectArg(ptype, parent, typecode)
        self.register(ptype, oa)  # in case I forget the * in the .defs
        self.register(ptype+'*', oa)
        self.register('const-'+ptype+'*', oa)
        if ptype == 'GdkPixmap':
            # hack to handle GdkBitmap synonym.
            self.register('GdkBitmap', oa)
            self.register('GdkBitmap*', oa)
    def register_boxed(self, ptype, typecode):
        if self.argtypes.has_key(ptype): return
        arg = BoxedArg(ptype, typecode)
        self.register(ptype, arg)
        self.register(ptype+'*', arg)
        self.register('const-'+ptype+'*', arg)
    def register_custom_boxed(self, ptype, pytype, getter, new):
        arg = CustomBoxedArg(ptype, pytype, getter, new)
        self.register(ptype+'*', arg)
        self.register('const-'+ptype+'*', arg)
    def register_pointer(self, ptype, typecode):
        arg = PointerArg(ptype, typecode)
        self.register(ptype, arg)
        self.register(ptype+'*', arg)
        self.register('const-'+ptype+'*', arg)

    def get(self, ptype):
        try:
            return self.argtypes[ptype]
        except KeyError:
            if ptype[:8] == 'GdkEvent' and ptype[-1] == '*':
                return self.argtypes['GdkEvent*']
            raise ArgTypeNotFoundError("No ArgType for %s" % (ptype,))
    def _get_reverse_common(self, ptype, registry):
        props = dict(c_type=ptype)
        try:
            return registry[ptype], props
        except KeyError:
            try:
                handler = self.argtypes[ptype]
            except KeyError:
                if ptype.startswith('GdkEvent') and ptype.endswith('*'):
                    handler = self.argtypes['GdkEvent*']
                else:
                    raise ArgTypeNotFoundError("No ArgType for %s" % (ptype,))
            if isinstance(handler, ObjectArg):
                return registry['GObject*'], props
            elif isinstance(handler, EnumArg):
                props['typecode'] = handler.typecode
                props['enumname'] = handler.enumname
                return registry['GEnum'], props
            elif isinstance(handler, FlagsArg):
                props['typecode'] = handler.typecode
                props['flagname'] = handler.flagname
                return registry['GFlags'], props
            elif isinstance(handler, BoxedArg):
                props['typecode'] = handler.typecode
                props['typename'] = handler.typename
                return registry['GBoxed'], props
            else:
                raise ArgTypeNotFoundError("No ArgType for %s" % (ptype,))

    def get_reverse(self, ptype):
        return self._get_reverse_common(ptype, self.reverse_argtypes)

    def get_reverse_ret(self, ptype):
        ret, props = self._get_reverse_common(ptype, self.reverse_rettypes)
        if hasattr(ptype, 'optional') and ptype.optional:
            if ret.supports_optional:
                props['optional'] = True
            else:
                raise ArgTypeNotFoundError("Unsupported 'optional' for %s"
                                           % (ptype,))
        return ret, props

    def object_is_a(self, otype, parent):
        if otype == None: return 0
        if otype == parent: return 1
        if not self.argtypes.has_key(otype): return 0
        return self.object_is_a(self.get(otype).parent, parent)

matcher = ArgMatcher()

arg = NoneArg()
matcher.register(None, arg)
matcher.register('none', arg)

arg = StringArg()
matcher.register('char*', arg)
matcher.register('gchar*', arg)
matcher.register('const-char*', arg)
matcher.register('char-const*', arg)
matcher.register('const-gchar*', arg)
matcher.register('gchar-const*', arg)
matcher.register('string', arg)
matcher.register('static_string', arg)

arg = UCharArg()
matcher.register('unsigned-char*', arg)
matcher.register('const-guchar*', arg)
matcher.register('const-guint8*', arg)
matcher.register('guchar*', arg)

arg = CharArg()
matcher.register('char', arg)
matcher.register('gchar', arg)
matcher.register('guchar', arg)

arg = GUniCharArg()
matcher.register('gunichar', arg)

arg = IntArg()
matcher.register('int', arg)
matcher.register('gint', arg)
matcher.register('short', arg)
matcher.register('gshort', arg)
matcher.register('gushort', arg)
matcher.register('gsize', SizeArg())
matcher.register('gssize', SSizeArg())
matcher.register('guint8', arg)
matcher.register('gint8', arg)
matcher.register('guint16', arg)
matcher.register('gint16', arg)
matcher.register('gint32', arg)
matcher.register('GTime', arg)
matcher.register('GSeekType', arg) # Hack, but we have no python wrapper

arg = LongArg()
matcher.register('long', arg)
matcher.register('glong', arg)

arg = UIntArg()
matcher.register('guint', arg)

arg = BoolArg()
matcher.register('gboolean', arg)

arg = TimeTArg()
matcher.register('time_t', arg)

matcher.register('guint32', UInt32Arg())

arg = ULongArg()
matcher.register('gulong', arg)

arg = Int64Arg()
matcher.register('gint64', arg)
matcher.register('long-long', arg)
matcher.register('goffset', arg)

arg = UInt64Arg()
matcher.register('guint64', arg)
matcher.register('unsigned-long-long', arg)

arg = DoubleArg()
matcher.register('double', arg)
matcher.register('gdouble', arg)
matcher.register('float', arg)
matcher.register('gfloat', arg)

arg = FileArg()
matcher.register('FILE*', arg)

# enums, flags, objects

matcher.register('GdkAtom', AtomArg())

matcher.register('GType', GTypeArg())
matcher.register('GtkType', GTypeArg())

matcher.register('GError**', GErrorArg())
matcher.register('GtkTreePath*', GtkTreePathArg())
matcher.register('GdkRectangle*', GdkRectanglePtrArg())
matcher.register('GtkAllocation*', GdkRectanglePtrArg())
matcher.register('GdkRectangle', GdkRectangleArg())
matcher.register('PyObject*', PyObjectArg())

matcher.register('GdkNativeWindow', ULongArg())

matcher.register_object('GObject', None, 'G_TYPE_OBJECT')

del arg

matcher.register('cairo_t*', CairoArg())
matcher.register_boxed("GClosure", "G_TYPE_CLOSURE")
