# 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 sys
import os
import utils

module_file = '_lasso.c'
header_file = '_lasso.h'
php_file = 'lasso.php'

def zval(name):
    return 'zval_' + name

class Binding:
    def __init__(self, binding_data):
        self.binding_data = binding_data
        self.module_fd = open(module_file, 'w')
        self.header_fd = open(header_file, 'w')
        self.php_fd = open(php_file, 'w')
        self.functions_list = list()
        self.shiftcount = 0

    def __del__(self):
        close(self.module_fd)
        close(self.header_fd)
        close(self.php_fd)

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

    def error(self,str):
        print >> sys.stderr, 'E: %s' % str

    def warning(self,str):
        print >> sys.stderr, 'W: %s' % str

    def printshift(self,fd):
        if self.shiftcount:
            print >> fd, (self.shiftcount-1)*' ',

    def shift(self):
        self.shiftcount += 4

    def unshift(self):
        self.shiftcount -= 4

    def module(self,str):
        self.printshift(self.module_fd)
        print >> self.module_fd, str

    def php(self,str):
        self.printshift(self.php_fd)
        print >> self.php_fd, str

    def header(self,str):
        self.printshift(self.header_fd)
        print >> self.header_fd, str

    def define(self,name,value):
        self.header('#define %s %s' % (name,value))

    def include(self, str):
        self.module('#include "%s"' % str)

    def function(self, function_name):
        self.functions_list.append(function_name)
        self.module('PHP_FUNCTION(%s)' % function_name)

    def open(self):
        self.module('{')
        self.shift()

    def close(self):
        self.unshift()
        self.module('}')

    def ret(self, value):
        self.module('return %s;' % value)

    def success(self):
        self.ret('SUCCESS')

    def declare_zval(self, name):
        self.declare('zval*', zval(name))

    def declare(self, type, name):
        self.module('%s %s;' % (type, name))

    def comment(self, str):
        self.module('/* %s */' % str)

    def free(self,name):
        self.module('free(%s);' % name)

    def get_php_list(self, name, element_type):
        self.comment(' Get php list %s of type %s from %s ' % (name, element_type, zval(name)))


    def get_php_hashtable(self, name, element_type):
        self.comment(' Get php hashtable %s of type %s from %s ' % (name, element_type, zval(name)))

    def get_object(self, name, type):
        self.comment(' Get object %s of type %s from php resource %s ' % (name, type, zval(name)))

    def parse_format_arg(self, arg):
        type, name, options = arg
        ret = ''
        if type == 'gboolean':
            ret =  'b'
            return ret
        elif type in ['int', 'gint'] + self.binding_data.enums:
            ret =  'l'
            return ret
        elif type in ('char*', 'gchar*','const char*','const gchar*'):
            ret =  's'
        elif type == 'xmlNode*':
            ret =  's'
        elif type == 'GList*':
            ret =  'a'
        elif type == 'GHashTable*':
            ret =  'a'
        elif self.is_object(type):
            ret =  'r'
        else:
            self.error('%s of type %s has no format string' % (name, type))
        if options.get('nonull') == None and ret:
            ret += '!'
        return ret

    def generate_parse_args(self, arg):
        type, name, options = arg
        ret = ''
        if type == 'gboolean' or type in ['int', 'gint'] + self.binding_data.enums:
            self.declare(type, name)
            return list(name)
        elif type in ('char*', 'gchar*','const char*','const gchar*'):
            self.declare('char*', name)
            self.declare('int', name + '_len')
            return (name, name + '_len')
        elif type == 'xmlNode*':
            self.declare('char*', name + '_str')
            self.declare('int', name + '_len')
            self.declare('xmlNode*', name)
            return (name + '_str', name + '_len')
        elif type == 'GList*' or type == 'GHashTable*' or self.is_object(type):
            self.declare_zval(name)
            self.declare(type, name)
            return list(zval(name))
        else:
            self.error('%s of type %s has no format arg' % (name, type))
        return list()

    def method_prologue(self, klass, name, args = list()):
        self.function(name)
        self.open()
        self.declare('int', 'zpp_ret')
        self.declare(klass.name + '*', 'a_gobject')
        this = zval('this')
        self.declare_zval('this')
        # this should not be null
        parse_format = 'r'
        parse_args = list(this)
        for arg in args:
            type, name, options = arg
            # Create parse_format
            parse_format += self.parse_format_char(arg)
            # Allocate variables
            parse_args = self.generate_parse_args(arg)
        # Call parse parameters
        self.module('zpp_ret = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "%s", %s)' % (parse_format, ','.join([ "&" + x for x in parse_args])))
        self.module('if (zpp_ret == Failure)')
        self.open()
        self.module('RETURN_NULL();');
        self.close()
        # Convert the resource
        self.module('ZEND_FETCH_RESOURCE(a_gobject, %s*, &%s, -1, PHP_LASSO_GOBJECT_RES_NAME, le_lasso_gobject);')
        self.module('if (a_gobject == NULL)')
        self.open()
        self.module('zend_error(E_WARNING, "Calling a method on a null GObject resource, big problem");')
        self.module('RETURN_NULL();');
        self.close()
        for arg in args:
            type, name, options = arg
            if type == 'gboolean' and type in ['int', 'gint'] + self.binding_data.enums and type in ('char*', 'gchar*','const char*','const gchar*'):
                pass
            elif type == 'xmlNode*':
                self.module('get_xml_node_from_string(&%(name)s,%(name)s_str);' % { 'name': name })
            elif type == 'GList*':
                self.get_php_list(name, options.get('element_type'))
            elif type == 'GHashTable*':
                self.get_php_hashtable(name, options.get('element_type'))
            elif self.is_object(type):
                self.get_object(name, type)

    def method_epilogue(self, args = list()):
        for arg in args:
            type, name, options = arg
            type, name, options = arg
            if type == 'gboolean' and type in ['int', 'gint'] + self.binding_data.enums and type in ('char*', 'gchar*','const char*','const gchar*'):
                pass
            elif type == 'xmlNode*':
                self.free_xmldoc(name)
            elif type == 'GList*':
                self.free_glist(name, options.get('element_type'))
            elif type == 'GHashTable*':
                self.free_hashtable(name, options.get('element_type'))
            elif self.is_object(type):
                pass
            else:
                self.error('Bizarre type %s' % type)
        self.close()

    def generate_module_dummy_function(self, name):
        self.module(name)
        self.open()
        self.success()
        self.close()
# Type helpers
    def update_c_value(self, left, right, type, options, free = True, copy = False):
        if type == 'gboolean' and type in ['int', 'gint'] + self.binding_data.enums:
            self.module('%s = %s;' % (left, right))
            return

        if free:
            self.module('if (%s)' % left)
            self.open()
            self.free(left, type, options)
            self.close()
        pat = ''
        if type in ('char*','gchar*') and copy:
            pat = '%s = g_strdup(%s);'
        else:
            par = '%s = %s;'
        self.module(pat % (left, right))

    def return_c_value(self, type, name, options = dict()):
        if type is None:
            return

        if type == 'gboolean':
            self.module('RETVAL_BOOL(%s);' % name)
            return
        elif type in ['int', 'gint'] + self.binding_data.enums:
            self.module('RETVAL_LONG(%s);' % name)
            return
        # Pointer types
        self.module('if (%s)' % name)
        self.open()
        if type in ('char*', 'gchar*','const char*','const gchar*'):
            if options.get('eallocated') == 1:
                self.module('RETVAL_STRING(%s,0);' % name)
            else:
                self.module('RETVAL_STRING(%s,1);' % name)
        elif type == 'xmlNode*':
            self.open()
            self.declare('char*','xmlString')
            self.module('xmlString = get_string_from_xml_node(%s);' % name)
            self.return_c_value('char*','xmlString', { 'eallocated': 1 })
            self.close()
        elif type == 'GList*':
            pass
        elif type == 'GHashTable*':
            pass
        elif self.is_object(type):
            self.module('ZVAL_REGISTER_RESOURCE(return_value, %s, le_lasso_gobject);' % name)
        else:
            self.error('%s of type %s is not returnable' % (name, type))
        self.close()
        self.module('else')
        self.open()
        self.module('RETVAL_NULL();')
        self.close()
#

    def generate(self):
        self.generate_php()
        self.generate_module()
        # Must be called last because generate_module
        # compute self.functions_lists
        # that is needed by generate_header_functions_list
        self.generate_header()

# Generation of the C header
    # Generate the lasso.h file
    def generate_header(self):
        self.header('/* this file has been generated automatically; do not edit */')
        self.header('#ifndef PHP_LASSO_H')
        self.header('#define PHP_LASSO_H 1')
        self.header('')
        self.define('PHP_LASSO_EXTNAME', '"lasso"')
        self.define('PHP_LASSO_VERSION', '"2.1.1"')
        self.define('PHP_LASSO_GOBJECT_RES_NAME', 'LassoGObject')
        self.header('PHP_MINIT_FUNCTION(lasso);')
        self.header('PHP_MSHUTDOWN_FUNCTION(lasso);')
        self.header('PHP_RINIT_FUNCTION(lasso);')
        self.generate_header_functions_list()
        self.generate_header_globals()
        # Declare GObject resource destructor
        self.header('static void php_lasso_gobject_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC);')
        self.header('extern zend_module_entry lasso_module_entry;')
        self.define('phpext_lasso_ptr','&lasso_module_entry')
        self.header('#endif')

    def generate_header_functions_list(self):
        for m in self.functions_list:
            self.header('PHP_FUNCTION(%s);' % m)
        self.header('')

    def generate_header_globals(self):
        self.header('#ifdef ZTS')
        self.header('#include "TSRM.h"')
        self.header('#endif')
        self.header('')
        self.header('ZEND_BEGIN_MODULE_GLOBALS(lasso)')
        self.header('ZEND_END_MODULE_GLOBALS(lasso)')
        self.header('')
        self.header('#ifdef ZTS')
        self.header('#define LASSO_G(v) TSRMG(lasso_globals_id, zend_lasso_globals *, v)')
        self.header('#else')
        self.header('#define LASSO_G(v) (lasso_globals.v)')
        self.header('#endif')

# Generation of the C module
    def generate_module(self):
        self.generate_module_top()
        self.generate_module_minit()
        self.generate_module_gobject_resource_dtor()
        self.generate_functions()
        self.generate_getters_setters()
        self.generate_function_declarations()
        self.generate_module_entry()
        self.generate_module_php_ini()

    def generate_module_top(self):
        self.include('php.h')
        self.include('php_ini.h')
        self.include(header_file)
        self.include('lasso_php4_helper.c')
        self.module('ZEND_DECLARE_MODULE_GLOBALS(lasso)')
        self.module('')
        self.module('#if COMPILE_DL_LASSO')
        self.module('ZEND_GET_MODULE(lasso) ')
        self.module('#endif')
        self.module('/* GObject resource type */')
        self.module('int le_lasso_gobject');

    def generate_module_entry(self):
        self.module('zend_module_entry lasso_module_entry = {')
        self.module('#if ZEND_MODULE_API_NO >= 20010901')
        self.module('    STANDARD_MODULE_HEADER,')
        self.module('#endif')
        self.module('    PHP_HELLO_WORLD_EXTNAME,')
        self.module('    lasso_functions,')
        self.module('    PHP_MINIT(lasso),')
        self.module('    PHP_MSHUTDOWN(lasso),')
        self.module('    PHP_RINIT(lasso), /* Request init function */')
        self.module('    PHP_RSHUTDOWN(lasso) , /* Request shutdown function/ ')
        self.module('    NULL, /* PHP info function */')
        self.module('#if ZEND_MODULE_API_NO >= 20010901')
        self.module('    PHP_HELLO_WORLD_VERSION,')
        self.module('#endif')
        self.module('    STANDARD_MODULE_PROPERTIES')
        self.module('};')

    def generate_module_gobject_resource_dtor(self):
        self.module('static void php_lasso_gobject_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {')
        self.shift()
        self.module('GObject *a_gobject;')
        self.module('g_object_unref((GObject*)rsrc->ptr);')
        self.unshift()
        self.module('}')

    def generate_module_php_ini(self):
        self.module('PHP_INI_BEGIN()')
        self.shift()
        # self.module('PHP_INI_ENTRY("lasso.greeting", "Hello World", PHP_INI_ALL, NULL)')
        # self.module('STD_PHP_INI_ENTRY("lasso.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_lasso_globals, lasso_globals)')
        self.unshift()
        self.module('PHP_INI_END()')

    def generate_module_minit(self):
        self.module('PHP_MINIT(lasso)')
        self.open()
        self.generate_module_minit_constants()
        # Init globals
        # self.module('LASSO_G(autocoin) = 1;')
        self.generate_module_minit_register_resources()
        self.success()
        self.close()

    def generate_module_minit_constants(self):
        self.module('/* Constants (both enums and defines) */')
        mapping = {
            'i': 'REGISTER_LONG_CONSTANT("%(name)s", %(name)s, CONST_CS|CONST_PERSISTENT);',
            's': 'REGISTER_STRING_CONSTANT("%(name)s", %(name)s, CONST_CS|CONST_PERSISTENT);',
            'b': '''\
#ifdef %(name)s
    REGISTER_LONG_CONSTANT("%(name)s", 1, CONST_CS|CONST_PERSISTENT);
#else
    REGISTER_LONG_CONSTANT("%(name)s", 0, CONST_CS|CONST_PERSISTENT);
#endif''' }
        for c in self.binding_data.constants:
            constant_type, constant_name = c
            if constant_type in mapping:
                self.module(mapping[constant_type] % { 'name': constant_name } )
            else:
                self.error('unknown constant type "%s"' % constant_type)


    def generate_module_minit_register_resources(self):
        self.module('le_lasso_gobject = zend_register_list_destructors_ex(php_lasso_gobject_dtor, NULL, PHP_LASSO_GOBJECT_RES_NAME, module_number);')


    def generate_module_mshutdown(self):
        self.generate_module_dummy_function('PHP_MSHUTDOWN(lasso)')

    def generate_module_rinit(self):
        self.generate_module_dummy_function('PHP_RINIT(lasso)')

    def generate_module_rshutdown(self):
        self.generate_module_dummy_function('PHP_RSHUTDOWN(lasso)')

    def generate_getters_setters(self):
        for klass in self.binding_data.structs:
            for member in klass.members:
                self.generate_getter(klass, member)
                self.generate_getter(klass, member)

    def function_name(self, klass, member, suffix):
        type, name, options = member
        return '%s_%s_%s' % (klass.name, utils.format_as_camelcase(name), suffix)
    def getter_function_name(self, klass, member):
        return self.function_name(klass, member, 'get')
    def setter_function_name(self, klass, member):
        return self.function_name(klass, member, 'set')
    def generate_getter(self, klass, member):
        type, name, options = member
        elem_type = options.get('elem_type')
        function_name = self.getter_function_name(klass, member)
        self.method_prologue(klass, function_name)
        self.return_c_value(type, 'a_gobject->' + name, options)
        self.method_epilogue()

    def generate_setter(self, klass, member):
        type, name, options = member
        elem_type = options.get('elem_type')
        function_name = self.setter_function_name(klass, member)
        self.method_prologue(klass, function_name, list(member))
        # Affectation: a_gobject->name = name
        self.update_c_value('a_gobject->' + name, name, type, options, True, True)
        self.method_epilogue()

    def generate_functions(self):
        pass

    def generate_function(self):
        pass

    def generate_function_arg_decl(self):
        pass

    def generate_function_call(self):
        pass

    def generate_function_arg_free(self):
        pass

    def genreate_function_return(self):
        pass

    def generate_function_declarations(self):
        pass

    def generate_function_declaration(self):
        pass

# Generate of the PHP file
    def generate_php(self):
        pass

    def generate_php_objects(self):
        pass

    def generate_php_getters_setters(self):
        pass

    def generate_methods(self):
        pass

    def generate_exception_classes(self):
        pass

    def generate_exception_class(self):
        pass

