#!/usr/bin/env python3

import os
import io
import sys
import re
import argparse
from collections import OrderedDict

parser = argparse.ArgumentParser(description='Generate macros for handling GL dispatch table.')
parser.add_argument('-m', '--maxparam', type=int, default=17,
                   help='The maximum number of parameters to generate')

parsed_args = parser.parse_args()

# on msys, print crlf output
if sys.platform == 'msys':
	sys.stdout = io.TextIOWrapper(sys.stdout.buffer, newline="\r\n")

# Get the file, relative to this script's location (same directory)
# that way we're not sensitive to CWD
pathname = os.path.abspath(os.path.dirname(sys.argv[0])) + os.path.sep

# Finding definitions in the dispatch table header
def_regex = re.compile('(?P<typedef>PFN.*PROC) (?P<name>.*);(\s*\/\/ aliases +)?(?P<aliases>[a-zA-Z0-9_ ,]*)?')

# Finding a function definition in the official headers
func_regex = re.compile('(WINAPI|APIENTRY) (w?gl[A-Za-z_0-9]+)\s?\(')

# Finding a typedef in the official headers
typedef_regex = re.compile('^typedef (?P<return>[A-Za-z_0-9\s*]+)\([A-Z_ *]* (?P<typedef>PFN[A-Z_0-9]+)\) \((?P<args>.*)\);')

# Replacing float arg[2] with float *arg in definitions
array_regex = re.compile('([A-Za-z_][a-zA-Z_0-9]*) ([A-Za-z_][a-zA-Z_0-9]*)\[[0-9]*\]')

# Split an argument definition up by extracting the last full word
argsplit_regex = re.compile('(.*)([\*\s])([a-zA-Z0-9]+)')

# List of hooks to define, will be filled out when processing the dispatch table header
hooks = []

# A dict of typedef information
# Elements contain:
#   'used':      True if it's used by our definitions or not - False if unsupported
#   'function':  The name of the function defined with this typedef.
#                e.g. typedefs['PFNGLBEGINPROC']['function'] = 'glBegin'.
#   'return':    The return type
#   'args':      The list of arguments with types and arguments separated
#                e.g. [["int", "a"], ["float", "b"]]
typedefs = {}

# Open the dispatch table file
with open(pathname + "gl_dispatch_table.h", 'r') as fp:
	# For each line that defines a dispatch pointer, process it
	for func in [line.strip() for line in fp.readlines() if "PFN" in line]:
		match = def_regex.search(func)

		# All lines that contain a dispatch pointer should match the regex
		if not match:
			raise RuntimeError("Badly formed definition: {0}".format(func))

		# Split the list of aliases
		aliases = match.group('aliases')
		aliases = re.split(', *', aliases) if aliases != '' else []

		# Add the hook
		hook = { 'typedef': match.group('typedef'), 'name': match.group('name'), 'aliases': aliases }
		hooks.append(hook)

		# Add the typedefs for the base function and all aliases as used
		typedefs['PFN{0}PROC'.format(hook['name'].upper())] = {'used': True}
		for a in aliases:
			typedefs['PFN{0}PROC'.format(a.upper())] = {'used': True}

# Read all the official headers into a single string
official_headers = []
for header in ['glcorearb.h', 'glext.h', 'gl32.h', 'glesext.h', 'wglext.h', 'legacygl.h']:
	with open(pathname + 'official' + os.path.sep + header, 'r') as fp:
		official_headers += fp.readlines()

# Look for function definitions and add typedef function names.
for line in official_headers:
	match = func_regex.search(line)
	if match:
		typedef = 'PFN{0}PROC'.format(match.group(2).upper())
		if typedef not in typedefs:
			typedefs[typedef] = {'used': False}

		typedefs[typedef]['function'] = match.group(2)

# Now find typedefs and add return type/argument data
for line in official_headers:
	match = typedef_regex.search(line)
	if match:
		typedef = match.group('typedef')
		typedefs[typedef]['return'] = match.group('return').strip()

		args = match.group('args')

		if args == '' or args == 'void':
			args = []
		else:
			# Replace array arguments with pointers - see glPathGlyphIndexRangeNV
			args = array_regex.sub(r"\1 *\2", match.group('args'))
			# Create an array with each parameter as an element
			args = [a.strip() for a in args.split(',')]
			# Split up each argument
			args = [re.split(' *, *', argsplit_regex.sub(r"\1\2,\3", a)) for a in args]

		typedefs[typedef]['args'] = args

# Print the file, starting with a template header
print('''
/******************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Baldur Karlsson
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/

#pragma once

// This file is autogenerated with gen_dispatch_table.py - any changes will be overwritten next time
// that script is run.
// $ ./gen_dispatch_table.py > gl_dispatch_table_defs.h

// We need to disable clang-format since this struct is programmatically generated
// clang-format off



'''.lstrip())

# Print the 'definitions' of these hooks - can be used for stringification or doing
# GetProcAddress style 'check name, return function'
print('#define ForEachSupported(FUNC) \\')

for hook in hooks:
	print('  FUNC({}, {}); \\'.format(hook['name'], hook['name']))
	for a in hook['aliases']:
		print('  FUNC({}, {}); \\'.format(hook['name'], a))
		
print("\n\n\n")

# Print the actual definitions - used to forward into FuncWrapperN/AliasWrapperN to define exported
# hook implementations
print('#define DefineSupportedHooks() \\')

for hook in hooks:
	typedef = typedefs[hook['typedef']]

	num = len(typedef['args'])

	arglist = ''
	for arg in typedef['args']:
		arglist += ', {}, {}'.format(arg[0], arg[1])

	print('  FuncWrapper{}({}, {}{}); \\'.format(num, typedef['return'], hook['name'], arglist))
	for a in hook['aliases']:
		print('  AliasWrapper{}({}, {}, {}{}); \\'.format(num, typedef['return'], a, hook['name'], arglist))
		
print("\n\n\n")

print('#define ForEachUnsupported(FUNC) \\')

for key in OrderedDict(sorted(typedefs.items())):
	typedef = typedefs[key]

	# Don't print for functions we support, or wgl/etc functions
	if typedef['used'] or typedef['function'][0:2] != 'gl':
		continue

	print('  FUNC({}); \\'.format(typedef['function']))

print("\n\n\n")

# For all typedefs not in the hooks, define them as unsupported
print('#define DefineUnsupportedHooks() \\')

for key in OrderedDict(sorted(typedefs.items())):
	typedef = typedefs[key]

	# Don't print for functions we support, or wgl/etc functions
	if typedef['used'] or typedef['function'][0:2] != 'gl':
		continue

	num = len(typedef['args'])

	arglist = ''
	for arg in typedef['args']:
		arglist += ', {}, {}'.format(arg[0], arg[1])

	print('  UnsupportedWrapper{}({}, {}{}); \\'.format(num, typedef['return'], typedef['function'], arglist))
	
# Now generate wrapper macros
print('''

		
// the _renderdoc_hooked variants are to make sure we always have a function symbol exported that we
// can return from GetProcAddress. On posix systems if another library (or the application itself)
// creates a symbol called 'glEnable' we'll return the address of that, and break badly. Instead we
// leave the 'naked' versions for applications trying to import those symbols, and declare the
// _renderdoc_hooked for returning as a func pointer. The raw version calls directly into the hooked
// version to hopefully allow the linker to tail-call optimise and reduce the overhead.
''')

template = '''
#define FuncWrapper{num}(ret, function{macroargs}) \\
  ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
  {{ \\
    SCOPED_GLCALL(function); \\
    UNINIT_CALL(function, {argpass}); \\
    return glhook.driver->function({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC GL_EXPORT_NAME(function)({argdecl}) \\
  {{ \\
    return CONCAT(function, _renderdoc_hooked)({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC function({argdecl});

#define AliasWrapper{num}(ret, function, realfunc{macroargs}) \\
  ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
  {{ \\
    SCOPED_GLCALL(function); \\
    UNINIT_CALL(realfunc, {argpass}); \\
    return glhook.driver->realfunc({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC GL_EXPORT_NAME(function)({argdecl}) \\
  {{ \\
    return CONCAT(function, _renderdoc_hooked)({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC function({argdecl});

#define UnsupportedWrapper{num}(ret, function{macroargs}) \\
  typedef ret(HOOK_CC *CONCAT(function, _hooktype))({argdecl}); \\
  CONCAT(function, _hooktype) CONCAT(unsupported_real_, function) = NULL; \\
  ret HOOK_CC CONCAT(function, _renderdoc_hooked)({argdecl}) \\
  {{ \\
    static bool hit = false; \\
    if(hit == false) \\
    {{ \\
      RDCERR("Function " STRINGIZE(function) " not supported - capture may be broken"); \\
      hit = true; \\
    }} \\
    if(!CONCAT(unsupported_real_, function)) \\
      CONCAT(unsupported_real_, function) = \\
          (CONCAT(function, _hooktype))glhook.GetUnsupportedFunction(STRINGIZE(function)); \\
    return CONCAT(unsupported_real_, function)({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC GL_EXPORT_NAME(function)({argdecl}) \\
  {{ \\
    return CONCAT(function, _renderdoc_hooked)({argpass}); \\
  }} \\
  HOOK_EXPORT ret HOOK_CC function({argdecl});
'''

for num in range(parsed_args.maxparam+1):
	macroargs = ', '.join([('t{0}, p{0}'.format(n+1)) for n in range(num)])
	argdecl = ', '.join([('t{0} p{0}'.format(n+1)) for n in range(num)])
	argpass = ', '.join([('p{0}'.format(n+1)) for n in range(num)])

	macroargs = ', ' + macroargs if num > 0 else macroargs

	print(template.format(num=num, macroargs=macroargs,
						  argdecl=argdecl, argpass=argpass))
