#!/usr/bin/python

# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script generates C++ and ASM code that will dynamically bind
# OpenGL functions at runtime

# TODO: This script has rudimentary support for multiple GL APIs

import argparse
import os
import platform
import re

from datetime import datetime

def printApis(gl_apis):
    for api in gl_apis:
        print('%s\t%s' % (api['api'], api['name']))

def parseFunctions(include_file, matcher):
    print('Parsing header %s...' % include_file)
    regex = re.compile(matcher)

    functions = []

    with open(include_file) as file:
        for line in file:
            match = regex.match(line)
            if match is not None:
                functions.append(match.group(1))

    return functions

def generateHeader(api, functions, include_dir, output_dir):
    suffix = api['suffix'] if api['suffix'] != 'Core' else ''
    gl_suffix = '_gl_' + suffix.lower() if len(suffix) > 0 else ''
    src_file = os.path.join(include_dir, 'bluegl', 'BlueGL%s.h' % suffix)

    print('Generating public header %s...' % src_file)

    headers = ''
    if len(api['defines']) > 0:
        headers += '\n'.join('#define %s 1' % d for d in api['defines'])
        headers += '\n\n'
    headers += '\n'.join('#include <%s/%s>' % (api['directory'], r) for r in api['headers'])

    header = '''/*
 * Copyright (C) %(year)d The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**********************************************************************************************
 * Generated by bluegl/bluegl-gen.py
 * DO NOT EDIT
 **********************************************************************************************/

#ifndef TNT_FILAMENT_BLUEGL_%(suffix)s_H
#define TNT_FILAMENT_BLUEGL_%(suffix)s_H

%(headers)s
''' % {
    'year':      datetime.now().year,
    'suffix':    suffix.upper(),
    'headers':   headers
 }

    content = '''
#if defined(WIN32)

#ifdef max
#undef max
#endif

#ifdef min
#undef min
#endif

#ifdef far
#undef far
#endif

#ifdef near
#undef near
#endif

#ifdef ERROR
#undef ERROR
#endif

#ifdef OPAQUE
#undef OPAQUE
#endif

#ifdef TRANSPARENT
#undef TRANSPARENT
#endif

#ifdef PURE
#undef PURE
#endif

#endif

namespace bluegl {

/**
 * Looks up and binds all available OpenGL %(api_suffix)s functions.
 * Every call to this function will increase an internal reference
 * counter that can be decreased by calling unbind().
 *
 * @return 0 on success or -1 if an error occurred.
 */
int bind%(gl_suffix)s();

/**
 * Unbinds all available OpenGL %(api_suffix)s functions.
 * Every call to this function will decrease an internal reference
 * counter and unbind all OpenGL functions when the counter reaches 0.
 * As such you should assume that no OpenGL calls can be made after
 * calling this function.
 */
void unbind%(gl_suffix)s();

''' % {
    'api_suffix': api['suffix'],
    'gl_suffix':  gl_suffix
}

    footer = '''
} // namespace bluegl

#endif // TNT_FILAMENT_BLUEGL_%s_H
''' % suffix.upper()

    directory = os.path.dirname(src_file)
    if not os.path.exists(directory):
        os.makedirs(directory)

    with open(src_file, 'w') as file:
        file.write(header)
        file.write(content)
        file.write(footer)

def generateProxies(api, functions, output_dir, platforms):
    suffix = api['suffix']

    header = '''/*
 * Copyright (C) %d The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**********************************************************************************************
 * Generated by bluegl/bluegl-gen.py
 * DO NOT EDIT
 **********************************************************************************************/
''' % datetime.now().year

    headerMasM = ''';
; Copyright (C) %(year)d The Android Open Source Project
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;      http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;

;
; Generated by bluegl/bluegl-gen.py
; DO NOT EDIT
;

.code
''' % {
        'year':       datetime.now().year,
    }

    osSpecificHeader = {
        'Linux': header ,
        'LinuxAArch64': header ,
        'Darwin': header ,
        'DarwinAArch64': header ,
        'Windows': headerMasM
    }

    osSpecificFooter = {
        'Linux': '',
        'LinuxAArch64': '' ,
        'Darwin': '',
        'DarwinAArch64': '',
        'Windows': 'end\n'
    }

    code = {
        'Linux': '''
.global %(function)s
.type %(function)s, %%function
%(function)s:
    mov __blue_gl%(suffix)s_%(function)s@GOTPCREL(%%rip), %%r11
    jmp *(%%r11)
''',
        'LinuxAArch64': '''
	.align	2
	.global	%(function)s
	.type	%(function)s, %%function
%(function)s:
	adrp	x16, :got:__blue_gl%(suffix)s_%(function)s
	ldr	x16, [x16, #:got_lo12:__blue_gl%(suffix)s_%(function)s]
	ldr	x16, [x16]
	br	x16
	.size	%(function)s, .-%(function)s
''',
        'Darwin': '''
.private_extern _%(function)s
_%(function)s:
    mov ___blue_gl%(suffix)s_%(function)s@GOTPCREL(%%rip), %%r11
    jmp *(%%r11)
''',
        'DarwinAArch64': '''
.private_extern _%(function)s
	.align	2
_%(function)s:
	adrp	x16, ___blue_gl%(suffix)s_%(function)s@GOTPAGE
	ldr	x16, [x16, ___blue_gl%(suffix)s_%(function)s@GOTPAGEOFF]
	ldr	x16, [x16]
	br	x16
''',
        'Windows': '''
extrn __blue_gl%(suffix)s_%(function)s: qword
%(function)s proc
    mov r11, __blue_gl%(suffix)s_%(function)s
    jmp r11
%(function)s endp
''',
    }

    for platform in platforms:
        src_file = os.path.join(output_dir, 'BlueGL%s%sImpl.S' % (suffix, platform))
        print('Generating proxy %s...' % src_file)

        with open(src_file, 'w') as file:
            file.write(osSpecificHeader[platform])
            for function in functions:
                file.write(code[platform] % {'function': function, 'suffix': suffix})
            file.write(osSpecificFooter[platform])


def generateSource(api, functions, output_dir):
    suffix = api['suffix'] if api['suffix'] != 'Core' else ''
    gl_suffix = '_gl_' + suffix.lower() if len(suffix) > 0 else ''
    src_file = os.path.join(output_dir, 'private_BlueGL%s.h' % suffix)

    print('Generating source %s...' % src_file)

    header = '''/*
 * Copyright (C) %(year)d The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**********************************************************************************************
 * Generated by bluegl/bluegl-gen.py
 * DO NOT EDIT
 **********************************************************************************************/

''' % {
    'year':       datetime.now().year,
}

    struct_header = '''
namespace bluegl {

// This mutex protect g_library_refcount below.
static std::mutex g_library_mutex;
static uint32_t g_library_refcount = 0;

struct {
    void** api_call;
    const char* api_name;
} g_gl%s_stubs[] = {
''' % (suffix)

    functions_footer = '''};
'''

    with open(src_file, 'w') as file:
        file.write(header)
        file.write("extern \"C\" {\n")
        for function in functions:
            file.write('void* __blue_gl%s_%s;\n' % (api['suffix'], function))
        file.write("}\n")
        file.write(struct_header)
        for function in functions:
            file.write('    { &__blue_gl%s_%s, "%s" },\n' % (api['suffix'], function, function))
        file.write(functions_footer)

        file.write("size_t %s%sNumFunctions = %d;" % ("blue", api['suffix'], len(functions)))
        file.write("\n} // namespace bluegl")


def generateApis(apis, include_dir, output_dir):
    platforms = ['Linux', 'LinuxAArch64', 'Darwin', 'DarwinAArch64', 'Windows']

    for api in apis:
        functions = []
        for header in api['headers']:
            include_file = os.path.join(include_dir, api['directory'], header)
            functions += parseFunctions(include_file, api['matcher'])

        # remove duplicates
        functions = list(set(functions))
        print('Found %s functions' % len(functions))

        generateHeader(api, functions, include_dir, output_dir)
        generateProxies(api, functions, output_dir, platforms)
        generateSource(api, functions, output_dir)

if __name__ == '__main__':
    gl_apis = [
        # OpenGL Core (4.x+) support
        {
            'api':       'gl',
            'name':      'OpenGL Core',
            'prefix':    'gl',
            'suffix':    'Core',
            'directory': 'GL',
            'headers':   ['glcorearb.h', 'glext.h'],
            'matcher':   r'GLAPI.*APIENTRY\s+(\w+)',
            'defines':   ['GL_GLEXT_PROTOTYPES']
        },
        # Legacy OpenGL support
        {
            'api':       'legacy',
            'name':      'OpenGL Legacy',
            'prefix':    'gl',
            'suffix':    'Legacy',
            'directory': 'Legacy',
            'headers':   ['gl.h', 'glext.h'],
            'matcher':   r'GLAPI.*APIENTRY\s+(\w+)',
            'defines':   []
        }
    ]

    parser = argparse.ArgumentParser(description='Blue OpenGL bindings generator.')
    parser.add_argument('-I', '--include', default='include', type=str, metavar='DIR',
            help='Include directory')
    parser.add_argument('-o', '--output', default='src', type=str, metavar='DIR',
            help='Output directory')
    parser.add_argument('-l', '--list', action='store_true',
            help='List known APIs')
    parser.add_argument('--api', type=str, metavar='API', nargs="*",
            choices=[ 'gl', 'legacy' ], help='Generate only specific API')

    args = parser.parse_args()

    include_dir = args.include if args.include is not None else os.path.join(os.getcwd(), 'include')
    output_dir  = args.output  if args.output  is not None else os.path.join(os.getcwd(), 'src')

    if args.list:
        printApis(gl_apis)
        exit(0)

    selected_apis = args.api if args.api else ['gl']

    apis = [api for api in gl_apis if api['api'] in selected_apis]
    generateApis(apis, include_dir, output_dir)
