import os
import string
import scmexpr
try:
        from cStringIO import StringIO
except ImportError:
        from StringIO import StringIO
# for fnmatchcase -- filename globbing
import fnmatch

TRUE  = 1
FALSE = 0

funcDefTmpl = '    { "%s", _wrap_%s, 1 },\n'
funcLeadTmpl = 'static PyObject *_wrap_%s(PyObject *self, PyObject *args) {\n'
enumCodeTmpl = '''    if (PyGtkEnum_get_value(%s, %s, (gint *)&%s))
        return NULL;\n'''
flagCodeTmpl = '''    if (PyGtkFlag_get_value(%s, %s, (gint *)&%s))
        return NULL;\n'''
setVarTmpl = '''  if (py_%s)
        %s = %s;\n'''
getTypeTmpl = '''static PyObject *_wrap_%s(PyObject *self, PyObject *args) {
    if (!PyArg_ParseTuple(args, ":%s"))
        return NULL;
    return PyInt_FromLong(%s());
}\n\n'''

nullokTmpl = '''    if (PyGtk_Check(py_%s))
        %s = %s(PyGtk_Get(py_%s));
    else if (py_%s != Py_None) {
        PyErr_SetString(PyExc_TypeError, "%s argument must be a %s or None");
        return NULL;
    }\n'''

nullokBoxedTmpl = '''    if (Py%s_Check(py_%s))
        %s = %s(py_%s);
    else if (py_%s != Py_None) {
        PyErr_SetString(PyExc_TypeError, "%s argument must be a %s or None");
        return NULL;
    }\n'''

convSpecialCases = {
        'GtkCList': '_GTK_CLIST',
        'GtkCTree': '_GTK_CTREE',
        'GtkCTreePos': '_GTK_CTREE_POS',
        'GtkCTreeLineStyle': '_GTK_CTREE_LINE_STYLE',
        'GtkCTreeExpanderStyle': '_GTK_CTREE_EXPANDER_STYLE',
        'GtkCTreeExpansionStyle': '_GTK_CTREE_EXPANSION_STYLE',
        'GnomeRootWin': '_GNOME_ROOTWIN',
        'GnomeAppBar': '_GNOME_APPBAR',
        'GnomeDEntryEdit': '_GNOME_DENTRY_EDIT',
}

def toUpperStr(str):
        """Converts a typename to the equivalent upercase and underscores
        name.  This is used to form the type conversion macros and enum/flag
        name variables"""
        if convSpecialCases.has_key(str):
                return convSpecialCases[str]
        ret = []
        while str:
                if len(str) > 1 and str[0] in string.uppercase and \
                   str[1] in string.lowercase:
                        ret.append("_" + str[:2])
                        str = str[2:]
                elif len(str) > 3 and \
                     str[0] in string.lowercase and \
                     str[1] in 'HV' and \
                     str[2] in string.uppercase and \
                     str[3] in string.lowercase:
                        ret.append(str[0] + "_" + str[1:3])
                        str = str[3:]
                elif len(str) > 2 and \
                     str[0] in string.lowercase and \
                     str[1] in string.uppercase and \
                     str[2] in string.uppercase:
                        ret.append(str[0] + "_" + str[1])
                        str = str[2:]
                else:
                        ret.append(str[0])
                        str = str[1:]
        return string.upper(string.join(ret, ''))

def enumName(typename):
        """create a GTK_TYPE_* name from the given type"""
        part = toUpperStr(typename)
        if len(part) > 4 and part[:4] == '_GTK':
                return 'GTK_TYPE' + part[4:]
        else:
                return 'GTK_TYPE' + part

def conversionMacro(typename):
        """get the conversion macro for the given type"""
        return toUpperStr(typename)[1:]

# a dictionary of known enum types ...
# the value is a cached copy of the GTK_TYPE name
enums = {}
# and flags ...
flags = {}

# a dictionary of objects ...
# the values are the conversion macros
objects = {}

# the known boxed types
boxed = {
        'GtkAccelGroup': ('PyGtkAccelGroup_Type', 'PyGtkAccelGroup_Get',
                          'PyGtkAccelGroup_New'),
        'GtkStyle': ('PyGtkStyle_Type', 'PyGtkStyle_Get', 'PyGtkStyle_New'),
        'GdkFont': ('PyGdkFont_Type', 'PyGdkFont_Get', 'PyGdkFont_New'),
        'GdkColor': ('PyGdkColor_Type', 'PyGdkColor_Get', 'PyGdkColor_New'),
        'GdkEvent': ('PyGdkEvent_Type', 'PyGdkEvent_Get', 'PyGdkEvent_New'),
        'GdkWindow': ('PyGdkWindow_Type', 'PyGdkWindow_Get','PyGdkWindow_New'),
        'GdkPixmap': ('PyGdkWindow_Type', 'PyGdkWindow_Get','PyGdkWindow_New'),
        'GdkBitmap': ('PyGdkWindow_Type', 'PyGdkWindow_Get','PyGdkWindow_New'),
        'GdkDrawable':('PyGdkWindow_Type','PyGdkWindow_Get','PyGdkWindow_New'),
        'GdkGC': ('PyGdkGC_Type', 'PyGdkGC_Get', 'PyGdkGC_New'),
        'GdkColormap': ('PyGdkColormap_Type', 'PyGdkColormap_Get',
                        'PyGdkColormap_New'),
        'GdkDragContext': ('PyGdkDragContext_Type', 'PyGdkDragContext_Get',
                           'PyGdkDragContext_New'),
        'GtkSelectionData': ('PyGtkSelectionData_Type',
                             'PyGtkSelectionData_Get',
                             'PyGtkSelectionData_New'),
        'GdkAtom': ('PyGdkAtom_Type', 'PyGdkAtom_Get', 'PyGdkAtom_New'),
        'GdkCursor': ('PyGdkCursor_Type', 'PyGdkCursor_Get',
                      'PyGdkCursor_New'),
        'GtkCTreeNode': ('PyGtkCTreeNode_Type', 'PyGtkCTreeNode_Get',
                         'PyGtkCTreeNode_New'),
}

class VarDefs:
        def __init__(self):
                self.vars = {}
        def add(self, type, name):
                if self.vars.has_key(type):
                        self.vars[type] = self.vars[type] + (name,)
                else:
                        self.vars[type] = (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, '')

class TypesParser(scmexpr.Parser):
        """A parser that only parses definitions -- no output"""
        def define_enum(self, name, *values):
                if not enums.has_key(name):
                        enums[name] = enumName(name)
        def define_flags(self, name, *values):
                if not flags.has_key(name):
                        flags[name] = enumName(name)
        def define_object(self, name, *args):
                if not objects.has_key(name):
                        objects[name] = conversionMacro(name)
        def define_boxed(self, name, reffunc=None, unreffunc=None, size=None):
                if not boxed.has_key(name):
                        print "Warning --", name, "not supported"
        def include(self, filename):
                if filename[0] != '/':
                        # filename relative to file being parsed
                        afile = os.path.join(os.path.dirname(self.filename),
                                             filename)
                else:
                        afile = filename
                if not os.path.exists(afile):
                        # fallback on PWD relative filename
                        afile = filename
                #print "including file", afile
                fp = open(afile)
                self.startParsing(scmexpr.parse(fp))

class FunctionDefsParser(TypesParser):
        def __init__(self, input, prefix='gtkmodule', typeprefix=''):
                # typeprefix is set to & if type structs are not pointers
                TypesParser.__init__(self, input)
                self.impl = open(prefix + '_impl.c', "w")
                self.defs = open(prefix + '_defs.c', "w")
                self.tp = typeprefix

        def unknown(self, tup):
                print "Unknown function:", (tup and tup[0] or "")

        def declare_object(self, name, *args):
                # Just parse - no output
                TypesParser.define_object(self, name)

        def define_object(self, name, parent=(), fields=()):
                TypesParser.define_object(self, name)
                get_type = string.lower(objects[name]) + '_get_type'
                self.defs.write(funcDefTmpl % (get_type,get_type))
                self.impl.write(getTypeTmpl % (get_type,get_type,get_type))

                if fields and fields[0] == 'fields':
                        for retType, attrname in fields[1:]:
                                self.get_attr_func(name, retType, attrname)

        def get_attr_func(self, name, retType, attrname):
                impl = StringIO()
                funcName = string.lower(objects[name]) + '_get_' + attrname
                impl.write(funcLeadTmpl % (funcName,))
                impl.write('    PyObject *obj;\n\n')
                impl.write('    if (!PyArg_ParseTuple(args, "O!:')
                impl.write(funcName)
                impl.write('", ' + self.tp + 'PyGtk_Type, &obj))\n')
                impl.write('        return NULL;\n')
                funcCall = '%s(PyGtk_Get(obj))->%s' % (objects[name], attrname)
                if self.decodeRet(impl, funcCall, retType):
                        return
                impl.write('}\n\n')
                # actually write the info to the output files
                self.defs.write(funcDefTmpl % (funcName, funcName))
                self.impl.write(impl.getvalue())
                
        def define_func(self, name, retType, args):
                # we write to a temp file, in case any errors occur ...
                impl = StringIO()
                impl.write(funcLeadTmpl % (name,))
                varDefs = VarDefs()
                parseStr = ''   # PyArg_ParseTuple string
                parseList = []  # args to PyArg_ParseTuple
                argList = []    # args to actual function
                extraCode = []  # any extra code (for enums, flags)
                nullret = FALSE # return value could be null
                if type(retType) == type(()) and len(retType) == 2 \
                   and retType[0] in objects.keys() \
                   and retType[1] == 'null-ok':
                        nullret = TRUE
                        retType = retType[0]
                        varDefs.add('GtkObject', '*retval')
                if retType == 'string':
                        # this is needed so we can free result string
                        varDefs.add('char', '*ret')
                        varDefs.add('PyObject', '*py_ret')
                if retType == 'const_string':
                        varDefs.add('const char', '*ret')
                for arg in args:
                        argType = arg[0]
                        argName = arg[1]
                        # munge special names ...
                        if argName in ('default', 'if', 'then', 'else',
                                       'while', 'for', 'do', 'case', 'select'):
                                argName = '_' + argName
                        default = ''
                        nullok = FALSE
                        if len(arg) > 2:
                                for a in arg[2:]:
                                        if type(a) == type(()) and a[0] == '=':
                                                default = ' = ' + a[1]
                                                if '|' not in parseStr:
                                                        parseStr = parseStr+'|'
                                        elif type(a) == type(()) and \
                                             a[0] == 'null-ok':
                                                nullok = TRUE
                        if argType == 'string':
                                varDefs.add('char', '*' + argName + default)
                                if nullok:
                                        parseStr = parseStr + 'z'
                                else:
                                        parseStr = parseStr + 's'
                                parseList.append('&' + argName)
                                argList.append(argName)
                        elif argType == 'const_string':
                                varDefs.add('const char', '*' + argName + default)
                                if nullok:
                                        parseStr = parseStr + 'z'
                                else:
                                        parseStr = parseStr + 's'
                                parseList.append('&' + argName)
                                argList.append(argName)
                        elif argType in ('char', 'uchar'):
                                varDefs.add('char', argName + default)
                                parseStr = parseStr + 'c'
                                parseList.append('&' + argName)
                                argList.append(argName)
                        elif argType in ('bool', 'int', 'uint', 'long',
                                         'ulong', 'guint', 'GdkAtom'):
                                # pretend atoms are integers for input ...
                                varDefs.add('int', argName + default)
                                parseStr = parseStr + 'i'
                                parseList.append('&' + argName)
                                argList.append(argName)
                        elif argType in ('float', 'double'):
                                varDefs.add('double', argName + default)
                                parseStr = parseStr + 'd'
                                parseList.append('&' + argName)
                                argList.append(argName)
                        elif argType == 'progress_cb':
                                varDefs.add('PyProgressData', 'sProgressInfo = {NULL,NULL,-1}')
                                parseStr = parseStr + 'O'
                                parseList.append('&(sProgressInfo.psPyCallback)')
                                argList.append('PyProgressProxy')
                        elif argType == 'progress_data':
                                parseStr = parseStr + 'O'
                                parseList.append('&(sProgressInfo.psPyCallbackData)')
                                argList.append('&sProgressInfo')
                        elif argType == 'FILE':
                                varDefs.add('PyObject', '*' + argName+default)
                                parseStr = parseStr + 'O!'
                                parseList.append('&PyFile_Type')
                                parseList.append('&' + argName)
                                argList.append('PyFile_AsFile(' +
                                               argName + ')')
                        elif argType in enums.keys():
                                varDefs.add(argType, argName + default)
                                if default:
                                        varDefs.add('PyObject', '*py_' + \
                                                    argName + ' = NULL')
                                else:
                                        varDefs.add('PyObject', '*py_'+argName)
                                parseStr = parseStr + 'O'
                                parseList.append('&py_' + argName)
                                argList.append(argName)
                                extraCode.append(enumCodeTmpl %
                                                 (enums[argType],
                                                  'py_' + argName,
                                                  argName))
                        elif argType in flags.keys():
                                varDefs.add(argType, argName + default)
                                if default:
                                        varDefs.add('PyObject', '*py_' + \
                                                    argName + ' = NULL')
                                else:
                                        varDefs.add('PyObject', '*py_'+argName)
                                parseStr = parseStr + 'O'
                                parseList.append('&py_' + argName)
                                argList.append(argName)
                                extraCode.append(flagCodeTmpl %
                                                 (flags[argType],
                                                  'py_' + argName,
                                                  argName))
                        elif argType in objects.keys():
                                if nullok:
                                        parseStr = parseStr + 'O'
                                        parseList.append('&py_' + argName)
                                        varDefs.add('PyObject', '*py_' +
                                                    argName + ' = Py_None')
                                        varDefs.add(argType, '*' + argName +
                                                    ' = NULL')
                                        extraCode.append(nullokTmpl %
                                                         (argName, argName,
                                                          objects[argType],
                                                          argName, argName,
                                                          argName, argType))
                                        argList.append(argName)
                                elif default:
                                        parseStr = parseStr + 'O!'
                                        parseList.append(self.tp +'PyGtk_Type')
                                        varDefs.add('PyObject', '*py_' +
                                                    argName + ' = NULL')
                                        varDefs.add(argType, '*' + argName +
                                                    default)
                                        parseList.append('&py_' + argName)
                                        extraCode.append(setVarTmpl %
                                                         (argName, argName,
                                                          objects[argType] +
                                                          '(PyGtk_Get(py_' +
                                                          argName + '))'))
                                        argList.append(argName)
                                else:
                                        parseStr = parseStr + 'O!'
                                        parseList.append(self.tp +'PyGtk_Type')
                                        varDefs.add('PyObject', '*' + argName)
                                        parseList.append('&' + argName)
                                        argList.append(objects[argType] +
                                                       '(PyGtk_Get(' +
                                                       argName + '))')
                        elif argType in boxed.keys():
                                tp, get, new = boxed[argType]
                                if nullok:
                                        varDefs.add('PyObject', '*py_' +
                                                    argName + ' = Py_None')
                                        varDefs.add(argType, '*' + argName +
                                                    ' = NULL')
                                        parseStr = parseStr + 'O'
                                        parseList.append('&py_' + argName)
                                        extraCode.append(nullokBoxedTmpl %
                                                         (argType, argName,
                                                          argName, get,
                                                          argName, argName,
                                                          argName, argType))
                                        argList.append(argName)
                                else:
                                        varDefs.add('PyObject', '*' + argName)
                                        parseStr = parseStr + 'O!'
                                        parseList.append(self.tp + tp)
                                        parseList.append('&' + argName)
                                        argList.append(get + '('+argName+')')
                        else:
                                print "%s: unknown arg type '%s'" % (name,
                                                                     argType)
                                return
                                
                impl.write(str(varDefs))
                impl.write('    if (!PyArg_ParseTuple(args, "')
                impl.write(parseStr)
                impl.write(':')
                impl.write(name)
                impl.write('"')
                if parseList:
                        impl.write(', ')
                        impl.write(string.join(parseList, ', '))
                impl.write('))\n        return NULL;\n')
                impl.write(string.join(extraCode, ''))

                funcCall = name + '(' + string.join(argList, ', ') + ')'
                if self.decodeRet(impl, funcCall, retType, nullret):
                        return
                impl.write('}\n\n')
                # actually write the info to the output files
                self.defs.write(funcDefTmpl % (name,name))
                self.impl.write(impl.getvalue())

        def decodeRet(self, impl, funcCall, retType, nullret):
                if retType == 'none':
                        impl.write('    ')
                        impl.write(funcCall)
                        impl.write(';\n    Py_INCREF(Py_None);\n    return Py_None;\n')
                elif retType == 'static_string':
                        impl.write('    return PyString_FromString(')
                        impl.write(funcCall)
                        impl.write(');\n')
                elif retType == 'string':
                        impl.write('    ret = ')
                        impl.write(funcCall)
                        impl.write(';\n    py_ret = PyString_FromString(ret);\n  g_free(ret);\n  return py_ret;\n')
                elif retType == 'const_string':
                        impl.write('    ret = ')
                        impl.write(funcCall)
                        impl.write(';\n')
                        impl.write('    if( ret != NULL)\n')
                        impl.write('        return PyString_FromString(ret);\n')
                        impl.write('    else\n')
                        impl.write('    {\n')
                        impl.write('        Py_INCREF(Py_None);\n')
                        impl.write('        return Py_None;\n')
                        impl.write('    }\n')
                elif retType in ('char', 'uchar'):
                        impl.write('    return PyString_fromStringAndSize(*(')
                        impl.write(funcCall)
                        impl.write('));\n')
                elif retType in ('bool','int','uint','long','ulong','guint') \
                     or retType in enums.keys() or retType in flags.keys():
                        impl.write('    return PyInt_FromLong(')
                        impl.write(funcCall)
                        impl.write(');\n')
                elif retType in ('float','double'):
                        impl.write('    return PyFloat_FromDouble(')
                        impl.write(funcCall)
                        impl.write(');\n')
                elif retType in boxed.keys():
                        impl.write('    return ')
                        impl.write(boxed[retType][2])
                        impl.write('(')
                        impl.write(funcCall)
                        impl.write(');\n')
                elif retType in objects.keys():
                    if nullret:
                        impl.write('    retval = (GtkObject *)')
                        impl.write(funcCall)
                        impl.write(';\n    if (retval) return PyGtk_New(retval);\n    Py_INCREF(Py_None);\n    return Py_None;\n')
                    else:
                        impl.write('    return PyGtk_New((GtkObject *)')
                        impl.write(funcCall)
                        impl.write(');\n')
                else:
                        print "unknown return type '%s'" % retType
                        return 1
                return 0

class FilteringParser(FunctionDefsParser):
        """A refinement of FunctionDefsParser with some common filter types
        built in"""
        def __init__(self, input, prefix='gtkmodule', typeprefix=''):
                FunctionDefsParser.__init__(self, input, prefix, typeprefix)
                # hash lookups are pretty fast ...
                self.excludeList = {}
                self.excludeGlob = []
        def filter(self, name):
                if self.excludeList.has_key(name):
                        return 0
                for glob in self.excludeGlob:
                        if fnmatch.fnmatchcase(name, glob):
                                return 0
                return 1
        def define_func(self, name, retType, args):
                if self.filter(name):
                        FunctionDefsParser.define_func(self,name,retType,args)
                else:
                        print "%s: filtered out" % (name)
        def addExcludeFile(self, fname):
                """Adds the function names from file fname to excludeList"""
                lines = open(fname).readlines()
                # remove \n from each line ...
                lines = map(lambda x: x[:-1], lines)
                # remove comments and blank lines ...
                lines = filter(lambda x: x and x[0] != '#', lines)
                for name in lines:
                        self.excludeList[name] = None
        def addExcludeGlob(self, glob):
                if glob not in self.excludeGlob:
                        self.excludeGlob.append(glob)
                
