# Lasso - A free implementation of the Liberty Alliance specifications.
#
# Copyright (C) 2004-2007 Entr'ouvert
# http://lasso.entrouvert.org
#
# Authors: See AUTHORS file in top-level directory.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import os
import sys
import re
import textwrap
from utils import *

def defval_to_python_value(defval):
    if defval is None:
        return 'None'
    if defval.startswith('b:'):
        if defval[2:].lower() == 'true':
            return 'True'
        if defval[2:].lower() == 'false':
            return 'False'
    if defval.startswith('c:'):
        return defval[8:]
    raise Exception('Could not convert %s to python value' % defval)

def get_python_arg_decl(arg):
    if is_optional(arg):
        return '%s = %s' % (arg_name(arg), defval_to_python_value(arg_default(arg)))
    else:
        return arg_name(arg)

class Binding:
    def __init__(self, binding_data):
        self.binding_data = binding_data
        self.src_dir = os.path.dirname(__file__)

    def is_pygobject(self, t):
        return t and is_object((t,'',{})) and t not in self.binding_data.enums

    def generate(self):
        fd = open('lasso.py', 'w')
        self.generate_header(fd)
        self.generate_exceptions(fd)
        self.generate_constants(fd)
        for clss in self.binding_data.structs:
            self.generate_class(clss, fd)
        self.generate_functions(fd)
        self.generate_footer(fd)
        fd.close()

        fd = open('_lasso.c', 'w')
        self.generate_wrapper(fd)
        fd.close()

    def generate_header(self, fd):
        print >> fd, '''\
# this file has been generated automatically; do not edit

import _lasso

_lasso.init()

def cptrToPy(cptr):
    if cptr is None:
        return None
    klass = getattr(lasso, cptr.typename)
    o = klass.__new__(klass)
    o._cptr = cptr
    return o

class frozendict(dict):
    \'\'\'Immutable dict\'\'\'
    # from Python Cookbook:
    #   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/414283
    def _blocked_attribute(obj):
        raise AttributeError('A frozendict cannot be modified.')
    _blocked_attribute = property(_blocked_attribute)

    __delitem__ = __setitem__ = clear = _blocked_attribute
    pop = popitem = setdefault = update = _blocked_attribute

    def __new__(cls, *args):
        new = dict.__new__(cls)
        dict.__init__(new, *args)
        return new

    def __init__(self, *args):
        pass

    def __hash__(self):
        try:
            return self._cached_hash
        except AttributeError:
            h = self._cached_hash = hash(tuple(sorted(self.items())))
            return h

    def __repr__(self):
        return 'frozendict(%s)' % dict.__repr__(self)
'''

    def generate_exceptions(self, fd):
        done_cats = []
        print >> fd, '''\
class Error(Exception):
    code = None

    @staticmethod
    def raise_on_rc(rc):
        global exceptions_dict
        exception = exceptions_dict.get(rc, Error())
        exception.code = rc
        raise exception

    def __str__(self):
        return '<lasso.%s(%s): %s>' % (self.__class__.__name__, self.code, _lasso.strError(self.code))

    def __getitem__(self, i):
        # compatibility with SWIG bindings
        if i == 0:
            return self.code
        elif i == 1:
            return _lasso.strError(self.code)
        else:
            raise IndexError()
'''
        for exc_cat in self.binding_data.overrides.findall('exception/category'):
            cat = exc_cat.attrib.get('name')
            done_cats.append(cat)
            parent_cat = exc_cat.attrib.get('parent', '')
            print >> fd, '''\
class %sError(%sError):
    pass
''' % (cat, parent_cat)

        exceptions_dict = {}

        for c in self.binding_data.constants:
            m = re.match(r'LASSO_(\w+)_ERROR_(.*)', c[1])
            if not m:
                continue
            cat, detail = m.groups()
            cat = cat.title().replace('_', '')
            detail = (cat + '_' + detail).title().replace('_', '')
            if not cat in done_cats:
                done_cats.append(cat)
                for exc_cat in self.binding_data.overrides.findall('exception/category'):
                    if exc_cat.attrib.get('name') == cat:
                        parent_cat = exc_cat.attrib.get('parent')
                        break
                else:
                    parent_cat = ''

                print >> fd, '''\
class %sError(%sError):
    pass
''' % (cat, parent_cat)

            exceptions_dict[detail] = c[1][6:]

            if (detail, cat) == ('UnsupportedProfile', 'Logout'):
                # skip Logout/UnsupportedProfile exception as its name would
                # be the same as Profile/UnsupportedProfile; it is not a
                # problem skipping it as they both inherit from ProfileError
                # and the exception code will correctly be set by raise_on_rc
                # afterwards.  (actually it is even totally unnecessary to skip
                # it here as Profile/UnsupportedProfile is handled after
                # Logout/UnsupportedProfile, this is just done in the case the
                # ordering would change)
                continue

            print >> fd, '''\
class %sError(%sError):
    pass
''' % (detail, cat)

        print >> fd, 'exceptions_dict = {'
        for k, v in exceptions_dict.items():
            print >> fd, '    _lasso.%s: %sError,' % (v, k)
        print >> fd, '}'
        print >> fd, ''

    def generate_footer(self, fd):
        print >> fd, '''

import lasso

# backward compatibility with the SWIG binding

WSF_SUPPORT = WSF_ENABLED

Profile.isIdentityDirty = property(Profile.hasDirtyIdentity)
Profile.isSessionDirty = property(Profile.hasDirtySession)

def identity_get_provider_ids(self):
    return self.federations.keys()
Identity.providerIds = property(identity_get_provider_ids)

def server_get_provider_ids(self):
    return self.providers.keys()
Server.providerIds = property(server_get_provider_ids)

def session_get_provider_ids(self):
    return self.assertions.keys()
Session.providerIds = property(session_get_provider_ids)

Samlp2AuthnRequest.nameIDPolicy = Samlp2AuthnRequest.nameIdPolicy
LibAuthnRequest.nameIDPolicy = LibAuthnRequest.nameIdPolicy
Saml2Subject.nameID = Saml2Subject.nameId
MiscTextNode.text_child = MiscTextNode.textChild
NodeList = list
StringList = list
StringDict = dict
registerIdWsf2DstService = registerIdwsf2DstService
Provider.setEncryptionMode = Provider.set_encryptionMode

if WSF_SUPPORT:
    DiscoDescription_newWithBriefSoapHttpDescription = DiscoDescription.newWithBriefSoapHttpDescription
    Discovery.buildRequestMsg = Discovery.buildSoapRequestMsg
    InteractionProfileService.buildRequestMsg = InteractionProfileService.buildSoapRequestMsg
    InteractionProfileService.buildResponseMsg = InteractionProfileService.buildSoapResponseMsg
    DataService.buildRequestMsg = DataService.buildSoapRequestMsg
    DiscoModifyResponse.newEntryIds = DiscoModifyResponse.newEntryIDs
'''

    def generate_constants(self, fd):
        print >> fd, '### Constants (both enums and defines)'
        for c in self.binding_data.constants:
            print >> fd, '%s = _lasso.%s' % (c[1][6:], c[1][6:])
        for c in self.binding_data.overrides.findall('constant'):
            name = c.attrib.get('name')
            if c.attrib.get('value'):
                name = name[6:] # dropping LASSO_
                value = c.attrib.get('value')
                if value == 'True':
                    print >> fd, '%s = True' % name
                else:
                    print >> sys.stderr, 'E: unknown value for constant: %r' % value
        print >> fd, ''

    def generate_class(self, clss, fd):
        klassname = clss.name[5:] # remove Lasso from class name
        if clss.parent == 'GObject':
            parentname = 'object'
        else:
            parentname = clss.parent[5:]

        print >> fd, '''class %(klassname)s(%(parentname)s):''' % locals()
        if not clss.members and not clss.methods:
            print >> fd, '    pass'
            print >> fd, ''
            return

        methods = clss.methods[:]
        # constructor(s)
        method_prefix = 'lasso_' + format_as_underscored(klassname) + '_'
        for m in self.binding_data.functions:
            if m.name == method_prefix + 'new':
                c_args = []
                py_args = []
                for o in m.args:
                    arg_type, arg_name, arg_options = o
                    py_args.append(get_python_arg_decl(o))

                    if self.is_pygobject(arg_type):
                        c_args.append('%s and %s._cptr' % (arg_name, arg_name))
                    else:
                        c_args.append(arg_name)

                c_args = ', '.join(c_args)
                py_args = ', ' + ', '.join(py_args)
                print >> fd, '    def __init__(self%s):' % py_args
                # XXX: could check self._cptr.typename to see if it got the
                # right class type
                print >> fd, '        self._cptr = _lasso.%s(%s)' % (
                        m.name[6:], c_args)
                print >> fd, '        if self._cptr is None:'
                print >> fd, '            raise Error(\'failed to create object\')'
                print >> fd, ''

        for m in self.binding_data.functions:
            if m.name.startswith(method_prefix + 'new_'):
                constructor_name = format_as_camelcase(m.name[len(method_prefix):])
                c_args = []
                py_args = []
                for o in m.args:
                    arg_type, arg_name, arg_options = o
                    py_args.append(get_python_arg_decl(o))

                    if self.is_pygobject(arg_type):
                        c_args.append('%s and %s._cptr' % (arg_name, arg_name))
                    else:
                        c_args.append(arg_name)
                c_args = ', '.join(c_args)
                py_args = ', ' + ', '.join(py_args)
                print >> fd, '    @classmethod'
                print >> fd, '    def %s(cls%s):' % (constructor_name, py_args)
                print >> fd, '         obj = cls.__new__(cls)'
                print >> fd, '         obj._cptr = _lasso.%s(%s)' % (m.name[6:], c_args)
                print >> fd, '         if obj._cptr is None:'
                print >> fd, '             raise RuntimeError(\'lasso failed to create object\')'
                print >> fd, '         return obj'
                print >> fd, ''

        # create properties for members
        for m in clss.members:
            mname = format_as_camelcase(m[1])
            options = m[2]
            print >> fd, '    def get_%s(self):' % mname
            if self.is_pygobject(m[0]):
                print >> fd, '        t = _lasso.%s_%s_get(self._cptr)' % (
                        klassname, mname)
                print >> fd, '        return cptrToPy(t)'
            elif m[0] == 'GList*' and options.get('element-type') not in ('char*', 'xmlNode*'):
                print >> fd, '        l = _lasso.%s_%s_get(self._cptr)' % (
                        klassname, mname)
                print >> fd, '        if not l: return l'
                print >> fd, '        return tuple([cptrToPy(x) for x in l])'
            elif m[0] == 'GHashTable*':
                print >> fd, '        d = _lasso.%s_%s_get(self._cptr)' % (
                        klassname, mname)
                print >> fd, '        if not d: return d'
                if options.get('element-type') != 'char*':
                    print >> fd, '        d2 = {}'
                    print >> fd, '        for k, v in d.items():'
                    print >> fd, '            d2[k] = cptrToPy(v)'
                    print >> fd, '        return frozendict(d2)'
                else:
                    print >> fd, '        return frozendict(d)'
            else:
                print >> fd, '        return _lasso.%s_%s_get(self._cptr)' % (
                        klassname, mname)
            print >> fd, '    def set_%s(self, value):' % mname
            if self.is_pygobject(m[0]):
                print >> fd, '        if value is not None:'
                print >> fd, '            value = value and value._cptr'
            elif m[0] == 'GList*' and options.get('element-type') not in ('char*', 'xmlNode*'):
                print >> fd, '        if value is not None:'
                print >> fd, '            value = tuple([x._cptr for x in value])'
            print >> fd, '        _lasso.%s_%s_set(self._cptr, value)' % (
                    klassname, mname)
            print >> fd, '    %s = property(get_%s, set_%s)' % (mname, mname, mname)
            print >> fd, ''

        # first pass on methods, getting accessors
        for m in clss.methods:
            if not ('_get_' in m.name and len(m.args) == 1):
                continue
            methods.remove(m)
            try:
                setter_name = m.name.replace('_get_', '_set_')
                setter = [x for x in methods if x.name == setter_name][0]
                methods.remove(setter)
            except IndexError:
                setter = None
            if m.rename:
                mname = m.rename
                if mname.startswith('lasso_'):
                    mname = mname[6:]
                mname = '%s%s' % (mname[0].lower(), mname[1:])
                print >> fd, '    def get_%s(self):' % mname
                function_name = m.rename
                if function_name.startswith('lasso_'):
                    function_name = function_name[6:]
            else:
                mname = m.name
                try:
                    mname = re.match(r'lasso_.*_get_(\w+)', mname).group(1)
                except:
                    raise
                mname = format_underscore_as_camelcase(mname)
                print >> fd, '    def get_%s(self):' % mname
                function_name = m.name[6:]

            if self.is_pygobject(m.return_type):
                print >> fd, '        t = _lasso.%s(self._cptr)' % function_name
                print >> fd, '        return cptrToPy(t)'
            else:
                print >> fd, '        return _lasso.%s(self._cptr)' % function_name

            if mname[0] == mname[0].lower() and not m.rename:
                # API compatibility with SWIG bindings which didn't have
                # accessors for those methods and used totally pythonified
                # method name instead, such as getNextProviderId
                print >> fd, '    get%s%s = get_%s' % (
                        mname[0].upper(), mname[1:], mname)
            if setter:
                print >> fd, '    def set_%s(self, value):' % mname
                print >> fd, '        _lasso.%s(self._cptr, value)' % setter.name[6:]
                print >> fd, '    %s = property(get_%s, set_%s)' % (mname, mname, mname)
            else:
                print >> fd, '    %s = property(get_%s)' % (mname, mname)
            print >> fd, ''

        # second pass on methods, real methods
        for m in methods:
            if m.name.endswith('_new') or m.name.endswith('_new_from_dump') or \
                    m.name.endswith('_new_full'):
                continue
            if not m.name.startswith(method_prefix):
                print >> sys.stderr, 'W:', m.name, 'vs', method_prefix
                continue

            if m.rename:
                mname = m.rename[len(method_prefix):]
                function_name = m.rename[6:]
            else:
                mname = m.name[len(method_prefix):]
                function_name = m.name[6:]
            py_args = []
            c_args = []
            outarg = None
            for o in m.args[1:]:
                arg_type, arg_name, arg_options = o
                if is_out(o):
                    assert not outarg
                    outarg = o
                    outvar = '_%s_out' % arg_name
                else:
                    py_args.append(get_python_arg_decl(o))

                if is_out(o):
                    c_args.append(outvar)
                elif not self.is_pygobject(arg_type):
                    c_args.append(arg_name)
                else:
                    c_args.append('%s and %s._cptr' % (arg_name, arg_name))
            # check py_args
            opt = False
            for x in py_args:
                if '=' in x:
                    opt = True
                elif opt:
                    print 'W: non-optional follow optional,', m

            if py_args:
                py_args = ', ' + ', '.join(py_args)
            else:
                py_args = ''
            if c_args:
                c_args = ', ' + ', '.join(c_args)
            else:
                c_args = ''

            print >> fd, '    def %s(self%s):' % (
                    format_underscore_as_camelcase(mname), py_args)
            if m.docstring:
                print >> fd, "        '''"
                print >> fd, self.format_docstring(m, mname, 8)
                print >> fd, "        '''"

            if outarg:
                print >> fd, "        %s = list((None,))" % outvar
            return_type = m.return_type
            return_type_qualifier = m.return_type_qualifier
            assert is_int(make_arg(return_type),self.binding_data) or not outarg
            if return_type in ('gint', 'int'):
                print >> fd, '        rc = _lasso.%s(self._cptr%s)' % (
                        function_name, c_args)
                print >> fd, '        if rc != 0:'
                print >> fd, '            raise Error.raise_on_rc(rc)'
            elif return_type in (None, 'void'):
                print >> fd, '        _lasso.%s(self._cptr%s)' % (
                        function_name, c_args)
            elif return_type == 'GList*' and self.is_pygobject(return_type_qualifier):
                print >> fd, '        value = _lasso.%s(self._cptr%s)' % (
                        function_name, c_args)
                print >> fd, '        if value is not None:'
                print >> fd, '            value = tuple([cptrToPy(x) for x in value])'
                print >> fd, '        return value'
            elif self.is_pygobject(return_type):
                print >> fd, '        return cptrToPy(_lasso.%s(self._cptr%s))' % (
                        function_name, c_args)
            else:
                print >> fd, '        return _lasso.%s(self._cptr%s)' % (
                        function_name, c_args)
            if outarg:
                print >> fd, '        return %s[0]' % outvar
            print >> fd, ''

        print >> fd, ''

    def format_docstring(self, func, method_name, indent):
        if func.args:
            first_arg_name = func.args[0][1]
        else:
            first_arg_name = None

        def format_inlines_sub(s):
            type = s.group(1)[0]
            var = s.group(1)[1:]
            if type == '#': # struct
                if var.startswith('Lasso'):
                    return 'L{%s}' % var[5:]
            elif type == '%': # %TRUE, %FALSE
                if var == 'TRUE':
                    return 'True'
                if var == 'FALSE':
                    return 'False'
                print >> sys.stderr, 'W: unknown docstring thingie: %s' % s.group(1)
            elif type == '@':
                if var == first_arg_name:
                    var = 'self'
                return 'C{%s}' % var
            return s.group(1)

        regex = re.compile(r'([\#%@]\w+)', re.DOTALL)

        def format_inline(s):
            s = regex.sub(format_inlines_sub, s)
            return s.replace('NULL', 'None')

        docstring = func.docstring
        s = []

        if docstring.description:
            for paragraph in docstring.description.split('\n\n'):
                if '<itemizedlist>' in paragraph:
                    before, after = paragraph.split('<itemizedlist>' ,1)
                    if before:
                        s.append('\n'.join(textwrap.wrap(
                                        format_inline(before), 70)))

                    # remove tags
                    after = after.replace('<itemizedlist>', '')
                    after = after.replace('</itemizedlist>', '')

                    for listitem in after.split('<listitem><para>'):
                        listitem = listitem.replace('</para></listitem>', '').strip()
                        s.append('\n'.join(textwrap.wrap(
                                        format_inline(listitem), 70,
                                        initial_indent = ' - ',
                                        subsequent_indent = '   ')))
                        s.append('\n\n')

                else:
                    s.append('\n'.join(textwrap.wrap(
                                    format_inline(paragraph), 70)))
                    s.append('\n\n')

        for param in docstring.parameters:
            s.append('\n'.join(textwrap.wrap(
                            format_inline(param[1]), 70,
                            initial_indent = '@param %s: ' % param[0],
                            subsequent_indent = 4*' ')))
            s.append('\n')
        if docstring.return_value:
            rv = docstring.return_value
            exceptions_instead = ['0 on success; or a negative value otherwise.',
                    '0 on success; a negative value if an error occured.',
                    '0 on success; another value if an error occured.']
            if not rv in exceptions_instead:
                owner_info = ['This xmlnode must be freed by caller.',
                        'The string must be freed by the caller.',
                        'It must be freed by the caller.',
                        'This string must be freed by the caller.']
                for o_i in owner_info:
                    rv = rv.replace(o_i, '')
                s.append('\n'.join(textwrap.wrap(
                                format_inline(rv), 70,
                                initial_indent = '@return: ',
                                subsequent_indent = 4*' ')))
                s.append('\n')


        if s:
            s[-1] = s[-1].rstrip() # remove trailing newline from last line

        return '\n'.join([(indent*' ')+x for x in ''.join(s).splitlines()])


    def generate_functions(self, fd):
        for m in self.binding_data.functions:
            if m.name.endswith('_new') or '_new_' in m.name:
                continue
            if m.rename:
                pname = m.rename
                name = m.rename
                if name.startswith('lasso_'):
                    name = name[6:]
                    pname = format_as_camelcase(name)
            else:
                name = m.name[6:]
                pname = format_as_camelcase(name)
            print >> fd, '%s = _lasso.%s' % (pname, name)


    def generate_wrapper(self, fd):
        print >> fd, open(os.path.join(self.src_dir,'wrapper_top.c')).read()
        for h in self.binding_data.headers:
            print >> fd, '#include <%s>' % h
        print >> fd, ''

        self.generate_constants_wrapper(fd)

        self.wrapper_list = []
        for m in self.binding_data.functions:
            self.generate_function_wrapper(m, fd)
        for c in self.binding_data.structs:
            self.generate_member_wrapper(c, fd)
            for m in c.methods:
                self.generate_function_wrapper(m, fd)
        self.generate_wrapper_list(fd)
        print >> fd, open(os.path.join(self.src_dir,'wrapper_bottom.c')).read()

    def generate_constants_wrapper(self, fd):
        print >> fd, '''static void
register_constants(PyObject *d)
{
    PyObject *obj;
'''
        for c in self.binding_data.constants:
            if c[0] == 'i':
                print >> fd, '    obj = PyInt_FromLong(%s);' % c[1]
            elif c[0] == 's':
                print >> fd, '    obj = PyString_FromString(%s);' % c[1]
            elif c[0] == 'b':
                print >> fd, '''\
#ifdef %s
    obj = Py_True;
#else
    obj = Py_False;
#endif''' % c[1]
            else:
                print >> sys.stderr, 'E: unknown constant type: %r' % c[0]
            print >> fd, '    PyDict_SetItemString(d, "%s", obj);' % c[1][6:]
            print >> fd, '    Py_DECREF(obj);'
        print >> fd, '}'
        print >> fd, ''


    def generate_member_wrapper(self, c, fd):
        klassname = c.name
        for m in c.members:
            mname = format_as_camelcase(m[1])
            # getter
            print >> fd, '''static PyObject*
%s_%s_get(G_GNUC_UNUSED PyObject *self, PyObject *args)
{''' % (klassname[5:], mname)
            self.wrapper_list.append('%s_%s_get' % (klassname[5:], mname))

            ftype = m[0]
            if ftype in ('char*', 'const char*', 'guchar*', 'const guchar*', 'gchar*', 'const gchar*'):
                ftype = 'char*'
            print >> fd, '    %s return_value;' % ftype
            print >> fd, '    PyObject* return_pyvalue;'
            print >> fd, '    PyGObjectPtr* cvt_this;'
            print >> fd, '    %s* this;' % klassname
            print >> fd, ''
            print >> fd, '    if (! PyArg_ParseTuple(args, "O", &cvt_this)) return NULL;'
            print >> fd, '    this = (%s*)cvt_this->obj;' % klassname

            if self.is_pygobject(ftype):
                print >> fd, '    return_value = this->%s;' % m[1];
            elif ftype in ('char*',):
                print >> fd, '    return_value = g_strdup(this->%s);' % m[1]
            else:
                print >> fd, '    return_value = this->%s;' % m[1];

            self.return_value(fd, ftype, m[2])

            print >> fd, '    return return_pyvalue;'
            print >> fd, '}'
            print >> fd, ''

            # setter
            print >> fd, '''static PyObject*
%s_%s_set(G_GNUC_UNUSED PyObject *self, PyObject *args)
{''' % (klassname[5:], mname)
            self.wrapper_list.append('%s_%s_set' % (klassname[5:], mname))

            print >> fd, '    PyGObjectPtr* cvt_this;'
            print >> fd, '    %s* this;' % klassname
            arg_type = m[0]
            # Determine type class
            if m[0] in ('char*', 'const char*', 'guchar*', 'const guchar*', 'gchar*', 'const gchar*'):
                arg_type = arg_type.replace('const ', '')
                parse_format = 'z'
                parse_arg = '&value'
                print >> fd, '    %s value;' % arg_type
            elif arg_type in ['int', 'gint', 'gboolean', 'const gboolean'] + self.binding_data.enums:
                parse_format = 'i'
                parse_arg = '&value'
                print >> fd, '    %s value;' % arg_type
            elif arg_type in ('GList*','GHashTable*', 'xmlNode*'):
                parse_format = 'O'
                print >> fd, '    PyObject *cvt_value;'
                parse_arg = '&cvt_value'
            else:
                parse_format = 'O'
                print >> fd, '    PyGObjectPtr *cvt_value;'
                parse_arg = '&cvt_value'
            # Get GObject
            print >> fd, '    if (! PyArg_ParseTuple(args, "O%s", &cvt_this, %s)) return NULL;' % (
                    parse_format, parse_arg)
            print >> fd, '    this = (%s*)cvt_this->obj;' % klassname
            # Change value
            if parse_format == 'i':
                print >> fd, '    this->%s = value;' % m[1]
            elif parse_format in ('s', 'z'):
                print >> fd, '    if (this->%s) g_free(this->%s);' % (m[1], m[1])
                print >> fd, '    this->%s = g_strdup(value);' % m[1]
            elif parse_format == 'O' and arg_type == 'GList*':
                element_type = m[2].get('element-type')
                if element_type == 'char*':
                    print >> fd, '    set_list_of_strings(&this->%s, cvt_value);' % m[1]
                elif element_type == 'xmlNode*':
                    print >> fd, '    set_list_of_xml_nodes(&this->%s, cvt_value);' % m[1]
                else:
                    print >> fd, '    set_list_of_pygobject(&this->%s, cvt_value);' % m[1]
            elif parse_format == 'O' and arg_type == 'GHashTable*':
                print >> fd, '    set_hashtable_of_pygobject(this->%s, cvt_value);' % m[1]
            elif parse_format == 'O' and arg_type == 'xmlNode*':
                print >> fd, '    if (this->%s) xmlFreeNode(this->%s);' % (m[1], m[1])
                print >> fd, '    this->%s = get_xml_node_from_pystring(cvt_value);' % m[1]
            elif parse_format == 'O':
                print >> fd, '    set_object_field((GObject**)&this->%s, cvt_value);' % m[1]
            print >> fd, '    return noneRef();'
            print >> fd, '}'
            print >> fd, ''


    def return_value(self, fd, vtype, options, return_var_name = 'return_value', return_pyvar_name = 'return_pyvalue'):
        if vtype == 'gboolean':
            print >> fd, '    if (%s) {' % return_var_name
            print >> fd, '        Py_INCREF(Py_True);'
            print >> fd, '        %s = Py_True;' % return_pyvar_name
            print >> fd, '    } else {'
            print >> fd, '        Py_INCREF(Py_False);'
            print >> fd, '        %s = Py_False;' % return_pyvar_name
            print >> fd, '    }'
        elif vtype in ['int', 'gint'] + self.binding_data.enums:
            print >> fd, '    %s = PyInt_FromLong(%s);' % (return_pyvar_name, return_var_name)
        elif vtype in ('char*', 'guchar*', 'const guchar*', 'gchar*'):
            print >> fd, '    if (%s) {' % return_var_name
            print >> fd, '        %s = PyString_FromString(%s);' % (return_pyvar_name, return_var_name)
            print >> fd, '        g_free(%s);' % return_var_name
            print >> fd, '    } else {'
            print >> fd, '        %s = noneRef();' % return_pyvar_name
            print >> fd, '    }'
        elif vtype in ('const char*', 'const gchar*'):
            print >> fd, '    if (%s) {' % return_var_name
            print >> fd, '        %s = PyString_FromString(%s);' % (return_pyvar_name, return_var_name)
            print >> fd, '    } else {'
            print >> fd, '        %s = noneRef();' % return_pyvar_name
            print >> fd, '    }'
        elif vtype in ('const GList*', 'GList*',):
            element_type = options.get('element-type')
            if not element_type or self.is_pygobject(element_type):
                print >> fd, '    %s = get_list_of_pygobject(%s);' % (return_pyvar_name, return_var_name)
            elif element_type == 'char*':
                print >> fd, '    %s = get_list_of_strings(%s);' % (return_pyvar_name, return_var_name)
            elif element_type.startswith('xmlNode'):
                print >> fd, '    %s = get_list_of_xml_nodes(%s);' % (return_pyvar_name, return_var_name)
            else:
                raise Exception('Should not happen: %s %s ' % (repr(options), vtype))
        elif vtype in ('GHashTable*',):
            element_type = options.get('element-type')
            if element_type == 'char*':
                print >> fd, '    %s = get_dict_from_hashtable_of_strings(%s);' % (return_pyvar_name, return_var_name)
            else:
                print >> fd, '    %s = get_dict_from_hashtable_of_objects(%s);' % (return_pyvar_name, return_var_name)
        elif vtype == 'xmlNode*':
            # convert xmlNode* to strings
            print >> fd, '    if (%s) {' % return_var_name
            print >> fd, '        %s = get_pystring_from_xml_node(%s);' % (return_pyvar_name, return_var_name)
            print >> fd, '    } else {'
            print >> fd, '        %s = noneRef();' % return_pyvar_name
            print >> fd, '    }'
        else:
            # return a PyGObjectPtr (wrapper around GObject)
            print >> fd, '''\
    if (%s) {
        %s = PyGObjectPtr_New(G_OBJECT(%s));
    } else {
        %s = noneRef();
    }
''' % (return_var_name, return_pyvar_name, return_var_name, return_pyvar_name)

    def generate_function_wrapper(self, m, fd):
        if m.rename:
            name = m.rename
            if name.startswith('lasso_'):
                name = name[6:]
        else:
            name = m.name[6:]
        self.wrapper_list.append(name)
        print >> fd, '''static PyObject*
%s(G_GNUC_UNUSED PyObject *self, PyObject *args)
{''' % name
        parse_tuple_format = []
        parse_tuple_args = []
        for arg in m.args:
            arg_type, arg_name, arg_options = arg
            arg_def = None
            python_cvt_def = None
            defval = None
            if arg_options.get('optional'):
                if not '|' in parse_tuple_format:
                    parse_tuple_format.append('|')
            if arg_type in ('char*', 'const char*', 'gchar*', 'const gchar*'):
                arg_type = arg_type.replace('const ', '')
                if arg_options.get('optional'):
                    parse_tuple_format.append('z')
                else:
                    parse_tuple_format.append('s')
                parse_tuple_args.append('&%s' % arg_name)
                arg_def = '    %s %s = NULL;' % (arg[0], arg[1])
            elif arg_type in ['GType', 'int', 'gint', 'gboolean', 'const gboolean', 'time_t'] + self.binding_data.enums:
                parse_tuple_format.append('i')
                parse_tuple_args.append('&%s' % arg_name)
                if arg_options.get('default'):
                    defval = arg_options.get('default')
                    if defval.startswith('b:'):
                        defval = defval[2:].upper()
                    else:
                        defval = defval[2:]
                    arg_def = '    %s %s = %s;' % (arg[0], arg[1], defval)
                else:
                    arg_def = '    %s %s;' % (arg[0], arg[1])
            elif is_xml_node(arg) or is_list(arg) or is_time_t_pointer(arg):
                parse_tuple_format.append('O')
                parse_tuple_args.append('&cvt_%s' % arg_name)
                arg_def = '    %s %s = NULL;' % (arg[0], arg[1])
                python_cvt_def = '    PyObject *cvt_%s = NULL;' % arg_name
            else:
                parse_tuple_format.append('O')
                parse_tuple_args.append('&cvt_%s' % arg_name)
                arg_def = '    %s %s = NULL;' % (arg[0], arg[1])
                python_cvt_def = '    PyGObjectPtr *cvt_%s = NULL;' % arg_name
            if is_out(arg):
                arg_def = '    %s %s = NULL;' % (var_type(arg), arg[1])
                parse_tuple_format.pop()
                parse_tuple_format.append('O')
                parse_tuple_args.pop()
                parse_tuple_args.append('&cvt_%s_out' % arg_name)
                python_cvt_def = '    PyObject *cvt_%s_out = NULL;' % arg_name
                print >> fd, '    PyObject *out_pyvalue = NULL;'
            print >> fd, arg_def
            if python_cvt_def:
                print >> fd, python_cvt_def

        if m.return_type:
            print >> fd, '    %s return_value;' % m.return_type
            print >> fd, '    PyObject* return_pyvalue = NULL;'
        print >> fd, ''

        parse_tuple_args = ', '.join(parse_tuple_args)
        if parse_tuple_args:
            parse_tuple_args = ', ' + parse_tuple_args

        print >> fd, '    if (! PyArg_ParseTuple(args, "%s"%s)) return NULL;' % (
                ''.join(parse_tuple_format), parse_tuple_args)

        for f, arg in zip([ x for x in parse_tuple_format if x != '|'], m.args):
            if is_out(arg):
                continue
            if is_list(arg):
                qualifier = element_type(arg)
                if qualifier == 'char*' or qualifier == 'utf8':
                    print >> fd, '    set_list_of_strings(&%s, cvt_%s);' % (arg[1], arg[1])
                elif qualifier == 'xmlNode*':
                    print >> fd, '    set_list_of_xml_nodes(&%s, cvt_%s);' % (arg[1], arg[1])
                elif isinstance(qualifier, basestring) and qualifier.startswith('Lasso'):
                    print >> fd, '    set_list_of_pygobject(&%s, cvt_%s);' % (arg[1], arg[1])
                else:
                    print >> sys.stderr, 'E: unqualified GList argument in', name, qualifier, arg
            elif is_xml_node(arg):
                print >> fd, '    %s = get_xml_node_from_pystring(cvt_%s);' % (arg[1], arg[1])
            elif is_time_t_pointer(arg):
                print >> fd, '    %s = get_time_t(cvt_%s);' % (arg[1], arg[1])
            elif f == 'O':
                if is_optional(arg):
                    print >> fd, '    if (PyObject_TypeCheck((PyObject*)cvt_%s, &PyGObjectPtrType)) {' % arg[1]
                    print >> fd, '        %s = (%s)cvt_%s->obj;' % (arg[1], arg[0], arg[1])
                    print >> fd, '    } else {'
                    print >> fd, '        %s = NULL;' % arg[1]
                    print >> fd, '    }'
                else:
                    print >> fd, '    if (PyObject_TypeCheck((PyObject*)cvt_%s, &PyGObjectPtrType)) {' % arg[1]
                    print >> fd, '        %s = (%s)cvt_%s->obj;' % (arg[1], arg[0], arg[1])
                    print >> fd, '    } else {'
                    print >> fd, '        PyErr_SetString(PyExc_TypeError, "value should be a PyGObject");'
                    print >> fd, '        return NULL;'
                    print >> fd, '    }'


        if m.return_type:
            print >> fd, '    return_value =',
            if 'new' in m.name:
                print >> fd, '(%s)' % m.return_type,
        else:
            print >> fd, '   ',
        print >> fd, '%s(%s);' % (m.name, ', '.join([ref_name(x) for x in m.args]))

        for f, arg in zip(parse_tuple_format, m.args):
            if is_out(arg):
                self.return_value(fd, var_type(arg), {'element-type': element_type(arg)}, return_var_name = arg[1], return_pyvar_name = 'out_pyvalue')
                print >> fd, '    PyList_SetItem(cvt_%s_out, 0, out_pyvalue);' % arg[1]
            elif arg[0] == 'GList*':
                qualifier = arg[2].get('element-type')
                if qualifier == 'char*':
                    print >> fd, '    free_list(&%s, (GFunc)g_free);' % arg[1]
                elif qualifier == 'xmlNode*':
                    print >> fd, '    free_list(&%s, (GFunc)xmlFreeNode);' % arg[1]
                elif qualifier == 'LassoNode':
                    print >> fd, '    free_list(&%s, (GFunc)g_object_unref);' % arg[1]
            elif is_time_t_pointer(arg):
                print >> fd, '    if (%s) free(%s);' % (arg[1], arg[1])

        if not m.return_type:
            print >> fd, '    return noneRef();'
        else:
            # Constructor so decrease refcount (it was incremented by PyGObjectPtr_New called
            # in self.return_value
            self.return_value(fd, m.return_type, {'element-type': m.return_type_qualifier})
            if m.return_owner and self.is_pygobject(m.return_type):
                print >> fd, '    if (return_value) g_object_unref(return_value);'
            print >> fd, '    return return_pyvalue;'
        print >> fd, '}'
        print >> fd, ''

    def generate_wrapper_list(self, fd):
        print >> fd, '''
static PyMethodDef lasso_methods[] = {'''
        for m in self.wrapper_list:
            print >> fd, '    {"%s", %s, METH_VARARGS, NULL},' % (m, m)
        print >> fd, '    {NULL, NULL, 0, NULL}'
        print >> fd, '};'
        print >> fd, ''

