#!/usr/bin/env python3

"""
Generates dispatch functions for EGL.

The list of functions and arguments is read from the Khronos's XML files, with
additional information defined in the module eglFunctionList.
"""

import argparse
import collections
import sys
import textwrap

import eglFunctionList
import genCommon

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("target", choices=("header", "source"),
            help="Whether to build the source or header file.")
    parser.add_argument("xml_files", nargs="+",
            help="The XML files with the EGL function lists.")

    args = parser.parse_args()

    xmlFunctions = genCommon.getFunctions(args.xml_files)
    xmlByName = dict((f.name, f) for f in xmlFunctions)
    functions = []
    for (name, eglFunc) in eglFunctionList.EGL_FUNCTIONS:
        func = xmlByName[name]
        eglFunc = fixupEglFunc(func, eglFunc)
        functions.append((func, eglFunc))

    # Sort the function list by name.
    functions = sorted(functions, key=lambda f: f[0].name)

    if args.target == "header":
        text = generateHeader(functions)
    elif args.target == "source":
        text = generateSource(functions)
    sys.stdout.write(text)

def fixupEglFunc(func, eglFunc):
    result = dict(eglFunc)
    if result.get("prefix") is None:
        result["prefix"] = ""

    if result.get("extension") is not None:
        text = "defined(" + result["extension"] + ")"
        result["extension"] = text

    if result["method"] in ("none", "custom"):
        return result

    if result["method"] not in ("display", "device", "current"):
        raise ValueError("Invalid dispatch method %r for function %r" % (result["method"], func.name))

    if func.hasReturn():
        if result.get("retval") is None:
            result["retval"] = getDefaultReturnValue(func.rt)

    return result

def generateHeader(functions):
    text = textwrap.dedent(r"""
    #ifndef G_EGLDISPATCH_STUBS_H
    #define G_EGLDISPATCH_STUBS_H

    #ifdef __cplusplus
    extern "C" {
    #endif

    #include <EGL/egl.h>
    #include <EGL/eglext.h>
    #include "glvnd/libeglabi.h"

    """.lstrip("\n"))

    text += "enum {\n"
    for (func, eglFunc) in functions:
        text += generateGuardBegin(func, eglFunc)
        text += "    __EGL_DISPATCH_" + func.name + ",\n"
        text += generateGuardEnd(func, eglFunc)
    text += "    __EGL_DISPATCH_COUNT\n"
    text += "};\n"

    for (func, eglFunc) in functions:
        if eglFunc["inheader"]:
            text += generateGuardBegin(func, eglFunc)
            text += "{f.rt} EGLAPIENTRY {ex[prefix]}{f.name}({f.decArgs});\n".format(f=func, ex=eglFunc)
            text += generateGuardEnd(func, eglFunc)

    text += textwrap.dedent(r"""
    #ifdef __cplusplus
    }
    #endif
    #endif // G_EGLDISPATCH_STUBS_H
    """)
    return text

def generateSource(functions):
    # First, sort the function list by name.
    text = ""
    text += '#include "egldispatchstubs.h"\n'
    text += '#include "g_egldispatchstubs.h"\n'
    text += '#include <stddef.h>\n'
    text += "\n"

    for (func, eglFunc) in functions:
        if eglFunc["method"] not in ("custom", "none"):
            text += generateGuardBegin(func, eglFunc)
            text += generateDispatchFunc(func, eglFunc)
            text += generateGuardEnd(func, eglFunc)

    text += "\n"
    text += "const char * const __EGL_DISPATCH_FUNC_NAMES[__EGL_DISPATCH_COUNT + 1] = {\n"
    for (func, eglFunc) in functions:
        text += generateGuardBegin(func, eglFunc)
        text += '    "' + func.name + '",\n'
        text += generateGuardEnd(func, eglFunc)
    text += "    NULL\n"
    text += "};\n"

    text += "const __eglMustCastToProperFunctionPointerType __EGL_DISPATCH_FUNCS[__EGL_DISPATCH_COUNT + 1] = {\n"
    for (func, eglFunc) in functions:
        text += generateGuardBegin(func, eglFunc)
        if eglFunc["method"] != "none":
            text += "    (__eglMustCastToProperFunctionPointerType) " + eglFunc.get("prefix", "") + func.name + ",\n"
        else:
            text += "    NULL, // " + func.name + "\n"
        text += generateGuardEnd(func, eglFunc)
    text += "    NULL\n"
    text += "};\n"

    return text

def generateGuardBegin(func, eglFunc):
    ext = eglFunc.get("extension")
    if ext is not None:
        return "#if " + ext + "\n"
    else:
        return ""

def generateGuardEnd(func, eglFunc):
    if eglFunc.get("extension") is not None:
        return "#endif\n"
    else:
        return ""

def generateDispatchFunc(func, eglFunc):
    text = ""

    if eglFunc.get("static"):
        text += "static "
    elif eglFunc.get("public"):
        text += "PUBLIC "
    text += textwrap.dedent(
    r"""
    {f.rt} EGLAPIENTRY {ef[prefix]}{f.name}({f.decArgs})
    {{
        typedef {f.rt} EGLAPIENTRY (* _pfn_{f.name})({f.decArgs});
    """).lstrip("\n").format(f=func, ef=eglFunc)

    if func.hasReturn():
        text += "    {f.rt} _ret = {ef[retval]};\n".format(f=func, ef=eglFunc)

    text += "    _pfn_{f.name} _ptr_{f.name} = (_pfn_{f.name}) ".format(f=func)
    if eglFunc["method"] == "current":
        text += "__eglDispatchFetchByCurrent(__EGL_DISPATCH_{f.name});\n".format(f=func)

    elif eglFunc["method"] in ("display", "device"):
        if eglFunc["method"] == "display":
            lookupFunc = "__eglDispatchFetchByDisplay"
            lookupType = "EGLDisplay"
        else:
            assert eglFunc["method"] == "device"
            lookupFunc = "__eglDispatchFetchByDevice"
            lookupType = "EGLDeviceEXT"

        lookupArg = None
        for arg in func.args:
            if arg.type == lookupType:
                lookupArg = arg.name
                break
        if lookupArg is None:
            raise ValueError("Can't find %s argument for function %s" % (lookupType, func.name,))

        text += "{lookupFunc}({lookupArg}, __EGL_DISPATCH_{f.name});\n".format(
                f=func, lookupFunc=lookupFunc, lookupArg=lookupArg)
    else:
        raise ValueError("Unknown dispatch method: %r" % (eglFunc["method"],))

    text += "    if(_ptr_{f.name} != NULL) {{\n".format(f=func)
    text += "        "
    if func.hasReturn():
        text += "_ret = "
    text += "_ptr_{f.name}({f.callArgs});\n".format(f=func)
    text += "    }\n"

    if func.hasReturn():
        text += "    return _ret;\n"
    text += "}\n"
    return text

def getDefaultReturnValue(typename):
    if typename.endswith("*"):
        return "NULL"
    elif typename == "EGLDisplay":
        return "EGL_NO_DISPLAY"
    elif typename == "EGLContext":
        return "EGL_NO_CONTEXT"
    elif typename == "EGLSurface":
        return "EGL_NO_SURFACE"
    elif typename == "EGLBoolean":
        return "EGL_FALSE";

    return "0"

if __name__ == "__main__":
    main()

