# 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, see <http://www.gnu.org/licenses/>.

import sys
import os

from utils import *

class WrapperSource:
    def __init__(self, binding_data, fd):
        self.binding_data = binding_data
        self.fd = fd
        self.functions_list = []
        self.src_dir = os.path.dirname(__file__)

    def is_object(self, t):
        return t not in ['char*', 'const char*', 'gchar*', 'const gchar*', 'GList*', 'GHashTable*', 'GType',
                'xmlNode*', 'int', 'gint', 'gboolean', 'const gboolean'] + self.binding_data.enums

    def generate(self):
        self.generate_header()
        self.generate_constants()
        self.generate_middle()
        for m in self.binding_data.functions:
            self.generate_function(m)
        for c in self.binding_data.structs:
            self.generate_members(c)
            for m in c.methods:
                self.generate_function(m)
        self.generate_functions_list()
        self.generate_footer()

    def generate_header(self):
        self.functions_list.append('lasso_get_object_typename')
        self.functions_list.append('lasso_init')
        self.functions_list.append('lasso_shutdown')

        print('''\
/* this file has been generated automatically; do not edit */
''', file=self.fd)

        print(open(os.path.join(self.src_dir,'wrapper_source_top.c')).read(), file=self.fd)

        for h in self.binding_data.headers:
            print('#include <%s>' % h, file=self.fd)
        print('', file=self.fd)

        print('''\
PHP_MINIT_FUNCTION(lasso)
{
    le_lasso_server = zend_register_list_destructors_ex(php_gobject_generic_destructor, NULL, PHP_LASSO_SERVER_RES_NAME, module_number);
    lasso_init();
''', file=self.fd)

    def generate_constants(self):
        print('    /* Constants (both enums and defines) */', file=self.fd)
        for c in self.binding_data.constants:
            if c[0] == 'i':
                print('    REGISTER_LONG_CONSTANT("%s", %s, CONST_CS|CONST_PERSISTENT);' % (c[1], c[1]), file=self.fd)
            elif c[0] == 's':
                print('    REGISTER_STRING_CONSTANT("%s", (char*) %s, CONST_CS|CONST_PERSISTENT);' % (c[1], c[1]), file=self.fd)
            elif c[0] == 'b':
                print('''\
#ifdef %s
    REGISTER_LONG_CONSTANT("%s", 1, CONST_CS|CONST_PERSISTENT);
#else
    REGISTER_LONG_CONSTANT("%s", 0, CONST_CS|CONST_PERSISTENT);
#endif''' % (c[1], c[1], c[1]), file=self.fd)
            else:
                print('E: unknown constant type: %r' % c[0], file=sys.stderr)
        print('', file=self.fd)

    def generate_middle(self):
        print('''\
    return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(lasso)
{
    lasso_shutdown();
    return SUCCESS;
}

''', file=self.fd)

    def set_zval(self, zval_name, c_variable, type, free = False):
        '''Emit code to set a zval* of name zval_name, from the value of the C variable called c_variable type, type.
        '''
        # first we free the previous value
        p = (zval_name, c_variable)
        q = { 'zval_name' : zval_name, 'c_variable' : c_variable }
        print('    zval_dtor(%s);' % zval_name, file=self.fd)
        if is_pointer(type):
            print('    if (! %s) {' % c_variable, file=self.fd)
            print('       ZVAL_NULL(%s);' % zval_name, file=self.fd)
            print('    } else {', file=self.fd)
        if is_int(type, self.binding_data):
            print('    ZVAL_LONG(%s, %s);' % p, file=self.fd)
        elif is_boolean(type):
            print('    ZVAL_BOOL(%s, %s);' % p, file=self.fd)
        elif is_cstring(type):
            print('    ZVAL_STRING(%s, (char*)%s);' % p, file=self.fd)
            if free and not is_const(type):
                print('g_free(%s)' % c_variable, file=self.fd)
        elif arg_type(type) == 'xmlNode*':
            print('''\
    {
        char* xmlString = get_string_from_xml_node(%(c_variable)s);
        if (xmlString) {
            ZVAL_STRING(%(zval_name)s, xmlString);
        } else {
            ZVAL_NULL(%(zval_name)s);
        }
    }
''' % q, file=self.fd)
        elif is_glist(type):
            elem_type = make_arg(element_type(type))
            if not arg_type(elem_type):
                raise Exception('unknown element-type: ' + repr(type))
            if is_cstring(elem_type):
                function = 'set_array_from_list_of_strings'
                free_function = 'free_glist(&%(c_variable)s, (GFunc)free);'
            elif arg_type(elem_type).startswith('xmlNode'):
                function = 'set_array_from_list_of_xmlnodes'
                free_function = 'free_glist(&%(c_variable)s, (GFunc)xmlFree);'
            elif is_object(elem_type):
                function = 'set_array_from_list_of_objects'
                free_function = 'g_list_free(%(c_variable)s);'
            else:
                raise Exception('unknown element-type: ' + repr(type))
            print('     %s((GList*)%s, &%s);' % (function, c_variable, zval_name), file=self.fd)
            if free:
                print('   ', free_function % q, file=self.fd)
        elif is_object(type):
            print('''\
    if (G_IS_OBJECT(%(c_variable)s)) {
        PhpGObjectPtr *obj = PhpGObjectPtr_New(G_OBJECT(%(c_variable)s));
        zend_resource *res = zend_register_resource(obj, le_lasso_server);
        ZVAL_RES(%(zval_name)s, res);
    } else {
        ZVAL_NULL(%(zval_name)s);
    }''' % q, file=self.fd)
            if free:
                print('''\
    if (%(c_variable)s) {
        g_object_unref(%(c_variable)s); // If constructor ref is off by one'
    }''' % q, file=self.fd)

        else:
            raise Exception('unknown type: ' + repr(type) + unconstify(arg_type(type)))
        if is_pointer(type):
            print('    }', file=self.fd)



    def return_value(self, arg, free = False):
        if arg is None:
            return

        if is_boolean(arg):
            print('    RETVAL_BOOL(return_c_value);', file=self.fd)
        elif is_int(arg, self.binding_data):
            print('    RETVAL_LONG(return_c_value);', file=self.fd)
        elif is_cstring(arg):
            print('''\
    if (return_c_value) {
        RETVAL_STRING((char*)return_c_value);
    } else {
        RETVAL_NULL();
    }''', file=self.fd)
            if free:
                print('    free(return_c_value);', file=self.fd)
        elif is_xml_node(arg):
            print('''\
    {
        char* xmlString = get_string_from_xml_node(return_c_value);
        if (xmlString) {
            RETVAL_STRING(xmlString);
        } else {
            RETVAL_NULL();
        }
    }
''', file=self.fd)
            if free:
                print('    lasso_release_xml_node(return_c_value);', file=self.fd)
        elif is_glist(arg):
            el_type = element_type(arg)
            if is_cstring(el_type):
                print('''\
    set_array_from_list_of_strings((GList*)return_c_value, &return_value);
''', file=self.fd)
                if free:
                    print('    lasso_release_list_of_strings(return_c_value);', file=self.fd)
            elif is_xml_node(el_type):
                print('''\
    set_array_from_list_of_xmlnodes((GList*)return_c_value, &return_value);
''', file=self.fd)
                if free or is_transfer_full(arg):
                    print('    lasso_release_list_of_xml_node(return_c_value);', file=self.fd)
            elif is_object(el_type):
                print('''\
    set_array_from_list_of_objects((GList*)return_c_value, &return_value);
''', file=self.fd)
                if free:
                    print('    lasso_release_list_of_gobjects(return_c_value);', file=self.fd)
            else:
                raise Exception('cannot return value for %s' % (arg,))
        elif is_hashtable(arg):
            el_type = element_type(arg)
            if is_object(el_type):
                print('''\
    set_array_from_hashtable_of_objects(return_c_value, &return_value);
''', file=self.fd)
            else:
                if not is_cstring(arg):
                    print >>sys.stderr, 'W: %s has no explicit string annotation' % (arg,)
                print('''\
    set_array_from_hashtable_of_strings(return_c_value, &return_value);
''', file=self.fd)
        elif is_object(arg):
            print('''\
    if (return_c_value) {
        PhpGObjectPtr *self;
        self = PhpGObjectPtr_New(G_OBJECT(return_c_value));
        zend_resource *res = zend_register_resource(self, le_lasso_server);
        ZVAL_RES(return_value, res);
    } else {
        RETVAL_NULL();
    }''', file=self.fd)
            if free:
                print('    lasso_release_gobject(return_c_value);', file=self.fd)
        else:
            raise Exception('cannot return value for %s' % (arg,))

    def generate_function(self, m):
        if m.name in ('lasso_init','lasso_shutdown'):
            return
        if m.rename:
            name = m.rename
        else:
            name = m.name
        self.functions_list.append(name)
        print('''PHP_FUNCTION(%s)
{''' % name, file=self.fd)
        parse_tuple_format = []
        parse_tuple_args = []
        for arg in m.args:
            if is_out(arg):
                print('   zval *php_out_%s = NULL;' % arg_name(arg), file=self.fd)
                print('   %s %s;' % (var_type(arg), arg_name(arg)), file=self.fd)
                parse_tuple_format.append('z!')
                parse_tuple_args.append('&php_out_%s' % arg_name(arg))
            elif is_cstring(arg):
                parse_tuple_format.append('s!')
                parse_tuple_args.append('&%s_str, &%s_len' % (arg_name(arg), arg_name(arg)))
                print('    %s %s = NULL;' % ('char*', arg_name(arg)), file=self.fd)
                print('    %s %s_str = NULL;' % ('char*', arg_name(arg)), file=self.fd)
                print('    %s %s_len = 0;' % ('size_t', arg_name(arg)), file=self.fd)
            elif is_int(arg, self.binding_data) or is_boolean(arg):
                parse_tuple_format.append('l')
                parse_tuple_args.append('&%s' % arg_name(arg))
                print('    %s %s;' % ('long', arg_name(arg)), file=self.fd)
            elif is_time_t_pointer(arg):
                parse_tuple_format.append('l')
                parse_tuple_args.append('&%s' % (arg_name(arg),))
                print >>self.fd,  '    time_t %s = 0;' % (arg_name(arg),)
            elif is_xml_node(arg):
                parse_tuple_format.append('s!')
                parse_tuple_args.append('&%s_str, &%s_len' % (arg_name(arg), arg_name(arg)))
                print('    %s %s = NULL;' % ('xmlNode*', arg_name(arg)), file=self.fd)
                print('    %s %s_str = NULL;'  % ('char*', arg_name(arg)), file=self.fd)
                print('    %s %s_len = 0;' % ('size_t', arg_name(arg)), file=self.fd)
            elif is_glist(arg):
                parse_tuple_format.append('a!')
                parse_tuple_args.append('&zval_%s' % arg_name(arg))
                print('    %s zval_%s = NULL;' % ('zval*', arg_name(arg)), file=self.fd)
                print('    %s %s = NULL;' % ('GList*', arg_name(arg)), file=self.fd)
            elif is_object(arg):
                parse_tuple_format.append('r')
                parse_tuple_args.append('&zval_%s' % arg_name(arg))
                print('    %s %s = NULL;' % (arg_type(arg), arg_name(arg)), file=self.fd)
                print('    %s zval_%s = NULL;' % ('zval*', arg_name(arg)), file=self.fd)
                print('    %s cvt_%s = NULL;' % ('PhpGObjectPtr*', arg_name(arg)), file=self.fd)
            else:
                raise Exception('Unsupported type %s %s' % (arg, m))

        if m.return_type:
            print('    %s return_c_value;' % m.return_type, file=self.fd)
        if m.return_type is not None and self.is_object(m.return_arg):
            print('    G_GNUC_UNUSED PhpGObjectPtr *self;', file=self.fd)
        print('', file=self.fd)

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

        print('''\
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "%s"%s) == FAILURE) {
        RETURN_FALSE;
    }
''' % (''.join(parse_tuple_format), parse_tuple_args), file=self.fd)

        for f, arg in zip(parse_tuple_format, m.args):
            if is_out(arg):
                continue
            elif is_xml_node(arg):
                print('''\
        %(name)s = get_xml_node_from_string(%(name)s_str);''' % {'name': arg[1]}, file=self.fd)
            elif f.startswith('s'):
                print('''\
        %(name)s = %(name)s_str;''' % {'name': arg[1]}, file=self.fd)
            elif f.startswith('r'):
                print('    if ((cvt_%s = (PhpGObjectPtr *)zend_fetch_resource(Z_RES_P(zval_%s), PHP_LASSO_SERVER_RES_NAME, le_lasso_server)) == NULL) {' % (arg[1], arg[1]), file=self.fd)
                print('        RETURN_FALSE;', file=self.fd)
                print('    }', file=self.fd)
                print('    %s = (%s)cvt_%s->obj;' % (arg[1], arg[0], arg[1]), file=self.fd)
            elif f.startswith('a'):
                el_type = element_type(arg)
                if is_cstring(el_type):
                    print('    %(name)s = get_list_from_array_of_strings(zval_%(name)s);' % {'name': arg[1]}, file=self.fd)
                elif is_object(el_type):
                    print('    %(name)s = get_list_from_array_of_objects(zval_%(name)s);' % {'name': arg[1]}, file=self.fd)
                else:
                    print('E: In %(function)s arg %(name)s is of type GList<%(elem)s>' % { 'function': m.name, 'name': arg[1], 'elem': el_type }, file=sys.stderr)
            elif f == 'l':
                pass
            else:
                raise Exception('%s format inconnu' % f)


        if m.return_type is not None:
            print('    return_c_value = ', file=self.fd)
            if 'new' in m.name:
                print('(%s)' % m.return_type, file=self.fd)
        else:
            print('   ', file=self.fd)
        def special(x):
            if is_time_t_pointer(x):
                return '%(name)s ? &%(name)s : NULL' % { 'name': arg_name(x) }
            else:
                return ref_name(x)
        print('%s(%s);' % (m.name, ', '.join([special(x) for x in m.args])), file=self.fd)
        # Free the converted arguments

        for f, arg in zip(parse_tuple_format, m.args):
            argtype, argname, argoptions = arg
            if is_out(arg):
                # export the returned variable
                free = is_transfer_full(unref_type(arg))
                self.set_zval('php_out_%s' % argname, argname, unref_type(arg), free = free)
                pass
            elif argtype == 'xmlNode*':
                print('    xmlFree(%s);' % argname, file=self.fd)
            elif f.startswith('a'):
                el_type = element_type(arg)
                if is_cstring(el_type):
                    print('    if (%(name)s) {' % { 'name': arg[1] }, file=self.fd)
                    print('        free_glist(&%(name)s,(GFunc)free);' % { 'name': arg[1] }, file=self.fd)
                    print('    }', file=self.fd)

        try:
            self.return_value(m.return_arg, is_transfer_full(m.return_arg, default=True))
        except:
            raise Exception('Cannot return value for function %s' % m)

        print('}', file=self.fd)
        print('', file=self.fd)

    def generate_members(self, c):
        for m in c.members:
            self.generate_getter(c, m)
            self.generate_setter(c, m)

    def generate_getter(self, c, m):
        klassname = c.name
        name = arg_name(m)
        type = arg_type(m)

        function_name = '%s_%s_get' % (klassname, format_as_camelcase(name))
        print('''PHP_FUNCTION(%s)
{''' % function_name, file=self.fd)
        self.functions_list.append(function_name)

        print('    %s return_c_value;' % type, file=self.fd)
        print('    %s* this;' % klassname, file=self.fd)
        print('    zval* zval_this;', file=self.fd)
        print('    PhpGObjectPtr *cvt_this;', file=self.fd)
        print('', file=self.fd)
        print('''\
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zval_this) == FAILURE) {
        RETURN_FALSE;
    }

    if ((cvt_this = (PhpGObjectPtr *)zend_fetch_resource(Z_RES_P(zval_this), PHP_LASSO_SERVER_RES_NAME, le_lasso_server)) == NULL) {
        RETURN_FALSE;
    }
    this = (%s*)cvt_this->obj;
''' % (klassname), file=self.fd)
        print('    return_c_value = (%s)this->%s;' % (type, name), file=self.fd)
        self.return_value(m)
        print('}', file=self.fd)
        print('', file=self.fd)

    def generate_setter(self, c, m):
        klassname = c.name
        name = arg_name(m)
        type = arg_type(m)
        function_name = '%s_%s_set' % (klassname, format_as_camelcase(name))
        print('''PHP_FUNCTION(%s)
{''' % function_name, file=self.fd)
        self.functions_list.append(function_name)

        print('    %s* this;' % klassname, file=self.fd)
        print('    zval* zval_this;', file=self.fd)
        print('    PhpGObjectPtr *cvt_this;', file=self.fd)

        # FIXME: This bloc should be factorised
        parse_tuple_format = ''
        parse_tuple_args = []
        if is_cstring(m) or is_xml_node(m):
            # arg_type = arg_type.replace('const ', '')
            parse_tuple_format += 's'
            parse_tuple_args.append('&%s_str, &%s_len' % (name, name))
            print('    %s %s_str = NULL;' % ('char*', name), file=self.fd)
            print('    %s %s_len = 0;' % ('size_t', name), file=self.fd)
        elif is_int(m, self.binding_data) or is_boolean(m):
            parse_tuple_format += 'l'
            parse_tuple_args.append('&%s' % name)
            print('    %s %s;' % ('long', name), file=self.fd)
        # Must also handle lists of Objects
        elif is_glist(m) or is_hashtable(m):
            parse_tuple_format += 'a'
            parse_tuple_args.append('&zval_%s' % name)
            print('    %s zval_%s;' % ('zval*', name), file=self.fd)
        elif is_object(m):
            parse_tuple_format += 'r'
            parse_tuple_args.append('&zval_%s' % name)
            print('    %s zval_%s = NULL;' % ('zval*', name), file=self.fd)
            print('    %s cvt_%s = NULL;' % ('PhpGObjectPtr*', name), file=self.fd)
        else:
            raise Exception('Cannot make a setter for %s.%s' % (c,m))

        if parse_tuple_args:
            parse_tuple_arg = parse_tuple_args[0]
        else:
            print('}', file=self.fd)
            print('', file=self.fd)
            return

        print('', file=self.fd)
        print('''\
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r%s", &zval_this, %s) == FAILURE) {
        return;
    }
''' % (parse_tuple_format, parse_tuple_arg), file=self.fd)

        # Get 'this' object
        print('''\
    if ((cvt_this = (PhpGObjectPtr *)zend_fetch_resource(Z_RES_P(zval_this), PHP_LASSO_SERVER_RES_NAME, le_lasso_server)) == NULL) {
        RETURN_FALSE;
    }
    this = (%s*)cvt_this->obj;
''' % klassname, file=self.fd)

        # Set new value
        d = { 'name': name, 'type': type }
        if is_int(m, self.binding_data) or is_boolean(m):
            print('    this->%s = %s;' % (name, name), file=self.fd)
        elif is_cstring(m):
            print('    lasso_assign_string(this->%(name)s, %(name)s_str);' % d, file=self.fd)
        elif is_xml_node(m):
            print('    lasso_assign_new_xml_node(this->%(name)s, get_xml_node_from_string(%(name)s_str));' % d, file=self.fd)
        elif is_glist(m):
            el_type = element_type(m)
            if is_cstring(el_type):
                print('    lasso_assign_new_list_of_strings(this->%(name)s, get_list_from_array_of_strings(zval_%(name)s));' % d, file=self.fd)
            elif is_xml_node(el_type):
                print('    lasso_assign_new_list_of_xml_node(this->%(name)s, get_list_from_array_of_xmlnodes(zval_%(name)s))' % d, file=self.fd)
            elif is_object(el_type):
                print('    lasso_assign_new_list_of_gobjects(this->%(name)s, get_list_from_array_of_objects(zval_%(name)s));' % d, file=self.fd)
            else:
                raise Exception('Cannot create C setter for %s.%s' % (c,m))
        elif is_hashtable(m):
            el_type = element_type(m)
            print('''\
        {
            GHashTable *oldhash = this->%(name)s;''' % d, file=self.fd)
            if is_object(el_type):
                print('            this->%(name)s = get_hashtable_from_array_of_objects(zval_%(name)s);' % d, file=self.fd)
            else:
                print('            this->%(name)s = get_hashtable_from_array_of_strings(zval_%(name)s);' % d, file=self.fd)
            print('            g_hash_table_destroy(oldhash);', file=self.fd)
            print('        }', file=self.fd)
        elif is_object(m):
            print('    if ((cvt_%(name)s = (PhpGObjectPtr*)zend_fetch_resource(Z_RES_P(zval_%(name)s), PHP_LASSO_SERVER_RES_NAME, le_lasso_server)) == NULL) {' % d, file=self.fd)
            print('        RETURN_FALSE;', file=self.fd)
            print('    }', file=self.fd)
            print('    lasso_assign_gobject(this->%(name)s, cvt_%(name)s->obj);' % d, file=self.fd)

        print('}', file=self.fd)
        print('', file=self.fd)

    def generate_functions_list(self):
        print('''\
static zend_function_entry lasso_functions[] = {''', file=self.fd)
        for m in self.functions_list:
            print('    PHP_FE(%s, NULL)' % m, file=self.fd)
        print('''\
    {NULL, NULL, NULL, 0, 0}
};
''', file=self.fd)

    def generate_footer(self):
        print('''\
zend_module_entry lasso_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_LASSO_EXTNAME,
    lasso_functions,
    PHP_MINIT(lasso),
    PHP_MSHUTDOWN(lasso),
    NULL,
    NULL,
    NULL,
#if ZEND_MODULE_API_NO >= 20010901
    PHP_LASSO_VERSION,
#endif
    STANDARD_MODULE_PROPERTIES
};
''', file=self.fd)

