
#################################################################################
# Copyright (C) 2007 Carl Johan Lejdfors                                        #
#                                                                               #
# 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     #
#                                                                               #
# Please send all bugreports to: Carl Johan Lejdfors <calle.lejdfors@cs.lth.se> #
#################################################################################


import re, string, sys, os.path

arrayT = re.compile('const (GL\w+) (?P<name>\w+)\[(\d+)\]')

def findTypesAndNames(args):
    return map(findTypeAndName, args)

def findTypeAndName(arg):
    m = arrayT.match(arg)
    if m:
        n = m.group('name')
        t = 'const ' + m.group(1) + '*' ## '[' + m.group(3) + ']' 
        ## print t,n
        return t,n  ## !!FIXME!! handle const T m[n] variables correctly...

    lastStar = string.rfind(arg, '*')
    if lastStar != -1:
        return arg[:lastStar+1], arg[lastStar+1:]

    return string.split(arg)


def isPointerType(arg):
    return '*' in arg

def isConstPointer(arg):
    return isPointerType(arg) and 'const' not in arg

getExceptions = ['glGetError',
                 'glGetString',
                 'glGetPointerv',
                 'glGetTexImage',
                 'glGetAttribLocation',
                 'glGetAttribLocationARB',
                 'glGetUniformLocation',
                 'glGetUniformLocationARB',
                 'glGetTexImage',
                 'glGetActiveAttribARB',
                 ]

genExceptions = ['glGenLists',
                 'glGenerateMipmapEXT',
                 ]

exclude = [
           'glAreTexturesResident',
           'glGetCompressedTexImage',
           'glGetBufferSubData',
           'glGetBufferSubDataARB',
           'glGetActiveAttrib',
           'glGetActiveUniform',
           'glGetActiveUniformARB',
           'glGetProgramInfoLog',
           'glGetInfoLogARB',
           'glGetShaderInfoLog',
           'glGetShaderSource',
           'glGetShaderSourceARB',
           ## 'glReadPixels',
           'glGetCompressedTexImageARB',
           'glGetHandleARB',
           ]
           
token = re.compile("^\s+(GLU?_\w+)\s+(\w+)$")
function = re.compile("^\s+((?:void|GL\w+|const GL\w+ \*)\*?) (gl\w+) \((.*)\)$")
typedef = re.compile("^\ttypedef (?P<srctype>\w+) (?P<targettype>\w+)$")
empty = re.compile("^(\t|\n)$")
webpage = re.compile("^http(?:\w|[/])+$")


def parse_extension(outdir, filename):
    extFile = file(filename, 'r')

    extName = extFile.readline()
    webpage = None
    constants = []
    functions = []
    typedefs = []
    
    for line in extFile:
        m = empty.match(line)
        if m:
            continue

        if line[0:7] == 'http://':
            webpage = line
            continue

        m = token.match(line)
        if m:
            constants.append((m.group(1), m.group(2)))
            continue

        m = function.match(line)
        if m:
            retType, name, args = m.groups()
            args = map(string.strip, string.split(args,','))
            args = filter(lambda x: x != 'void', args)
            functions.append((retType, name, args))
            continue

        m = typedef.match(line)
        if m:
            typedefs.append(tuple(m.groups()))
            continue

        if line:
            print "UNKNOWN LINE:"
            print '%s' % line[:-1]
            print 

    outputName = os.path.join(outdir, os.path.basename(filename))

    declFile = file('%s-gen.hh' % outputName, 'w')
    implFile = file('%s-gen.cc' % outputName, 'w')
    methodFile = file('%s-methods.cc' % outputName, 'w')
    constFile = file('%s-constants.cc' % outputName, 'w')

    print >> implFile, "#include \"Python.h\""
    print >> implFile, "#include \"GL/glew.h\""
    print >> implFile, "#include \"unpack.hh\""
    print >> implFile, "#include \"unpack_ptr.hh\""
    print >> implFile, "#include \"pack.hh\""
    print >> implFile, "#include \"%s-gen.hh\"" % extName[:-1]

    glewName = 'GLEW_' + extName[3:-1]

    ## write declaration
    print >> declFile, "#ifdef WINDOWS"
    print >> declFile, "#ifdef BUILD_DLL"
    print >> declFile, "#define EXPORT __declspec(dllexport)"
    print >> declFile, "#else"
    print >> declFile, "#define EXPORT __declspec(dllimport)"
    print >> declFile, "#endif"
    print >> declFile, "#else"
    print >> declFile, "#define EXPORT "
    print >> declFile, "#endif"
    print >> declFile, "EXPORT PyObject* ___%s(PyObject*, PyObject* args);" % glewName
    
    print >> methodFile, '{ "%s", ___%s, METH_VARARGS, "Nothing for now..." },' % (glewName, glewName)
    
    print >> implFile, "EXPORT PyObject* ___%s(PyObject*, PyObject* args)" % glewName
    print >> implFile, "{"
    print >> implFile, "  return pack((GLboolean)__%s);" % glewName
    print >> implFile, "}"
    print >> implFile

    for f in functions:
        retType, name, args = f

        print >> declFile, "EXPORT PyObject* __%s(PyObject*, PyObject* args);" % name
        print >> methodFile, '{ "%s", __%s, METH_VARARGS, "Nothing for now..." },' % (name, name)

        if name in exclude: ## name[-7:] == 'Pointer':
            print name, "is not implemented!"
            print >> implFile, "EXPORT PyObject* __%s(PyObject*, PyObject* args)" % name
            print >> implFile, "{"
            print >> implFile, "   PyErr_SetString(PyExc_NotImplementedError, \"Function '%s' not implemented yet\");" % name
            print >> implFile, "   return NULL;"
            print >> implFile, "}"
            print >> implFile

        elif os.access(os.path.join('src', name+".cc"), os.R_OK):
            print >> implFile, "#include \"src/%s.cc\"" % name

        elif name[0:5] == 'glGen' and name not in genExceptions:
            assert retType == 'void'
        
            ## is a generator function. Need to repack arguments to hide
            ## passing the ptr
            size, ret = args
            sizeT, sizeN = findTypeAndName(size)
            retT, retN = findTypeAndName(ret)
            
            print >> implFile, "EXPORT PyObject* __%s(PyObject*, PyObject* args)" % name
            print >> implFile, "{"
            print >> implFile, "   %s %s;" % (sizeT, sizeN)
            print >> implFile, "   if ( !PyArg_ParseTuple(args, \"i\", &%s) ) {" % (sizeN)
            print >> implFile, "      return NULL;"
            print >> implFile, "   }"
        
            print >> implFile, "   %s %s = new %s(%s);" % (retT, retN, retT[0:-1], sizeN);
            print >> implFile, "   %s(%s, %s);" % (name, sizeN, retN)
            print >> implFile, "   PyObject* _result = pack(%s, %s);" % (sizeN, retN)
            print >> implFile, "   delete[] %s;" % (retN)
            print >> implFile, "   return _result;"
            print >> implFile, "}"
            print >> implFile

            
        elif name[0:5] == 'glGet' and name not in getExceptions:
            ## is an "getter" function. Need to repack arguments to hide
            ## passing the ptr
            print >> implFile, "EXPORT PyObject* __%s(PyObject*, PyObject* args)" % name
            print >> implFile, "{"

            args = findTypesAndNames(args)
            inArgs = args ##[:-1]
            objNames = map(lambda (t,n): '%s_obj' % n, inArgs)
            for objName in objNames:
                print >> implFile, "   PyObject* %s;" % objName

            if inArgs:
                print >> implFile, "   if ( !PyArg_ParseTuple(args, \"%s\", %s) ) {" % ('O'*len(inArgs),
                                                            string.join(map(lambda n: '&' + n, objNames), ", "))
                print >> implFile, "      return NULL;"
                print >> implFile, "   }"
            
            for (t,n), obj in zip(args, objNames):
                print >> implFile, "   %s %s = unpack<%s>(%s);" % (t, n, t, obj)

            #size = 1 ## !!FIXME!! must estimate number of elements needed...
            #retT,retN = args[-1]
            #print >> implFile, "   %s *%s = new %s[%s];" % (retT[:-1], retN, retT[:-1], size)
            print >> implFile, "   %s(%s);" % (name, string.join(map(lambda (t,n): n, args), ", "))

            #for t,n in args[:-1]:
            #    if isPointer(t):
            #        print >> implFile, "   delete[] %s;" % (n)

            #print >> implFile, "   PyObject* res = pack(%s, %s);" % (size, retN)
            #print >> implFile, "   delete[] %s;" % (retN)
            print >> implFile, "   Py_INCREF(Py_None);"
            print >> implFile, "   return Py_None;"
            print >> implFile, "}"

        elif name[-7:] == 'Pointer':
            print >> implFile, "static pointer_wrapper<GLvoid*> __%s_ptr = 0;" % name
            print >> implFile, "EXPORT PyObject* __%s( PyObject*, PyObject* args )" % name
            print >> implFile, "{"

            args = findTypesAndNames(args)
            for t,n in args[:-1]:
                print >> implFile, "   %s %s;" % (t,n)

            #lastT,lastN = args[-1]
            #if lastN == 'ptr':
            print >> implFile, "   PyObject* obj;" 

            print >> implFile, "   if ( !PyArg_ParseTuple(args, \"%sO\", %s, &obj) ) {" \
                  % ('i'*(len(args)-1),
                     string.join(map(lambda (t,n): '&%s' % n, args[:-1]), ", "))
            print >> implFile, "      return NULL;"
            print >> implFile, "   }"
            
            #print >> implFile, "   if ( __%s_ptr )" % name
            #print >> implFile, "      free( __%s_ptr );" % name

            print >> implFile, "   try {" 
            if name == 'glEdgeFlagPointer':
                print >> implFile, "   __%s_ptr = unpack_pointer(GL_BOOL, stride, obj);" % name
            else:
                print >> implFile, "   __%s_ptr = unpack_pointer(type, stride, obj);" % name

            print >> implFile, "   %s(%s, __%s_ptr);" \
                  % (name, string.join(map(lambda (t,n): n, args[:-1]), ", "), name)

            print >> implFile, "   } catch ( const pyglew_exception &e ) {" 
            print >> implFile, "      PyErr_SetString(PyExc_TypeError, e.what());"
            print >> implFile, "      return NULL;"
            print >> implFile, "   }"
            print >> implFile, "   Py_INCREF(Py_None);"
            print >> implFile, "   return Py_None;"
            print >> implFile, "}"


        else:
            print >> implFile, "EXPORT PyObject* __%s(PyObject*, PyObject* args)" % name
            print >> implFile, "{"

            ## !!FIXME!! can implement direct translation here for some types
            ## however that will make interfacing with e.g. Numeric problematic

            args = findTypesAndNames(args)
            objNames = map(lambda (t,n): '%s_obj' % n, args)
            for objName in objNames:
                print >> implFile, "   PyObject* %s;" % objName
                
            if args:
                print >> implFile, "  if ( !PyArg_ParseTuple(args, \"%s\", %s) ) {" % ('O'*len(args),
                                                                                       string.join(map(lambda n: '&' + n, objNames), ", "))
                print >> implFile, "    return NULL;"
                print >> implFile, "  }"

            print >> implFile, "  try {"
            for (t,n), ra in zip(args, objNames):
                if isPointerType(t):
                    print >> implFile, "   pointer_wrapper<%s> %s = unpack_ptr<%s>(%s);" % (t, n, t, ra)
                else:
                    print >> implFile, "   %s %s = unpack<%s>(%s);" % (t, n, t, ra)
                        
            if retType == 'void':
                print >> implFile, "   %s(%s);" % (name, string.join(map(lambda (t,n): n, args), ", "))

                #for t,n in args:
                #    if shouldDelete(t):
                #        print >> implFile, "   delete[] %s;" % (n)

                print >> implFile, "   Py_INCREF(Py_None);"
                print >> implFile, "   return Py_None;"
            else:
                print >> implFile, "   %s _result = %s(%s);" % (retType, name, string.join(map(lambda (t,n): n, args), ", "))

                #for t,n in args:
                #    if shouldDelete(t):
                #        print >> implFile, "   delete[] %s;" % (n)
                print >> implFile, "   return pack(_result);"

            print >> implFile, "  } catch (const pyglew_exception& e) {"
            print >> implFile, "    PyErr_SetString(PyExc_TypeError, e.what());"
            print >> implFile, "  }"

            print >> implFile, "  return NULL;"
            print >> implFile, "}"
            print >> implFile

    for n, v in constants:
        print >> constFile, "{ %s, \"%s\", %s }," % (0, n, v)


if __name__ == '__main__':
    parse_extension(sys.argv[1], sys.argv[2])

