#!/usr/bin/env python

# Copyright 2015 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.

# Utility functions used to parse a list of DLL entry points.
# Expected format:
#
#   <empty-line>   -> ignored
#   #<comment>     -> ignored
#   %<verbatim>    -> verbatim output for header files.
#   !<prefix>      -> prefix name for header files.
#   <return-type> <function-name> <signature> ; -> entry point declaration.
#
# Anything else is an error.

import re
import sys
import argparse

re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""")
re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_\[\]]*)$""")

class Entry:
    """Small class used to model a single DLL entry point."""
    def __init__(self, func_name, return_type, parameters):
        """Initialize Entry instance. |func_name| is the function name,
           |return_type| its return type, and |parameters| is a list of
           (type,name) tuples from the entry's signature.
        """
        self.__name__ = func_name
        self.return_type = return_type
        self.parameters = ""
        self.vartypes = []
        self.varnames = []
        self.call = ""
        comma = ""
        for param in parameters:
            self.vartypes.append(param[0])
            self.varnames.append(param[1])
            self.parameters += "%s%s %s" % (comma, param[0], param[1])
            self.call += "%s%s" % (comma, param[1])
            comma = ", "

def banner_command(argv):
    """Return sanitized command-line description.
       |argv| must be a list of command-line parameters, e.g. sys.argv.
       Return a string corresponding to the command, with platform-specific
       paths removed."""

    # Remove path from first parameter
    argv = argv[:]
    argv[0] = "android/scripts/gen-entries.py"
    return ' '.join(argv)

def parse_entries_file(lines):
    """Parse an .entries file and return a tuple of:
        entries: list of Entry instances from the file.
        prefix_name: prefix name from the file, or None.
        verbatim: list of verbatim lines from the file.
        errors: list of errors in the file, prefixed by line number.
    """
    entries = []
    verbatim = []
    errors = []
    lineno = 0
    prefix_name = None
    namespaces = []
    for line in lines:
        lineno += 1
        line = line.strip()
        if len(line) == 0:  # Ignore empty lines
            continue
        if line[0] == '#':  # Ignore comments
            continue
        if line[0] == '!':  # Prefix name
            prefix_name = line[1:]
            continue
        if line[0] == '%':  # Verbatim line copy
            verbatim.append(line[1:])
            continue
        if line.startswith("namespaces"): # Namespaces
            namespaces = list(map(lambda t: t.strip(), line.split("namespaces")[1].strip().split(",")))
            continue
        # Must be a function signature.
        m = re_func.match(line)
        if not m:
            errors.append("%d: '%s'" % (lineno, line))
            continue

        return_type, func_name, parameters = m.groups()
        return_type = return_type.strip()
        parameters = parameters.strip()
        params = []
        failure = False
        if parameters != "void":
            for parameter in parameters.split(','):
                parameter = parameter.strip()
                m = re_param.match(parameter)
                if not m:
                    errors.append("%d: parameter '%s'" % (lineno, parameter))
                    failure = True
                    break
                else:
                    param_type, param_name = m.groups()
                    params.append((param_type.strip(), param_name.strip()))

        if not failure:
            entries.append(Entry(func_name, return_type, params))

    return (entries, prefix_name, verbatim, namespaces, errors)


def gen_functions_header(entries, prefix_name, verbatim, filename, with_args):
    """Generate a C header containing a macro listing all entry points.
       |entries| is a list of Entry instances.
       |prefix_name| is a prefix-name, it will be converted to upper-case.
       |verbatim| is a list of verbatim lines that must appear before the
       macro declaration. Useful to insert #include <> statements.
       |filename| is the name of the original file.
    """
    prefix_name = prefix_name.upper()

    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// DO NOT EDIT THIS FILE")
    print("")
    print("#ifndef %s_FUNCTIONS_H" % prefix_name)
    print("#define %s_FUNCTIONS_H" % prefix_name)
    print("")
    for line in verbatim:
        print(line)

    print("#define LIST_%s_FUNCTIONS(X) \\" % prefix_name)

    new_header_apis = [
        # AEMU private exts
        "eglGetMaxGLESVersion",
        "eglBlitFromCurrentReadBufferANDROID",
        "eglSetImageFenceANDROID",
        "eglWaitImageFenceANDROID",
        "eglAddLibrarySearchPathANDROID",
        "eglQueryVulkanInteropSupportANDROID",
        "eglQueryVulkanInteropSupportANDROID",
        # For snapshotting
        "eglLoadConfig",
        "eglLoadContext",
        "eglLoadAllImages",
        "eglSaveConfig",
        "eglSaveContext",
        "eglSaveAllImages",
        "eglPreSaveContext",
        "eglPostLoadAllImages",
        "eglPostSaveContext",
        "eglUseOsEglApi",
        "eglFillUsages",
        "eglSetMaxGLESVersion",
    ]

    need_decls = []

    for entry in entries:
        if entry.__name__ in new_header_apis:
            need_decls.append(entry)

        if with_args:
            print("  X(%s, %s, (%s), (%s)) \\" % \
                    (entry.return_type, entry.__name__, entry.parameters,
                     entry.call))
        else:
            print("  X(%s, %s, (%s)) \\" % \
                    (entry.return_type, entry.__name__, entry.parameters))


    print("")

    for entry in need_decls:
        print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters))

    print("")
    print("#endif  // %s_FUNCTIONS_H" % prefix_name)

def gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, with_args):
    """Generate a C++ header containing a header for |entries|
       with nesting inside |namespaces|.
       |prefix_name| is a prefix-name, it will be converted to upper-case.
       |verbatim| is a list of verbatim lines that must appear before the
       macro declaration. Useful to insert #include <> statements.
       |filename| is the name of the original file.
    """
    prefix_name = prefix_name.upper()

    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// DO NOT EDIT THIS FILE")
    print("")
    print("#pragma once")
    print("")
    for line in verbatim:
        print(line)

    for ns in namespaces:
        print("namespace %s {" % ns)

    for entry in entries:
        if "gles" in filename:
            print("GL_APICALL %s GL_APIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters))
        else:
            print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters))

    for ns in namespaces:
        print("} // namespace %s" % ns)

def gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, with_args):
    """Generate a C++ header containing a header for |entries|
       with nesting inside |namespaces|.
       |prefix_name| is a prefix-name, it will be converted to upper-case.
       |verbatim| is a list of verbatim lines that must appear before the
       macro declaration. Useful to insert #include <> statements.
       |filename| is the name of the original file.
    """
    prefix_name = prefix_name.upper()

    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// DO NOT EDIT THIS FILE")
    print("")
    for line in verbatim:
        print(line)

    for ns in namespaces:
        print("namespace %s {" % ns)

    for entry in entries:
        if "gles" in filename:
            if "void" == entry.return_type:
                return_part = "return"
            else:
                return_part = "return (%s)0" % entry.return_type

            print("GL_APICALL %s GL_APIENTRY %s(%s) { %s; }" % (entry.return_type, entry.__name__, ", ".join(entry.vartypes), return_part))
        else:
            print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters))

    namespaces.reverse()

    for ns in namespaces:
        print("} // namespace %s" % ns)

# The purpose of gen_translator()
# is to quickly generate implementations on the host Translator,
# which processes commands that just got onto the renderthread off goldfish pipe
# and are fed to system OpenGL.

def gen_translator(entries):
    # Definitions for custom implementation bodies go in
    # android/scripts/gles3translatorgen/gles30_custom.py
    # android/scripts/gles3translatorgen/gles31_custom.py
    from gles3translatorgen import gles30_custom
    from gles3translatorgen import gles31_custom

    translator_custom_share_processing = { }
    for (k, v) in list(gles30_custom.custom_share_processing.items()):
        translator_custom_share_processing[k] = v
    for (k, v) in list(gles31_custom.custom_share_processing.items()):
        translator_custom_share_processing[k] = v

    translator_custom_pre = { }
    for (k, v) in list(gles30_custom.custom_preprocesses.items()):
        translator_custom_pre[k] = v
    for (k, v) in list(gles31_custom.custom_preprocesses.items()):
        translator_custom_pre[k] = v

    translator_custom_post = { }
    for (k, v) in list(gles30_custom.custom_postprocesses.items()):
        translator_custom_post[k] = v
    for (k, v) in list(gles31_custom.custom_postprocesses.items()):
        translator_custom_post[k] = v

    translator_no_passthrough = {}
    for (k, v) in list(gles30_custom.no_passthrough.items()):
        translator_no_passthrough[k] = v
    for (k, v) in list(gles31_custom.no_passthrough.items()):
        translator_no_passthrough[k] = v

    translator_needexternc = {
            "glGetStringi": 1,
            "glUniform4ui": 1,
            "glGetUniformIndices": 1,
            "glTransformFeedbackVaryings": 1,
            "glCreateShaderProgramv": 1,
            "glProgramUniform2ui": 1,
            "glProgramUniform3ui": 1,
            "glProgramUniform4ui": 1,
            "glBindVertexBuffer": 1,
    };
    translator_nocontext_fail_codes = {
            "glClientWaitSync" : "GL_WAIT_FAILED",
    };
    def needExternC(entry):
        if entry.__name__ in translator_needexternc:
            return "extern \"C\" "
        else:
            return ""
    def get_fail_code(entry):
        if entry.__name__ in translator_nocontext_fail_codes:
            return translator_nocontext_fail_codes[entry.__name__];
        else:
            return "0"
    def gen_cxt_getter(entry):
        if (entry.return_type == "void"):
            print("    GET_CTX_V2();")
        else:
            print("    GET_CTX_V2_RET(%s);" % get_fail_code(entry))

    def gen_validations_custom_impl(entry):
        isGen = entry.__name__.startswith("glGen")
        isDelete = entry.__name__.startswith("glDelete")
        isBufferOp = "Buffer" in entry.__name__

        hasTargetArg = "target" in entry.varnames
        hasProgramArg = "program" in entry.varnames

        def mySetError(condition, glerr):
            if entry.return_type == "void":
                return "SET_ERROR_IF(%s,%s)" % (condition, glerr);
            else:
                return "RET_AND_SET_ERROR_IF(%s,%s,%s)" % (condition, glerr, get_fail_code(entry));

        if (isGen or isDelete) and ("n" in entry.varnames):
            print("    %s;" % mySetError("n < 0", "GL_INVALID_VALUE"));
        if (isBufferOp and hasTargetArg):
            print("    %s;" % mySetError("!GLESv2Validate::bufferTarget(ctx, target)", "GL_INVALID_ENUM"));
        if entry.__name__ in translator_custom_pre:
            print(translator_custom_pre[entry.__name__])

    def gen_call_ret(entry):
        globalNameTypes = {
                ("GLuint", "program") : "NamedObjectType::SHADER_OR_PROGRAM",
                ("GLuint", "texture") : "NamedObjectType::TEXTURE",
                ("GLuint", "buffer") : "NamedObjectType::VERTEXBUFFER",
                ("GLuint", "sampler") : "NamedObjectType::SAMPLER",
                ("GLuint", "query") : "NamedObjectType::QUERY",
        }
        globalNames = {
                ("GLuint", "program") : "globalProgramName",
                ("GLuint", "texture") : "globalTextureName",
                ("GLuint", "buffer") : "globalBufferName",
                ("GLuint", "sampler") : "globalSampler",
                ("GLuint", "query") : "globalQuery",
        }

        needsShareGroup = False
        for v in zip(entry.vartypes, entry.varnames):
            if v in list(globalNameTypes.keys()):
                needsShareGroup = True

        if needsShareGroup:
            print("    if (ctx->shareGroup().get()) {")
            for key in zip(entry.vartypes, entry.varnames):
                vartype, varname = key
                if key in globalNames:
                    print("        const GLuint %s = ctx->shareGroup()->getGlobalName(%s, %s);" % (globalNames[key], globalNameTypes[key], varname))

        globalCall = ", ".join([globalNames.get(k, k[1]) for k in zip(entry.vartypes, entry.varnames)])

        if needsShareGroup and entry.__name__ in translator_custom_share_processing:
            print(translator_custom_share_processing[entry.__name__])

        if (entry.return_type == "void"):
            if (needsShareGroup):
                print("   ")

            if entry.__name__ not in translator_no_passthrough:
                print("    ctx->dispatcher().%s(%s);" % (entry.__name__, globalCall))

            if needsShareGroup:
                print("    }")
            if entry.__name__ in translator_custom_post:
                print(translator_custom_post[entry.__name__]);
        else:
            if (needsShareGroup):
                print("   ")
            if entry.__name__ not in translator_no_passthrough:
                print("    %s %s = ctx->dispatcher().%s(%s);" % (entry.return_type, entry.__name__ + "RET", entry.__name__, globalCall))
            else:
                print("    %s %s = %s" % (entry.return_type, entry_func_name + "RET", get_fail_code(entry)))

            if entry.__name__ in translator_custom_post:
                print(translator_custom_post[entry.__name__]);

            print("    return %s;" % (entry.__name__ + "RET"));
            if needsShareGroup:
                print("    } else return %s;" % (get_fail_code(entry)))

    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// This file is best left unedited.")
    print("// Try to make changes through gen_translator in gen-entries.py,")
    print("// and/or parcel out custom functionality in separate code.")
    for entry in entries:
        print("%sGL_APICALL %s GL_APIENTRY %s(%s) {" % (needExternC(entry), entry.return_type, entry.__name__, entry.parameters))
        gen_cxt_getter(entry);
        gen_validations_custom_impl(entry);
        gen_call_ret(entry);
        print("}\n")

def gen_dll_wrapper(entries, prefix_name, verbatim, filename):
    """Generate a C source file that contains functions that act as wrappers
       for entry points located in another shared library. This allows the
       code that calls these functions to perform lazy-linking to system
       libraries.
       |entries|, |prefix_name|, |verbatim| and |filename| are the same as
       for gen_functions_header() above.
    """
    upper_name = prefix_name.upper()

    ENTRY_PREFIX = "__dll_"

    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// DO NOT EDIT THIS FILE")
    print("")
    print("#include <dlfcn.h>")
    for line in verbatim:
        print(line)

    print("")
    print("///")
    print("///  W R A P P E R   P O I N T E R S")
    print("///")
    print("")
    for entry in entries:
        ptr_name = ENTRY_PREFIX + entry.__name__
        print("static %s (*%s)(%s) = 0;" % \
                (entry.return_type, ptr_name, entry.parameters))

    print("")
    print("///")
    print("///  W R A P P E R   F U N C T I O N S")
    print("///")
    print("")

    for entry in entries:
        print("%s %s(%s) {" % \
                (entry.return_type, entry.__name__, entry.parameters))
        ptr_name = ENTRY_PREFIX + entry.__name__
        if entry.return_type != "void":
            print("  return %s(%s);" % (ptr_name, entry.call))
        else:
            print("  %s(%s);" % (ptr_name, entry.call))
        print("}\n")

    print("")
    print("///")
    print("///  I N I T I A L I Z A T I O N   F U N C T I O N")
    print("///")
    print("")

    print("int %s_dynlink_init(void* lib) {" % prefix_name)
    for entry in entries:
        ptr_name = ENTRY_PREFIX + entry.__name__
        print("  %s = (%s(*)(%s))dlsym(lib, \"%s\");" % \
                (ptr_name,
                 entry.return_type,
                 entry.parameters,
                 entry.__name__))
        print("  if (!%s) return -1;" % ptr_name)
    print("  return 0;")
    print("}")


def gen_windows_def_file(entries):
    """Generate a windows DLL .def file. |entries| is a list of Entry instances.
    """
    print("EXPORTS")
    for entry in entries:
        print("    %s" % entry.__name__)


def gen_unix_sym_file(entries):
    """Generate an ELF linker version file. |entries| is a list of Entry
       instances.
    """
    print("VERSION {")
    print("\tglobal:")
    for entry in entries:
        print("\t\t%s;" % entry.__name__)
    print("\tlocal:")
    print("\t\t*;")
    print("};")

def gen_symbols(entries, underscore):
    """Generate a list of symbols from |entries|, a list of Entry instances.
       |underscore| is a boolean. If True, then prepend an underscore to each
       symbol name.
    """
    prefix = ""
    if underscore:
        prefix = "_"
    for entry in entries:
        print("%s%s" % (prefix, entry.__name__))


VARTYPE_TO_PRINT_FORMAT = {
    'GLenum': '0x%X',
    'GLboolean': '%d',
    'GLbitfield': '%d',
    'GLvoid': '%d',
    'GLubyte': '%u',
    'GLbyte': '%c',
    'GLshort': '%d',
    'GLushort': '%d',
    'GLint': '%d',
    'GLuint': '%d',
    'GLclampx': '%d',
    'GLsizei': '%d',
    'GLfloat': '%f',
    'GLclampf': '%f',
    'GLdouble': '%f',
    'GLclampd': '%f',
    'GLchar': '%c',
    'GLcharARB': '%c',
    'GLfixed': '%d',
    'GLintptr': '%ld',
    'GLsizeiptr': '%ld',
    'GLsync': '%p',
    'GLuint64': "%lu",
    'GLeglImageOES': '%p',
}

def get_printf_format(var_type, var_name):
    if '*' in var_type:
        return "%p"

    if '[' in var_name:
        return "%p"

    # Function pointer
    if 'PROC' in var_type:
        return "%p"

    return VARTYPE_TO_PRINT_FORMAT[var_type]

def get_printf_name(var_name):
    if '[' in var_name:
        return var_name[:var_name.index('[')]

    return var_name

def gen_dispatch_logging_wrappers(entries):
    print("// Auto-generated with: %s" % banner_command(sys.argv))
    print("// DO NOT EDIT THIS FILE")
    print("")

    for entry in entries:
        print("%s %s_dispatchLoggingWrapper(%s) {" % \
                (entry.return_type, entry.__name__, entry.parameters))

        print_var_formats = []
        print_var_names = []
        for (param_type, param_name) in zip(entry.vartypes, entry.varnames):
            print_var_formats.append("%s:%s" % (param_name, get_printf_format(param_type, param_name)))
            print_var_names.append(get_printf_name(param_name))

        optional_comma = ", " if print_var_formats else ""
        print("\tDISPATCH_DEBUG_LOG(\"%s(%s)\"%s%s);"
                % (entry.__name__, ", ".join(print_var_formats), optional_comma, ", ".join(print_var_names)))

        if entry.return_type == "void":
            optional_return = ""
        else:
            optional_return = "return "

        print("\t%sGLDispatch::%s_underlying(%s);" % (optional_return, entry.__name__, ", ".join(print_var_names)))
        print("}")
        print("")


def parse_file(filename, lines, mode):
    """Generate one of possible outputs from |filename|. |lines| must be a list
       of text lines from the file, and |mode| is one of the --mode option
       values.
    """
    entries, prefix_name, verbatim, namespaces, errors = parse_entries_file(lines)
    if errors:
        for error in errors:
            sys.stderr.write("ERROR: %s:%s" % (filename, error))
        sys.exit(1)

    if not prefix_name:
        prefix_name = "unknown"

    if mode == 'def':
        gen_windows_def_file(entries)
    elif mode == 'sym':
        gen_unix_sym_file(entries)
    elif mode == 'translator_passthrough':
        gen_translator(entries)
    elif mode == 'wrapper':
        gen_dll_wrapper(entries, prefix_name, verbatim, filename)
    elif mode == 'symbols':
        gen_symbols(entries, False)
    elif mode == '_symbols':
        gen_symbols(entries, True)
    elif mode == 'functions':
        gen_functions_header(entries, prefix_name, verbatim, filename, False)
    elif mode == 'funcargs':
        gen_functions_header(entries, prefix_name, verbatim, filename, True)
    elif mode == 'static_translator_namespaced_header':
        gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, True)
    elif mode == 'static_translator_namespaced_stubs':
        gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, True)
    elif mode == 'dispatch_logging_wrappers':
        gen_dispatch_logging_wrappers(entries)

# List of valid --mode option values.
mode_list = [
    'def', 'sym', 'translator_passthrough', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs', 'static_translator_namespaced_header', 'static_translator_namespaced_stubs', 'dispatch_logging_wrappers',
]

# Argument parsing.
parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description="""\
A script used to parse an .entries input file containing a list of function
declarations, and generate various output files depending on the value of
the --mode option, which can be:

  def        Generate a windows DLL .def file.
  sym        Generate a Unix .so linker script.
  wrapper    Generate a C source file containing wrapper functions.
  symbols    Generate a simple list of symbols, one per line.
  _symbols   Generate a simple list of symbols, prefixed with _.
  functions  Generate a C header containing a macro listing all functions.
  funcargs   Like 'functions', but adds function call arguments to listing.
  static_translator_namespaced_header Generate C++ header with namespaced versions of the api declarations.
  dispatch_logging_wrappers Generate C++ functions which debug log the function with arguments and then call the underlying function.

""")
parser.add_argument("--mode", help="Output mode", choices=mode_list)
parser.add_argument("--output", help="output file")
parser.add_argument("file", help=".entries file path")

args = parser.parse_args()

if not args.mode:
    sys,stderr.write("ERROR: Please use --mode=<name>, see --help.")
    sys.exit(1)

if args.output:
    sys.stdout = open(args.output, "w+")

if args.file == '--':
    parse_file("<stdin>", sys.stdin, args.mode)
else:
    parse_file(args.file, open(args.file), args.mode)
