#!/usr/bin/python3
#
# python script to generate cdecl to stdcall wrappers for GL functions
# adapted from genheaders.py
#
# Copyright (c) 2013 The Khronos Group Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and/or associated documentation files (the
# "Materials"), to deal in the Materials without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Materials, and to
# permit persons to whom the Materials are 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 Materials.
#
# THE MATERIALS ARE 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
# MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.

import sys
from reg import *

# Default input / log files
errFilename = None
diagFilename = 'diag.txt'
regFilename = 'gl.xml'
outFilename = 'gen_gl_wrappers.c'

protect=True
prefix="gl"
preresolve=False
wrapper=False
shim=False
thunk=False
thunkdefs=False
staticwrappers=False
nodebug=False

# list of WGL extension functions we use
used_wgl_ext_fns = {key: 1 for key in [
    "wglSwapIntervalEXT",
    "wglGetExtensionsStringARB",
    "wglDestroyPbufferARB",
    "wglGetPbufferDCARB",
    "wglReleasePbufferDCARB",
    "wglCreatePbufferARB",
    "wglMakeContextCurrentARB",
    "wglChoosePixelFormatARB",
    "wglGetPixelFormatAttribivARB",
    "wglGetPixelFormatAttribivARB"
]}

if __name__ == '__main__':
    i = 1
    while (i < len(sys.argv)):
        arg = sys.argv[i]
        i = i + 1
        if (arg == '-noprotect'):
            print('Disabling inclusion protection in output headers', file=sys.stderr)
            protect = False
        elif (arg == '-registry'):
            regFilename = sys.argv[i]
            i = i+1
            print('Using registry', regFilename, file=sys.stderr)
        elif (arg == '-outfile'):
            outFilename = sys.argv[i]
            i = i+1
        elif (arg == '-preresolve'):
            preresolve=True
        elif (arg == '-wrapper'):
            wrapper=True
        elif (arg == '-shim'):
            shim=True
        elif (arg == '-thunk'):
            thunk=True
        elif (arg == '-thunkdefs'):
            thunkdefs=True
        elif (arg == '-staticwrappers'):
            staticwrappers=True
        elif (arg == '-prefix'):
            prefix = sys.argv[i]
            i = i+1
        elif (arg == '-nodebug'):
            nodebug = True
        elif (arg[0:1] == '-'):
            print('Unrecognized argument:', arg, file=sys.stderr)
            exit(1)

print('Generating', outFilename, file=sys.stderr)

# Load & parse registry
reg = Registry()
tree = etree.parse(regFilename)
reg.loadElementTree(tree)

if shim:
    versions = '1\.[012]'
else:
    versions = '.*'

genOpts = CGeneratorOptions(
        apiname           = prefix,
        profile           = 'compatibility',
        versions          = versions,
        emitversions      = versions,
        defaultExtensions = prefix,                   # Default extensions for GL
        protectFile       = protect,
        protectFeature    = protect,
        protectProto      = protect,
        )

# create error/warning & diagnostic files
if (errFilename):
    errWarn = open(errFilename,'w')
else:
    errWarn = sys.stderr
diag = open(diagFilename, 'w')

def ParseCmdRettype(cmd):
    proto=noneStr(cmd.elem.find('proto'))
    rettype=noneStr(proto.text)
    if rettype.lower()!="void ":
        plist = ([t for t in proto.itertext()])
        rettype = ''.join(plist[:-1])
    rettype=rettype.strip()
    return rettype

def ParseCmdParams(cmd):
    params = cmd.elem.findall('param')
    plist=[]
    for param in params:
        # construct the formal parameter definition from ptype and name
        # elements, also using any text found around these in the
        # param element, in the order it appears in the document
        paramtype = ''
        # also extract the formal parameter name from the name element
        paramname = ''
        for t in param.iter():
            if t.tag == 'ptype' or t.tag == 'param':
                paramtype = paramtype + noneStr(t.text)
            if t.tag == 'name':
                paramname = t.text + '_'
                paramtype = paramtype + ' ' + paramname
            if t.tail is not None:
                paramtype = paramtype + t.tail.strip()
        plist.append((paramtype, paramname))
    return plist

class PreResolveOutputGenerator(OutputGenerator):
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
        self.wrappers={}
    def beginFile(self, genOpts):
        self.outFile.write('/* Automatically generated from %s - DO NOT EDIT */\n\n'%regFilename)
    def endFile(self):
        self.outFile.write('\nvoid ' + prefix + 'ResolveExtensionProcs(void)\n{\n')
        for funcname in self.wrappers.keys():
            self.outFile.write( '  PRERESOLVE(PFN' + funcname.upper() + 'PROC, "' + funcname + '");\n')
        self.outFile.write('}\n\n')
    def beginFeature(self, interface, emit):
        OutputGenerator.beginFeature(self, interface, emit)
    def endFeature(self):
        OutputGenerator.endFeature(self)
    def genType(self, typeinfo, name):
        OutputGenerator.genType(self, typeinfo, name)
    def genEnum(self, enuminfo, name):
        OutputGenerator.genEnum(self, enuminfo, name)
    def genCmd(self, cmd, name):
        OutputGenerator.genCmd(self, cmd, name)

        if prefix == 'wgl' and not name in used_wgl_ext_fns:
            return

        self.outFile.write('RESOLVE_DECL(PFN' + name.upper() + 'PROC);\n')
        self.wrappers[name]=1

class WrapperOutputGenerator(OutputGenerator):
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
    def beginFile(self, genOpts):
        self.outFile.write('/* Automatically generated from %s - DO NOT EDIT */\n\n'%regFilename)
    def endFile(self):
        pass
    def beginFeature(self, interface, emit):
        OutputGenerator.beginFeature(self, interface, emit)
        self.OldVersion = self.featureName.startswith('GL_VERSION_1_0') or self.featureName.startswith('GL_VERSION_1_1')
    def endFeature(self):
        OutputGenerator.endFeature(self)
    def genType(self, typeinfo, name):
        OutputGenerator.genType(self, typeinfo, name)
    def genEnum(self, enuminfo, name):
        OutputGenerator.genEnum(self, enuminfo, name)
    def genCmd(self, cmd, name):
        OutputGenerator.genCmd(self, cmd, name)

        if prefix == 'wgl' and not name in used_wgl_ext_fns:
            return

        rettype=ParseCmdRettype(cmd)

        if staticwrappers: self.outFile.write("static ")
        self.outFile.write("%s %sWrapper("%(rettype, name))
        plist=ParseCmdParams(cmd)
        Comma=""
        if len(plist):
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, ptype))
                Comma=", "
        else:
            self.outFile.write("void")

        self.outFile.write(")\n{\n")

        # for GL 1.0 and 1.1 functions, generate stdcall wrappers which call the function directly
        if self.OldVersion:
            if not nodebug:
                self.outFile.write('  if (glxWinDebugSettings.enable%scallTrace) ErrorF("%s\\n");\n'%(prefix.upper(), name))
                self.outFile.write("  glWinDirectProcCalls++;\n")
                self.outFile.write("\n")

            if rettype.lower()=="void":
                self.outFile.write("  %s( "%(name))
            else:
                self.outFile.write("  return %s( "%(name))

            Comma=""
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, pname))
                Comma=", "

        # for GL 1.2+ functions, generate stdcall wrappers which use wglGetProcAddress()
        else:
            if rettype.lower()=="void":
                self.outFile.write('  RESOLVE(PFN%sPROC, "%s");\n'%(name.upper(), name))

                if not nodebug:
                    self.outFile.write("\n")
                    self.outFile.write('  if (glxWinDebugSettings.enable%scallTrace) ErrorF("%s\\n");\n'%(prefix.upper(), name))
                    self.outFile.write("\n")

                self.outFile.write("  RESOLVED_PROC(PFN%sPROC)( """%(name.upper()))
            else:
                self.outFile.write('  RESOLVE_RET(PFN%sPROC, "%s", FALSE);\n'%(name.upper(), name))

                if not nodebug:
                    self.outFile.write("\n")
                    self.outFile.write('  if (glxWinDebugSettings.enable%scallTrace) ErrorF("%s\\n");\n'%(prefix.upper(), name))
                    self.outFile.write("\n")

                self.outFile.write("  return RESOLVED_PROC(PFN%sPROC)("%(name.upper()))

            Comma=""
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, pname))
                Comma=", "
        self.outFile.write(" );\n}\n\n")

class ThunkOutputGenerator(OutputGenerator):
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
    def beginFile(self, genOpts):
        self.outFile.write('/* Automatically generated from %s - DO NOT EDIT */\n\n'%regFilename)
    def endFile(self):
        pass
    def beginFeature(self, interface, emit):
        OutputGenerator.beginFeature(self, interface, emit)
        self.OldVersion = (self.featureName in ['GL_VERSION_1_0', 'GL_VERSION_1_1'])
    def endFeature(self):
        OutputGenerator.endFeature(self)
    def genType(self, typeinfo, name):
        OutputGenerator.genType(self, typeinfo, name)
    def genEnum(self, enuminfo, name):
        OutputGenerator.genEnum(self, enuminfo, name)
    def genCmd(self, cmd, name):
        OutputGenerator.genCmd(self, cmd, name)

        rettype=ParseCmdRettype(cmd)
        self.outFile.write("%s %sWrapper("%(rettype, name))
        plist=ParseCmdParams(cmd)

        Comma=""
        if len(plist):
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, ptype))
                Comma=", "
        else:
            self.outFile.write("void")

        self.outFile.write(")\n{\n")

        # for GL 1.0 and 1.1 functions, generate stdcall thunk wrappers which call the function directly
        if self.OldVersion:
            if rettype.lower()=="void":
                self.outFile.write("  %s( "%(name))
            else:
                self.outFile.write("  return %s( "%(name))

            Comma=""
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, pname))
                Comma=", "

        # for GL 1.2+ functions, generate wrappers which use wglGetProcAddress()
        else:
            if rettype.lower()=="void":
                self.outFile.write('  RESOLVE(PFN%sPROC, "%s");\n'%(name.upper(), name))
                self.outFile.write("  RESOLVED_PROC(PFN%sPROC)( """%(name.upper()))
            else:
                self.outFile.write('  RESOLVE_RET(PFN%sPROC, "%s", FALSE);\n'%(name.upper(), name))
                self.outFile.write("  return RESOLVED_PROC(PFN%sPROC)("%(name.upper()))

            Comma=""
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, pname))
                Comma=", "
        self.outFile.write(" );\n}\n\n")

class ThunkDefsOutputGenerator(OutputGenerator):
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
    def beginFile(self, genOpts):
        self.outFile.write("EXPORTS\n"); # this must be the first line for libtool to realize this is a .def file
        self.outFile.write('; Automatically generated from %s - DO NOT EDIT\n\n'%regFilename)
    def endFile(self):
        pass
    def beginFeature(self, interface, emit):
        OutputGenerator.beginFeature(self, interface, emit)
    def endFeature(self):
        OutputGenerator.endFeature(self)
    def genType(self, typeinfo, name):
        OutputGenerator.genType(self, typeinfo, name)
    def genEnum(self, enuminfo, name):
        OutputGenerator.genEnum(self, enuminfo, name)
    def genCmd(self, cmd, name):
        OutputGenerator.genCmd(self, cmd, name)

        # export the wrapper function with the name of the function it wraps
        self.outFile.write("%s = %sWrapper\n"%(name, name))

class ShimOutputGenerator(OutputGenerator):
    def __init__(self,
                 errFile = sys.stderr,
                 warnFile = sys.stderr,
                 diagFile = sys.stdout):
        OutputGenerator.__init__(self, errFile, warnFile, diagFile)
    def beginFile(self, genOpts):
        self.outFile.write('/* Automatically generated from %s - DO NOT EDIT */\n\n'%regFilename)
    def endFile(self):
        pass
    def beginFeature(self, interface, emit):
        OutputGenerator.beginFeature(self, interface, emit)
        self.OldVersion = (self.featureName in ['GL_VERSION_1_0', 'GL_VERSION_1_1', 'GL_VERSION_1_2', 'GL_ARB_imaging', 'GL_ARB_multitexture', 'GL_ARB_texture_compression'])
    def endFeature(self):
        OutputGenerator.endFeature(self)
    def genType(self, typeinfo, name):
        OutputGenerator.genType(self, typeinfo, name)
    def genEnum(self, enuminfo, name):
        OutputGenerator.genEnum(self, enuminfo, name)
    def genCmd(self, cmd, name):
        OutputGenerator.genCmd(self, cmd, name)

        if not self.OldVersion:
            return

        # for GL functions which are in the ABI, generate a shim which calls the function via GetProcAddress
        rettype=ParseCmdRettype(cmd)
        self.outFile.write("%s %s("%(rettype, name))
        plist=ParseCmdParams(cmd)

        Comma=""
        if len(plist):
            for ptype, pname in plist:
                self.outFile.write("%s%s"%(Comma, ptype))
                Comma=", "
        else:
            self.outFile.write("void")

        self.outFile.write(")\n{\n")

        self.outFile.write('  typedef %s (* PFN%sPROC)(' % (rettype, name.upper()))

        if len(plist):
            Comma=""
            for ptype, pname in plist:
                self.outFile.write("%s %s"%(Comma, ptype))
                Comma=", "
        else:
            self.outFile.write("void")

        self.outFile.write(');\n')

        if rettype.lower()=="void":
            self.outFile.write('  RESOLVE(PFN%sPROC, "%s");\n'%(name.upper(), name))
            self.outFile.write('  RESOLVED_PROC(')
        else:
            self.outFile.write('  RESOLVE_RET(PFN%sPROC, "%s", 0);\n'%(name.upper(), name))
            self.outFile.write('  return RESOLVED_PROC(')

        Comma=""
        for ptype, pname in plist:
            self.outFile.write("%s%s"%(Comma, pname))
            Comma=", "

        self.outFile.write(" );\n}\n\n")

def genHeaders():
    outFile = open(outFilename,"w")

    if preresolve:
        gen = PreResolveOutputGenerator(errFile=errWarn,
                                        warnFile=errWarn,
                                        diagFile=diag)
        gen.outFile=outFile
        reg.setGenerator(gen)
        reg.apiGen(genOpts)

    if wrapper:
        gen = WrapperOutputGenerator(errFile=errWarn,
                                     warnFile=errWarn,
                                     diagFile=diag)
        gen.outFile=outFile
        reg.setGenerator(gen)
        reg.apiGen(genOpts)

    if shim:
        gen = ShimOutputGenerator(errFile=errWarn,
                                  warnFile=errWarn,
                                  diagFile=diag)
        gen.outFile=outFile
        reg.setGenerator(gen)
        reg.apiGen(genOpts)

    if thunk:
        gen = ThunkOutputGenerator(errFile=errWarn,
                                   warnFile=errWarn,
                                   diagFile=diag)
        gen.outFile=outFile
        reg.setGenerator(gen)
        reg.apiGen(genOpts)


    if thunkdefs:
        gen = ThunkDefsOutputGenerator(errFile=errWarn,
                                       warnFile=errWarn,
                                       diagFile=diag)
        gen.outFile=outFile
        reg.setGenerator(gen)
        reg.apiGen(genOpts)

    outFile.close()

genHeaders()
