#!/usr/bin/python3
#
# Copyright 2018 The ANGLE Project Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# generate_loader.py:
#   Generates dynamic loaders for various binding interfaces.
#   NOTE: don't run this script directly. Run scripts/run_code_generation.py.

import sys, os, pprint, json
import registry_xml


DEFAULT_INTERNAL_PREFIX = "l_"


def write_header(data_source_name,
                 all_cmds,
                 api,
                 preamble,
                 path,
                 lib,
                 ns="",
                 prefix=None,
                 export="",
                 internal_prefix=DEFAULT_INTERNAL_PREFIX,
                 file_prefix=""):
    file_name = "%s%s_loader_autogen.h" % (file_prefix, api)
    header_path = registry_xml.path_to(path, file_name)

    def pre(cmd):
        if prefix == None:
            return cmd
        return prefix + cmd[len(api):]

    with open(header_path, "w") as out:
        defines = [
            "#define %s%s %s%s%s" % (ns, pre(cmd), internal_prefix, ns, pre(cmd))
            for cmd in all_cmds
        ]
        var_protos = [
            "%sextern PFN%sPROC %s%s%s;" % (export, cmd.upper(), internal_prefix, ns, pre(cmd))
            for cmd in all_cmds
        ]
        loader_header = template_loader_h.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name=data_source_name,
            defines="\n".join(defines),
            function_pointers="\n".join(var_protos),
            api_upper=api.upper(),
            api_lower=api,
            preamble=preamble,
            export=export,
            lib=lib.upper(),
            load_fn_name="Load%s%s" % (prefix if prefix else "", api.upper()),
            file_prefix=file_prefix)

        out.write(loader_header)
        out.close()


def write_source(data_source_name,
                 all_cmds,
                 api,
                 path,
                 ns="",
                 prefix=None,
                 export="",
                 internal_prefix=DEFAULT_INTERNAL_PREFIX,
                 file_prefix=""):
    file_name = "%s%s_loader_autogen.cpp" % (file_prefix, api)
    source_path = registry_xml.path_to(path, file_name)

    def pre(cmd):
        if prefix == None:
            return cmd
        return prefix + cmd[len(api):]

    with open(source_path, "w") as out:
        var_defs = [
            "%sPFN%sPROC %s%s%s;" % (export, cmd.upper(), internal_prefix, ns, pre(cmd))
            for cmd in all_cmds
        ]

        setter = "    %s%s%s = reinterpret_cast<PFN%sPROC>(loadProc(\"%s\"));"
        setters = [
            setter % (internal_prefix, ns, pre(cmd), cmd.upper(), pre(cmd)) for cmd in all_cmds
        ]

        loader_source = template_loader_cpp.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name=data_source_name,
            function_pointers="\n".join(var_defs),
            set_pointers="\n".join(setters),
            api_upper=api.upper(),
            api_lower=api,
            load_fn_name="Load%s%s" % (prefix if prefix else "", api.upper()),
            file_prefix=file_prefix)

        out.write(loader_source)
        out.close()


def gen_libcl_loader():

    xml = registry_xml.RegistryXML("cl.xml")
    for major_version, minor_version in registry_xml.CL_VERSIONS:
        name_prefix = "CL_VERSION_"
        annotation = "%d_%d" % (major_version, minor_version)
        feature_name = "%s%s" % (name_prefix, annotation)
        xml.AddCommands(feature_name, annotation)
    all_cmds = xml.all_cmd_names.get_all_commands()

    path = os.path.join("..", "src", "libOpenCL")
    source_path = registry_xml.path_to(path, "cl_loader_autogen.cpp")

    with open(source_path, "w") as out:
        # TODO(jplate): use first setters after migration in http://anglebug.com/5759
        # setter = "    cl_loader.%s = reinterpret_cast<cl_api_%s>(loadProc(\"CL_%s\"));"
        # setters = [setter % (cmd, cmd, cmd[2:]) for cmd in all_cmds]
        setter = "    cl_loader.%s = CL_%s;"
        setters = [setter % (cmd, cmd[2:]) for cmd in all_cmds]

        loader_source = template_cl_loader_cpp.format(
            script_name=os.path.basename(sys.argv[0]),
            data_source_name="cl.xml",
            set_pointers="\n".join(setters))

        out.write(loader_source)
        out.close()


def gen_libegl_loader():

    data_source_name = "egl.xml and egl_angle_ext.xml"
    xml = registry_xml.RegistryXML("egl.xml", "egl_angle_ext.xml")

    for major_version, minor_version in [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5]]:
        annotation = "{}_{}".format(major_version, minor_version)
        name_prefix = "EGL_VERSION_"

        feature_name = "{}{}".format(name_prefix, annotation)

        xml.AddCommands(feature_name, annotation)

    xml.AddExtensionCommands(registry_xml.supported_egl_extensions, ['egl'])

    all_cmds = xml.all_cmd_names.get_all_commands()

    path = os.path.join("..", "src", "libEGL")
    write_header(
        data_source_name,
        all_cmds,
        "egl",
        libegl_preamble,
        path,
        "LIBEGL",
        prefix="EGL_",
        export="ANGLE_NO_EXPORT ")
    write_source(data_source_name, all_cmds, "egl", path, prefix="EGL_")


def gen_gles_loader(gles_preamble, path, header_lib, export, internal_prefix, file_prefix):

    data_source_name = "gl.xml and gl_angle_ext.xml"
    xml = registry_xml.RegistryXML("gl.xml", "gl_angle_ext.xml")

    # First run through the main GLES entry points.  Since ES2+ is the primary use
    # case, we go through those first and then add ES1-only APIs at the end.
    for major_version, minor_version in [[2, 0], [3, 0], [3, 1], [3, 2], [1, 0]]:
        annotation = "{}_{}".format(major_version, minor_version)
        name_prefix = "GL_ES_VERSION_"

        is_gles1 = major_version == 1
        if is_gles1:
            name_prefix = "GL_VERSION_ES_CM_"

        feature_name = "{}{}".format(name_prefix, annotation)

        xml.AddCommands(feature_name, annotation)

    xml.AddExtensionCommands(registry_xml.supported_extensions, ['gles2', 'gles1'])

    all_cmds = xml.all_cmd_names.get_all_commands()

    if registry_xml.support_EGL_ANGLE_explicit_context:
        all_cmds += [cmd + "ContextANGLE" for cmd in xml.all_cmd_names.get_all_commands()]

    # Ensure there are no duplicates
    assert (len(all_cmds) == len(set(all_cmds))), "Duplicate command names found"

    write_header(
        data_source_name,
        all_cmds,
        "gles",
        gles_preamble,
        path,
        header_lib,
        export=export,
        internal_prefix=internal_prefix,
        file_prefix=file_prefix)
    write_source(
        data_source_name,
        all_cmds,
        "gles",
        path,
        export=export,
        internal_prefix=internal_prefix,
        file_prefix=file_prefix)


def gen_egl_loader(egl_preamble, path, header_lib, export, internal_prefix, file_prefix):

    data_source_name = "egl.xml and egl_angle_ext.xml"
    xml = registry_xml.RegistryXML("egl.xml", "egl_angle_ext.xml")

    for major_version, minor_version in [[1, 0], [1, 1], [1, 2], [1, 3], [1, 4], [1, 5]]:
        annotation = "{}_{}".format(major_version, minor_version)
        name_prefix = "EGL_VERSION_"

        feature_name = "{}{}".format(name_prefix, annotation)

        xml.AddCommands(feature_name, annotation)

    xml.AddExtensionCommands(registry_xml.supported_egl_extensions, ['egl'])

    all_cmds = xml.all_cmd_names.get_all_commands()

    write_header(
        data_source_name,
        all_cmds,
        "egl",
        egl_preamble,
        path,
        header_lib,
        export=export,
        internal_prefix=internal_prefix,
        file_prefix=file_prefix)
    write_source(
        data_source_name,
        all_cmds,
        "egl",
        path,
        export=export,
        internal_prefix=internal_prefix,
        file_prefix=file_prefix)


def gen_util_gles_and_egl_loaders():
    path = os.path.join("..", "util")
    export = "ANGLE_UTIL_EXPORT "
    lib = "UTIL"
    gen_gles_loader(util_gles_preamble, path, lib, export, DEFAULT_INTERNAL_PREFIX, "")
    gen_egl_loader(util_egl_preamble, path, lib, export, DEFAULT_INTERNAL_PREFIX, "")


def gen_trace_gles_and_egl_loaders():
    path = os.path.join("..", "src", "tests", "restricted_traces")
    export = "ANGLE_TRACE_LOADER_EXPORT "
    lib = "ANGLE_RESTRICTED_TRACES"
    gen_gles_loader(trace_gles_preamble, path, lib, export, "t_", "trace_")
    gen_egl_loader(trace_egl_preamble, path, lib, export, "t_", "trace_")


def gen_util_wgl_loader():

    supported_wgl_extensions = [
        "WGL_ARB_create_context",
        "WGL_ARB_extensions_string",
        "WGL_EXT_swap_control",
    ]

    source = "wgl.xml"
    xml = registry_xml.RegistryXML(source)

    for major_version, minor_version in [[1, 0]]:
        annotation = "{}_{}".format(major_version, minor_version)
        name_prefix = "WGL_VERSION_"

        feature_name = "{}{}".format(name_prefix, annotation)

        xml.AddCommands(feature_name, annotation)

    xml.AddExtensionCommands(supported_wgl_extensions, ['wgl'])

    all_cmds = xml.all_cmd_names.get_all_commands()

    path = os.path.join("..", "util", "windows")
    write_header(source, all_cmds, "wgl", util_wgl_preamble, path, "UTIL_WINDOWS", "_")
    write_source(source, all_cmds, "wgl", path, "_")


def main():

    # Handle inputs/outputs for run_code_generation.py's auto_script
    if len(sys.argv) > 1:
        inputs = registry_xml.xml_inputs
        outputs = [
            '../src/libOpenCL/cl_loader_autogen.cpp',
            '../src/libEGL/egl_loader_autogen.cpp',
            '../src/libEGL/egl_loader_autogen.h',
            '../util/egl_loader_autogen.cpp',
            '../util/egl_loader_autogen.h',
            '../util/gles_loader_autogen.cpp',
            '../util/gles_loader_autogen.h',
            '../util/windows/wgl_loader_autogen.cpp',
            '../util/windows/wgl_loader_autogen.h',
            '../src/tests/restricted_traces/trace_egl_loader_autogen.cpp',
            '../src/tests/restricted_traces/trace_egl_loader_autogen.h',
            '../src/tests/restricted_traces/trace_gles_loader_autogen.cpp',
            '../src/tests/restricted_traces/trace_gles_loader_autogen.h',
        ]

        if sys.argv[1] == 'inputs':
            print(','.join(inputs))
        elif sys.argv[1] == 'outputs':
            print(','.join(outputs))
        else:
            print('Invalid script parameters')
            return 1
        return 0

    gen_libcl_loader()
    gen_libegl_loader()
    gen_util_gles_and_egl_loaders()
    gen_util_wgl_loader()
    gen_trace_gles_and_egl_loaders()
    return 0


libegl_preamble = """#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <export.h>
"""

util_gles_preamble = """#if defined(GL_GLES_PROTOTYPES) && GL_GLES_PROTOTYPES
#error "Don't define GL prototypes if you want to use a loader!"
#endif  // defined(GL_GLES_PROTOTYPES)

#include "angle_gl.h"
#include "util/util_export.h"
"""

util_egl_preamble = """#include "util/util_export.h"

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

trace_gles_preamble = """#if defined(GL_GLES_PROTOTYPES) && GL_GLES_PROTOTYPES
#error "Don't define GL prototypes if you want to use a loader!"
#endif  // defined(GL_GLES_PROTOTYPES)

#include "angle_gl.h"
#include "restricted_traces_autogen.h"
"""

trace_egl_preamble = """#include "restricted_traces_autogen.h"

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

util_wgl_preamble = """
#include <WGL/wgl.h>
#include <GLES2/gl2.h>

// We add an underscore before each function name to ensure common names like "ChoosePixelFormat"
// and "SwapBuffers" don't conflict with our function pointers. We can't use a namespace because
// some functions conflict with preprocessor definitions.
"""

template_loader_h = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}.
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// {api_lower}_loader_autogen.h:
//   Simple {api_upper} function loader.

#ifndef {lib}_{api_upper}_LOADER_AUTOGEN_H_
#define {lib}_{api_upper}_LOADER_AUTOGEN_H_

{preamble}
{defines}
{function_pointers}

namespace {file_prefix}angle
{{
using GenericProc = void (*)();
using LoadProc = GenericProc (KHRONOS_APIENTRY *)(const char *);
{export}void {load_fn_name}(LoadProc loadProc);
}}  // namespace angle

#endif  // {lib}_{api_upper}_LOADER_AUTOGEN_H_
"""

template_loader_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}.
//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// {api_lower}_loader_autogen.cpp:
//   Simple {api_upper} function loader.

#include "{file_prefix}{api_lower}_loader_autogen.h"

{function_pointers}

namespace {file_prefix}angle
{{
void {load_fn_name}(LoadProc loadProc)
{{
{set_pointers}
}}
}}  // namespace angle
"""

template_cl_loader_cpp = """// GENERATED FILE - DO NOT EDIT.
// Generated by {script_name} using data from {data_source_name}.
//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// cl_loader_autogen.cpp:
//   Simple CL function loader.

#include "cl_loader.h"

// TODO(jplate): remove include after entry points moved to GLESV2 lib http://anglebug.com/5759
#include "entry_points_cl_autogen.h"

cl_icd_dispatch cl_loader;

namespace angle
{{
void LoadCL(LoadProc loadProc)
{{
{set_pointers}
}}
}}  // namespace angle
"""

if __name__ == '__main__':
    sys.exit(main())
