#!/usr/bin/python3
# -*- coding: utf-8 -*-

#  Copyright © 2012-2013  B. Clausius <barcc@gmx.de>
#
#  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 3 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
from os.path import dirname, basename, join, isfile, isdir
import re


def convert_symbols(sources):
    yield '\nfrom libc.stddef cimport ptrdiff_t'
    yield 'from libc.stdint cimport int32_t, intptr_t, int8_t, uint8_t'
    
    for abspath, relpath, source in sources:
        yield '\n\n# from {}:\n'.format(abspath)
        yield "cdef extern from '{}':".format(relpath)
        for match in re.finditer(r'^\#define[ \t]+(\w+).*?$', source, re.MULTILINE | re.DOTALL):
            if match.group(1).startswith('GL_'):
                yield match.expand(r'    enum: \1')
                
    for abspath, relpath, source in sources:
        yield '\n\n# from {}:\n'.format(abspath)
        for match in re.finditer(r'^(typedef[\s\w]*);.*?$', source, re.MULTILINE | re.DOTALL):
            mg1 = match.group(1)
            mg1 = mg1.replace('khronos_float_t', 'float')
            mg1 = mg1.replace('khronos_', '')
            if not mg1.endswith(('_t', '64')) and not (mg1[-2:].isalpha() and mg1[-2:].isupper()):
                yield 'c'+mg1
            
    yield '\n\ncdef extern from *:'
    def const_types(types):
        for type in types.split():
            yield '    ctypedef {0}* const_{0}_ptr "const {0}*"'.format(type)
    yield from const_types('GLubyte GLboolean GLvoid GLchar GLfloat GLint GLshort '
                           'GLbyte GLuint GLushort GLclampf GLsizei GLenum void')
                           
    for abspath, relpath, source in sources:
        yield '\n\n# from {} with:\n'.format(abspath)
        yield "cdef extern from '{}':".format(relpath)
        for match in re.finditer(
                r'^(?:GLAPI|GL_APICALL)([\s\w*]*?)(?:GLAPIENTRY|APIENTRY|GL_APIENTRY)([^(]*)\(([^)]*)\);(.*?)$',
                source, re.MULTILINE | re.DOTALL):
            mg2s2 = match.group(2).strip()[-2:]
            if mg2s2.isalpha() and mg2s2.isupper():
                continue
            for mgf in (match.group(1).find, match.group(3).find):
                if not mgf('64') == mgf('GLsync') == mgf('GLDEBUGPROC') == -1:
                    break
            else:
                if match.group(3).strip() == 'void':
                    template = r'    cdef\1\2()\4'
                else:
                    template = r'    cdef\1\2(\3)\4'
                yield match.expand(template).replace('const ', '').replace('const*', '*') \
                                            .replace(' in,', ' in_,').replace('/*', '#/*')
                
def read_url(url, headerfilename):
    import urllib.request
    text = urllib.request.urlopen(url).read().decode('utf-8')
    return [(url, headerfilename, text)]
    
def read_headers(path, headerfiles):
    sources = []
    for headerfilename in headerfiles:
        absheaderfilename = join(path, headerfilename) if isdir(path) else path
        with open(absheaderfilename, 'rt', encoding='utf-8') as headerfile:
            sources.append((absheaderfilename, headerfilename, headerfile.read()))
    return sources
    
def create_pxd(sources, pxdfilename):
    with open(pxdfilename, 'wt', encoding='utf-8') as pxdfile:
        print('# {}'.format(pxdfilename), file=pxdfile)
        print('# generated with {}'.format(sys.argv[0]), file=pxdfile)
        for token in convert_symbols(sources):
            print(token, file=pxdfile)
            
def usage(status):
    print('usage:\n'
          '{0} [-h|--help]\n'
          '{0} gl|gles2|gles3\n'
          '{0} filename\n'
          '{0} url\n'
          .format(basename(sys.argv[0])))
    sys.exit(status)
    
def main():
    args = sys.argv[1:]
    if not args or args[0] in ['-h', '--help']:
        usage(0)
    if len(args) != 1:
        print('wrong number of arguments')
        usage(1)
    includedir = '/usr/include'
    if args[0] == 'gl':
        sources = read_headers(includedir, ['GL/gl.h', 'GL/glext.h'])
        create_pxd(sources, 'pybiklib/gl.pxd')
    elif args[0] == 'gles2':
        sources = read_headers(includedir, ['GLES2/gl2.h', 'GLES2/gl2ext.h'])
        create_pxd(sources, 'pybiklib/gl.pxd')
    elif args[0] == 'gles3':
        sources = read_headers(includedir, ['GLES3/gl3.h'])
        create_pxd(sources, 'pybiklib/gl.pxd')
    elif isfile(args[0]):
        sources = read_headers(args[0], ['GL/gl.h'])
        create_pxd(sources, 'pybiklib/gl.pxd')
    elif args[0].startswith('http://') or args[0].startswith('https://'):
        sources = read_url(args[0], 'GL/gl.h')
        create_pxd(sources, 'pybiklib/gl.pxd')
    else:
        print('wrong argument:', args[0])
        usage(1)
    

if __name__ == '__main__':
    main()
    

