#!/usr/bin/python3
#
# Copyright 2013-2023 The Khronos Group Inc.
# Copyright 2023-2024 Google Inc.
#
# SPDX-License-Identifier: Apache-2.0

import argparse
import os
import re
import sys
import xml.etree.ElementTree as etree

sys.path.append(os.path.abspath(os.path.dirname(__file__)))

from cgenerator import CGeneratorOptions, COutputGenerator

from generator import write, regSortFeatures
from reg import Registry

# gfxstream + cereal modules
from cerealgenerator import CerealGenerator, SUPPORTED_FEATURES

from typing import Optional

def cerealSortFeatures(featureList):
    regSortFeatures(featureList)
    featureList.sort(key=lambda f: 0 if f.name in SUPPORTED_FEATURES else 1)

def makeREstring(strings, default=None, strings_are_regex=False):
    """Turn a list of strings into a regexp string matching exactly those strings."""
    if strings or default is None:
        if not strings_are_regex:
            strings = (re.escape(s) for s in strings)
        return '^(' + '|'.join(strings) + ')$'
    return default


def makeGenOpts(args):
    """Returns a directory of [ generator function, generator options ] indexed
    by specified short names. The generator options incorporate the following
    parameters:

    args is an parsed argument object; see below for the fields that are used."""
    global genOpts
    genOpts = {}

    # Output target directory
    directory = args.directory

    # Descriptive names for various regexp patterns used to select
    # versions and extensions
    allFormats = allFeatures = allExtensions = r'.*'

    # Turn lists of names/patterns into matching regular expressions
    emitExtensionsPat    = makeREstring([], allExtensions)
    emitFormatsPat       = makeREstring([], allFormats)
    featuresPat          = makeREstring([], allFeatures)

    # Copyright text prefixing all headers (list of strings).
    # The SPDX formatting below works around constraints of the 'reuse' tool
    prefixStrings = [
        '/*',
        '** Copyright 2025 The Android Open Source Project',
        '** Copyright 2015-2023 The Khronos Group Inc.',
        '**',
        '** SPDX-License-Identifier' + ': Apache-2.0',
        '*/',
        ''
    ]

    # Text specific to Vulkan headers
    vkPrefixStrings = [
        '/*',
        '** This header is generated from the Khronos Vulkan XML API Registry.',
        '**',
        '*/',
        ''
    ]

    genOpts['cereal'] = [
            CerealGenerator,
            CGeneratorOptions(
                directory         = directory,
                versions          = featuresPat,
                emitversions      = featuresPat,
                addExtensions     = None,
                emitExtensions    = emitExtensionsPat,
                prefixText        = prefixStrings + vkPrefixStrings,
                apientry          = 'VKAPI_CALL ',
                apientryp         = 'VKAPI_PTR *',
                alignFuncParam    = 48,
                sortProcedure     = cerealSortFeatures)
        ]

    gfxstreamPrefixStrings = [
        '#pragma once',
        '#ifdef VK_GFXSTREAM_STRUCTURE_TYPE_EXT',
        '#include "vulkan_gfxstream_structure_type.h"',
        '#endif',
    ]

    # gfxstream specific header
    genOpts['vulkan_gfxstream.h'] = [
          COutputGenerator,
          CGeneratorOptions(
            filename          = 'vulkan_gfxstream.h',
            directory         = directory,
            versions          = featuresPat,
            emitversions      = None,
            addExtensions     = makeREstring(['VK_GOOGLE_gfxstream'], None),
            emitExtensions    = makeREstring(['VK_GOOGLE_gfxstream'], None),
            prefixText        = prefixStrings + vkPrefixStrings + gfxstreamPrefixStrings,
            # Use #pragma once in the prefixText instead, so that we can put the copyright comments
            # at the beginning of the file.
            apientry          = 'VKAPI_CALL ',
            apientryp         = 'VKAPI_PTR *',
            alignFuncParam    = 48)
        ]

def genTarget(args):
    """Create an API generator and corresponding generator options based on
    the requested target and command line options.

    This is encapsulated in a function so it can be profiled and/or timed.
    The args parameter is an parsed argument object containing the following
    fields that are used:

    - target - target to generate
    - directory - directory to generate it in
    - extensions - list of additional extensions to include in generated interfaces"""

    # Create generator options with parameters specified on command line
    makeGenOpts(args)

    # Select a generator matching the requested target
    if args.target in genOpts:
        createGenerator = genOpts[args.target][0]
        options = genOpts[args.target][1]

        gen = createGenerator(errFile=errWarn,
                              warnFile=errWarn,
                              diagFile=diag)
        return (gen, options)
    else:
        return None


# -feature name
# -extension name
# For both, "name" may be a single name, or a space-separated list
# of names, or a regular expression.
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-registry', action='store',
                        default='vk.xml',
                        help='Use specified registry file instead of vk.xml')
    parser.add_argument('-registryGfxstream', action='store',
                        default=None,
                        help='Use specified gfxstream registry file')
    parser.add_argument('-o', action='store', dest='directory',
                        default='.',
                        help='Create target and related files in specified directory')
    parser.add_argument('target', metavar='target', nargs='?',
                        help='Specify target')

    args = parser.parse_args()

    errWarn = sys.stderr
    diag = None

    # Create the API generator & generator options
    (gen, options) = genTarget(args)

    # Create the registry object with the specified generator and generator
    # options. The options are set before XML loading as they may affect it.
    reg = Registry(gen, options)

    # Parse the specified registry XML into an ElementTree object
    tree = etree.parse(args.registry)

    # Merge the gfxstream registry with the official Vulkan registry if the
    # target is the cereal generator
    if args.registryGfxstream is not None and args.target == 'cereal':
        treeGfxstream = etree.parse(args.registryGfxstream)
        treeRoot = tree.getroot()
        treeGfxstreamRoot = treeGfxstream.getroot()

        def getEntryName(entry) -> Optional[str]:
            name = entry.get("name")
            if name is not None:
                return name
            try:
                return entry.find("proto").find("name").text
            except AttributeError:
                return None

        for entriesName in ['types', 'commands', 'extensions']:
            treeEntries = treeRoot.find(entriesName)

            originalEntryDict = {}
            for entry in treeEntries:
                name = getEntryName(entry)
                if name is not None:
                    originalEntryDict[name] = entry

            for entry in treeGfxstreamRoot.find(entriesName):
                name = getEntryName(entry)
                # New entry, just append to entry list
                if name not in originalEntryDict.keys():
                    treeEntries.append(entry)
                    continue

                originalEntry = originalEntryDict[name]

                # Extending an existing entry. This happens for MVK.
                if entriesName == "extensions":
                    for key, value in entry.attrib.items():
                        originalEntry.set(key, value)
                    require = entry.find("require")
                    if require is not None:
                        for child in require:
                            originalEntry.find("require").append(child)
                    continue

                # Overwriting an existing entry. This happen for
                # VkNativeBufferANDROID
                if entriesName == "types" or entriesName == "commands":
                    originalEntry.clear()
                    originalEntry.attrib = entry.attrib
                    for child in entry:
                        originalEntry.append(child)

    # Load the XML tree into the registry object
    reg.loadElementTree(tree)
    reg.apiGen()
