#     Copyright 2026, Kay Hayen, mailto:kay.hayen@gmail.com find license text at end of file


"""This tool is generating code variants for helper codes from Jinja templates."""

from nuitka.States import states

states.is_full_compat = False

# isort:start

import os
import re

import nuitka.specs.BuiltinBytesOperationSpecs
import nuitka.specs.BuiltinDictOperationSpecs
import nuitka.specs.BuiltinListOperationSpecs
import nuitka.specs.BuiltinStrOperationSpecs
import nuitka.specs.BuiltinUnicodeOperationSpecs
from nuitka.build.AdaptPythonHeaderFiles import getOffsetsJsonRequiredKeys
from nuitka.code_generation.BinaryOperationHelperDefinitions import (
    getSpecializedBinaryOperations,
    isCommutativeOperation,
    parseTypesFromHelper,
)
from nuitka.code_generation.c_types.CTypePyObjectPointers import (
    make_list_constant_direct_threshold,
    make_list_constant_hinted_threshold,
)
from nuitka.code_generation.CallCodes import (
    getQuickCallCode,
    getQuickMethodCallCode,
    getQuickMethodDescriptorCallCode,
    getQuickMixedCallCode,
    getTemplateCodeDeclaredFunction,
    max_quick_call,
)
from nuitka.code_generation.ComparisonHelperDefinitions import (
    getSpecializedComparisonOperations,
)
from nuitka.code_generation.ImportCodes import getImportModuleHardCodeName
from nuitka.containers.OrderedSets import OrderedSet
from nuitka.HardImportRegistry import (
    hard_modules,
    hard_modules_non_stdlib,
    hard_modules_version,
)
from nuitka.nodes.shapes.BuiltinTypeShapes import (
    tshape_bool,
    tshape_bytes,
    tshape_dict,
    tshape_int,
    tshape_list,
    tshape_none,
    tshape_str,
    tshape_tuple,
)
from nuitka.Tracing import tools_logger
from nuitka.utils.FileOperations import (
    getFileList,
    getNormalizedPath,
    makePath,
)
from nuitka.utils.Jinja2 import getTemplateC
from nuitka.utils.Json import loadJsonFromFilename

from .Common import (
    formatArgs,
    getLicenseGeneratedCode,
    getMethodVariations,
    isCheckOnlyMode,
    parseOptions,
    python2_dict_methods,
    python2_list_methods,
    python2_str_methods,
    python2_unicode_methods,
    python3_bytes_methods,
    python3_dict_methods,
    python3_list_methods,
    python3_str_methods,
    withFileOpenedAndAutoFormattedWithClaim,
    writeLine,
)
from .CTypeDescriptions import (
    bytes_desc,
    c_bool_desc,
    c_digit_desc,
    c_float_desc,
    c_long_desc,
    dict_desc,
    float_desc,
    int_desc,
    list_desc,
    long_desc,
    n_bool_desc,
    n_ilong_desc,
    object_desc,
    set_desc,
    str_desc,
    tuple_desc,
    unicode_desc,
)


def getDoExtensionUsingTemplateC(template_name):
    return getTemplateC(
        package_name="nuitka.code_generation",
        template_subdir="templates_c",
        template_name=template_name,
        extensions=("jinja2.ext.do",),
    )


class AlternativeTypeBase(object):
    # TODO: Base class for alternative types
    pass


class AlternativeIntOrClong(AlternativeTypeBase):
    # TODO: Base class for alternative type int or clong.
    pass


types = (
    int_desc,
    str_desc,
    unicode_desc,
    float_desc,
    tuple_desc,
    list_desc,
    set_desc,
    dict_desc,
    bytes_desc,
    long_desc,
    c_long_desc,
    c_digit_desc,
    c_float_desc,
    c_bool_desc,
    n_bool_desc,
    n_ilong_desc,
    object_desc,
)


def findTypeFromCodeName(code_name):
    for candidate in types:
        if candidate.getHelperCodeName() == code_name:
            return candidate


op_slot_codes = set()

# Reverse operation mapping.
reversed_args_compare_op_codes = {
    "LE": "GE",
    "LT": "GT",
    "EQ": "EQ",
    "NE": "NE",
    "GT": "LT",
    "GE": "LE",
}

inverse_compare_op_code = {
    "LE": "GT",
    "LT": "GE",
    "EQ": "NE",
    "NE": "EQ",
    "GT": "LE",
    "GE": "LT",
}

standard_template_context = {
    "isCommutativeOperation": isCommutativeOperation,
    "object_desc": object_desc,
    "int_desc": int_desc,
    "long_desc": long_desc,
    "float_desc": float_desc,
    "list_desc": list_desc,
    "tuple_desc": tuple_desc,
    "set_desc": set_desc,
    "str_desc": str_desc,
    "unicode_desc": unicode_desc,
    "bytes_desc": bytes_desc,
    "c_long_desc": c_long_desc,
    "c_digit_desc": c_digit_desc,
    "c_bool_desc": c_bool_desc,
    "n_bool_desc": n_bool_desc,
    "n_ilong_desc": n_ilong_desc,
}


def makeCompareSlotCode(operator, op_code, target, left, right, emit):
    # Many variations to consider, pylint: disable=too-many-branches

    key = operator, op_code, target, left, right
    if key in op_slot_codes:
        return

    int_types_family = (int_desc, c_long_desc)
    long_types_family = (int_desc, long_desc, c_long_desc, c_digit_desc)
    float_types_family = (int_desc, long_desc, float_desc, c_long_desc, c_float_desc)

    # Special casing variant that makes no sense and is actively avoided in the templates.
    if {left, right} == {c_long_desc, c_digit_desc}:
        return

    if left in int_types_family and right in int_types_family:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonInt.c.j2")
    elif left in long_types_family and right in long_types_family:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonLong.c.j2")
    elif left in float_types_family and right in float_types_family:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonFloat.c.j2")
    elif left == int_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonInt.c.j2")
    elif left == long_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonLong.c.j2")
    elif left == float_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonFloat.c.j2")
    elif left == tuple_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonTuple.c.j2")
    elif left == list_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonList.c.j2")
    # elif left == set_desc:
    #     template = env.get_template("HelperOperationComparisonSet.c.j2")
    elif left == bytes_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonBytes.c.j2")
    elif left == str_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonStr.c.j2")
    elif left == unicode_desc:
        template = getDoExtensionUsingTemplateC("HelperOperationComparisonUnicode.c.j2")
    else:
        return

    assert left is not int_desc or right is not int_desc or target is not n_bool_desc

    python_requirement = _parseRequirements(op_code, target, left, right, emit)

    code = template.render(
        operator=operator,
        target=target,
        left=left,
        right=right,
        op_code=op_code,
        reversed_args_op_code=reversed_args_compare_op_codes[op_code],
        inverse_compare_op_code=inverse_compare_op_code[op_code],
        name=template.name,
        **standard_template_context
    )

    emit(code)

    if python_requirement:
        emit("#endif")

    op_slot_codes.add(key)


mul_repeats = set()


def makeMulRepeatCode(target, left, right, emit):
    key = right, left
    if key in mul_repeats:
        return

    template = getDoExtensionUsingTemplateC("HelperOperationMulRepeatSlot.c.j2")

    code = template.render(target=target, left=left, right=right)

    emit(code)

    mul_repeats.add(key)


def _getNbSlotFromOperand(operand, op_code):
    # pylint: disable=too-many-branches,too-many-return-statements

    if operand == "+":
        return "nb_add"
    elif operand == "*":
        return "nb_multiply"
    elif operand == "-":
        return "nb_subtract"
    elif operand == "//":
        return "nb_floor_divide"
    elif operand == "/":
        if op_code == "TRUEDIV":
            return "nb_true_divide"
        else:
            return "nb_divide"
    elif operand == "%":
        return "nb_remainder"
    elif operand == "**":
        return "nb_power"
    elif operand == "<<":
        return "nb_lshift"
    elif operand == ">>":
        return "nb_rshift"
    elif operand == "|":
        return "nb_or"
    elif operand == "&":
        return "nb_and"
    elif operand == "^":
        return "nb_xor"
    elif operand == "@":
        return "nb_matrix_multiply"
    elif operand == "divmod":
        return "nb_divmod"
    else:
        assert False, operand


def _getNbInplaceSlotFromOperand(operand, op_code):
    if operand == "divmod":
        return None

    nb_slot = _getNbSlotFromOperand(operand, op_code)
    return nb_slot.replace("nb_", "nb_inplace_")


def _parseTypesFromHelper(helper_name):
    (
        target_code,
        left_code,
        right_code,
    ) = parseTypesFromHelper(helper_name)

    if target_code is not None:
        target = findTypeFromCodeName(target_code)
        assert target is not None, target_code

    else:
        target = None

    left = findTypeFromCodeName(left_code)
    right = findTypeFromCodeName(right_code)

    return target_code, target, left, right


def _parseRequirements(op_code, target, left, right, emit):
    python_requirement = set()

    # There is an obsolete Python2 operation too, making sure it's guarded in code.
    if op_code == "OLDDIV":
        python_requirement.add(int_desc.python_requirement)
    if op_code == "MATMULT":
        python_requirement.add("PYTHON_VERSION >= 0x350")
    if target is not None and target.python_requirement:
        python_requirement.add(target.python_requirement)
    if left.python_requirement:
        python_requirement.add(left.python_requirement)
    if right.python_requirement:
        python_requirement.add(right.python_requirement)

    if python_requirement:
        assert len(python_requirement) == 1, (target, left, right)
        python_requirement = python_requirement.pop()

        emit("#if %s" % python_requirement)

    return python_requirement


def makeHelperOperations(
    template, in_place, helpers_set, operator, op_code, emit_h, emit_c, emit
):
    # Complexity comes natural, pylint: disable=too-many-locals

    emit(
        '/* C helpers for type %s "%s" (%s) operations */'
        % ("in-place" if in_place else "specialized", operator, op_code)
    )
    emit()

    for helper_name in helpers_set:
        target_code, target, left, right = _parseTypesFromHelper(helper_name)
        assert left is not None, helper_name

        assert target is None or not in_place, (target_code, in_place)

        python_requirement = _parseRequirements(op_code, target, left, right, emit)

        emit(
            '/* Code referring to "%s" corresponds to %s and "%s" to %s. */'
            % (
                left.getHelperCodeName(),
                left.type_desc,
                right.getHelperCodeName(),
                right.type_desc,
            )
        )

        if operator == "+":
            sq_slot = "sq_concat"
        elif operator == "*":
            sq_slot = "sq_repeat"
        else:
            sq_slot = None

        if in_place and sq_slot is not None:
            sq_inplace_slot = sq_slot.replace("sq_", "sq_inplace_")
        else:
            sq_inplace_slot = None

        code = template.render(
            target=target,
            left=left,
            right=right,
            op_code=op_code,
            operator=operator,
            nb_slot=_getNbSlotFromOperand(operator, op_code),
            nb_inplace_slot=(
                _getNbInplaceSlotFromOperand(operator, op_code) if in_place else None
            ),
            sq_slot=sq_slot,
            sq_inplace_slot=sq_inplace_slot,
            **standard_template_context
        )

        emit_c(code)
        emit_h(getTemplateCodeDeclaredFunction(code))

        if python_requirement:
            emit("#endif")

        emit()


def makeHelperComparisons(
    template, helpers_set, operator, op_code, emit_h, emit_c, emit
):
    # Details to look for, pylint: disable=too-many-locals

    emit(
        '/* C helpers for type specialized "%s" (%s) comparisons */'
        % (operator, op_code)
    )
    emit()

    for target in (object_desc, c_bool_desc):
        makeCompareSlotCode(operator, op_code, target, int_desc, int_desc, emit_c)

    for helper_name in helpers_set:
        assert helper_name.split("_")[:2] == ["RICH", "COMPARE"], (helper_name,)

        # Filter for the operation.
        if helper_name.split("_")[2] != op_code:
            continue

        _target_code, target, left, right = _parseTypesFromHelper(helper_name)

        assert target is not None, helper_name
        assert left is not None, helper_name
        assert right is not None, helper_name

        (
            code,
            helper_target,
            type_desc1,
            type_desc2,
            _operand1,
            _operand2,
        ) = left.getTypeComparisonSpecializationHelper(
            other=right,
            op_code=op_code,
            target=target,
            operand1="operand1",
            operand2="operand2",
        )

        if left.isDualType() or right.isDualType():
            if op_code in ("LT", "LE", "EQ"):
                makeCompareSlotCode(
                    operator,
                    op_code,
                    helper_target,
                    left.getDualType("C"),
                    right.getDualType("C"),
                    emit_c,
                )
        else:
            if code:
                makeCompareSlotCode(
                    operator, op_code, helper_target, type_desc1, type_desc2, emit_c
                )

        python_requirement = _parseRequirements(op_code, target, left, right, emit)

        emit(
            '/* Code referring to "%s" corresponds to %s and "%s" to %s. */'
            % (
                left.getHelperCodeName(),
                left.type_desc,
                right.getHelperCodeName(),
                right.type_desc,
            )
        )

        if not python_requirement:
            is_py3_only = False
            is_py2_only = False
        elif python_requirement == "PYTHON_VERSION < 0x300":
            is_py3_only = False
            is_py2_only = True
        elif python_requirement == "PYTHON_VERSION >= 0x300":
            is_py3_only = True
            is_py2_only = False
        else:
            assert False, python_requirement

        code = template.render(
            target=target,
            left=left,
            right=right,
            op_code=op_code,
            reversed_args_op_code=reversed_args_compare_op_codes[op_code],
            inverse_compare_op_code=inverse_compare_op_code[op_code],
            operator=operator,
            is_py3_only=is_py3_only,
            is_py2_only=is_py2_only,
            object_desc=object_desc,
            int_desc=int_desc,
            n_bool_desc=n_bool_desc,
            c_bool_desc=c_bool_desc,
            c_long_desc=c_long_desc,
            c_digit_desc=c_digit_desc,
            n_ilong_desc=n_ilong_desc,
        )

        emit_c(code)
        emit_h(getTemplateCodeDeclaredFunction(code))

        if python_requirement:
            emit("#endif")

        emit()


def emitGenerationWarning(emit, template_name):
    emit(
        "/* WARNING, this code is GENERATED. Modify the template %s instead! */"
        % template_name
    )


def emitIDE(emit):
    emit("""
/* This file is included from another C file, help IDEs to still parse it on its own. */
#ifdef __IDE_ONLY__
#include "nuitka/prelude.h"
#endif
""")


def _getSpecializedComparisonOperations(dual):
    return OrderedSet(
        helper
        for helper in getSpecializedComparisonOperations()
        if dual == any(part in helper for part in ("NILONG", "NFLOAT"))
    )


def makeHelpersComparisonOperation(operand, op_code):
    specialized_cmp_helpers_set = _getSpecializedComparisonOperations(dual=False)

    template = getDoExtensionUsingTemplateC("HelperOperationComparison.c.j2")

    filename_c = "nuitka/build/static_src/HelpersComparison%s.c" % op_code.capitalize()
    filename_h = "nuitka/build/include/nuitka/helper/comparisons_%s.h" % op_code.lower()

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            filename_utils = filename_c[:-2] + "Utils.c"

            if os.path.exists(filename_utils):
                emit_c('#include "%s"' % os.path.basename(filename_utils))

            makeHelperComparisons(
                template,
                specialized_cmp_helpers_set,
                operand,
                op_code,
                emit_h,
                emit_c,
                emit,
            )


def makeHelpersComparisonDualOperation(operand, op_code):
    specialized_cmp_helpers_set = _getSpecializedComparisonOperations(dual=True)

    template = getDoExtensionUsingTemplateC("HelperOperationComparisonDual.c.j2")

    filename_c = (
        "nuitka/build/static_src/HelpersComparisonDual%s.c" % op_code.capitalize()
    )
    filename_h = (
        "nuitka/build/include/nuitka/helper/comparisons_dual_%s.h" % op_code.lower()
    )

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            filename_utils = filename_c[:-2] + "Utils.c"

            if os.path.exists(filename_utils):
                emit_c('#include "%s"' % os.path.basename(filename_utils))

            makeHelperComparisons(
                template,
                specialized_cmp_helpers_set,
                operand,
                op_code,
                emit_h,
                emit_c,
                emit,
            )


def _getSpecializedBinaryOperations(op_code, dual):
    return OrderedSet(
        helper
        for helper in getSpecializedBinaryOperations(op_code)
        if dual == any(part in helper for part in ("NILONG", "NFLOAT"))
    )


def makeHelpersBinaryOperation(operator, op_code):
    specialized_op_helpers_set = _getSpecializedBinaryOperations(
        op_code=op_code,
        dual=False,
    )

    template = getDoExtensionUsingTemplateC("HelperOperationBinary.c.j2")

    filename_c = (
        "nuitka/build/static_src/HelpersOperationBinary%s.c" % op_code.capitalize()
    )
    filename_h = (
        "nuitka/build/include/nuitka/helper/operations_binary_%s.h" % op_code.lower()
    )

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            filename_utils = filename_c[:-2] + "Utils.c"

            if os.path.exists(filename_utils):
                emit_c('#include "%s"' % os.path.basename(filename_utils))

            makeHelperOperations(
                template=template,
                in_place=False,
                helpers_set=specialized_op_helpers_set,
                operator=operator,
                op_code=op_code,
                emit_h=emit_h,
                emit_c=emit_c,
                emit=emit,
            )


def makeHelpersInplaceOperation(operator, op_code):
    specialized_op_helpers_set = getSpecializedBinaryOperations("I" + op_code)

    template = getDoExtensionUsingTemplateC("HelperOperationInplace.c.j2")

    filename_c = (
        "nuitka/build/static_src/HelpersOperationInplace%s.c" % op_code.capitalize()
    )
    filename_h = (
        "nuitka/build/include/nuitka/helper/operations_inplace_%s.h" % op_code.lower()
    )

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            filename_utils = filename_c[:-2] + "Utils.c"

            if os.path.exists(filename_utils):
                emit_c('#include "%s"' % os.path.basename(filename_utils))

            makeHelperOperations(
                template=template,
                in_place=True,
                helpers_set=specialized_op_helpers_set,
                operator=operator,
                op_code=op_code,
                emit_h=emit_h,
                emit_c=emit_c,
                emit=emit,
            )


def makeHelpersBinaryDualOperation(operand, op_code):
    specialized_op_helpers_set = _getSpecializedBinaryOperations(
        op_code=op_code,
        dual=True,
    )

    template = getDoExtensionUsingTemplateC("HelperOperationBinaryDual.c.j2")

    filename_c = (
        "nuitka/build/static_src/HelpersOperationBinaryDual%s.c" % op_code.capitalize()
    )
    filename_h = (
        "nuitka/build/include/nuitka/helper/operations_binary_dual_%s.h"
        % op_code.lower()
    )

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            filename_utils = filename_c[:-2] + "Utils.c"

            if os.path.exists(filename_utils):
                emit_c('#include "%s"' % os.path.basename(filename_utils))

            makeHelperOperations(
                template=template,
                in_place=False,
                helpers_set=specialized_op_helpers_set,
                operator=operand,
                op_code=op_code,
                emit_h=emit_h,
                emit_c=emit_c,
                emit=emit,
            )


def makeHelpersImportHard():
    filename_c = "nuitka/build/static_src/HelpersImportHard.c"
    filename_h = "nuitka/build/include/nuitka/helper/import_hard.h"

    template = getDoExtensionUsingTemplateC("HelperImportHard.c.j2")

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            for module_name in sorted(hard_modules):
                makeHelperImportModuleHard(
                    template,
                    module_name,
                    emit_h,
                    emit_c,
                    emit,
                )


def makeHelperImportModuleHard(template, module_name, emit_h, emit_c, emit):
    emit('/* C helper for hard import of module "%s" import. */' % module_name)

    python_min_max_os_version = hard_modules_version.get(module_name)

    if python_min_max_os_version is not None:
        assert len(python_min_max_os_version) == 3, module_name
        python_min_version, python_max_version, os_limit = python_min_max_os_version

        parts = []

        if python_min_version is not None:
            parts.append("PYTHON_VERSION >= %s" % hex(python_min_version))
        if python_max_version is not None:
            parts.append("PYTHON_VERSION < %s" % hex(python_max_version))
        if os_limit is not None:
            parts.append({"win32": "defined(_WIN32)"}[os_limit])

        python_requirement = " && ".join(parts)

    else:
        python_requirement = None

    if python_requirement:
        emit("#if %s" % python_requirement)

    code = template.render(
        module_name=module_name,
        module_code_name=getImportModuleHardCodeName(module_name),
        name=template.name,
        target=object_desc,
        is_stdlib=module_name not in hard_modules_non_stdlib,
    )

    emit_c(code)
    emit_h(getTemplateCodeDeclaredFunction(code))

    if python_requirement:
        emit("#endif")

    emit()


def makeHelperCalls():
    filename_c = "nuitka/build/static_src/HelpersCallingGenerated.c"
    filename_h = "nuitka/build/include/nuitka/helper/calling_generated.h"

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                assert args[0] != "extern "
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            template = getTemplateC(
                "nuitka.code_generation", "CodeTemplateCallsPositional.c.j2"
            )

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            emit_c("""\
// We are switching some warnings off for this code, as they are triggered
// by the generated code constructs in a way that is not useful.
// spell-checker: ignore Wparentheses GNUC
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wparentheses-equality"
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wparentheses"
#endif
""")

            for args_count in range(max_quick_call + 1):
                code = getQuickCallCode(args_count=args_count, has_tuple_arg=False)

                emit_c(code)
                emit_h(getTemplateCodeDeclaredFunction(code))

                if args_count >= 1:
                    code = getQuickCallCode(args_count=args_count, has_tuple_arg=True)

                    emit_c(code)
                    emit_h(getTemplateCodeDeclaredFunction(code))

            template = getTemplateC(
                "nuitka.code_generation", "CodeTemplateCallsMixed.c.j2"
            )

            # Only keywords, but not positional arguments, via split args.
            code = getQuickMixedCallCode(
                args_count=0,
                has_tuple_arg=False,
                has_dict_values=True,
            )

            emit_c(code)
            emit_h(getTemplateCodeDeclaredFunction(code))

            for args_count in range(1, max_quick_call + 1):
                for has_tuple_arg in (False, True):
                    for has_dict_values in (False, True):
                        # We do not do that.
                        if not has_dict_values and has_tuple_arg:
                            continue

                        code = getQuickMixedCallCode(
                            args_count=args_count,
                            has_tuple_arg=has_tuple_arg,
                            has_dict_values=has_dict_values,
                        )

                        emit_c(code)
                        emit_h(getTemplateCodeDeclaredFunction(code))

            for args_count in range(1, 5):
                code = getQuickMethodDescriptorCallCode(args_count=args_count)

                emit_c(code)
                emit_h(getTemplateCodeDeclaredFunction(code))

            for args_count in range(max_quick_call + 1):
                code = getQuickMethodCallCode(args_count=args_count)

                emit_c(code)
                emit_h(getTemplateCodeDeclaredFunction(code))

            emit_c("""\
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#endif
""")


def makeHelperLists():
    filename_c = "nuitka/build/static_src/HelpersListsGenerated.c"
    filename_h = "nuitka/build/include/nuitka/helper/lists_generated.h"

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                assert args[0] != "extern "
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            template = getTemplateC(
                "nuitka.code_generation", "CodeTemplateMakeListSmall.c.j2"
            )

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            for args_count in range(1, make_list_constant_direct_threshold):
                code = template.render(args_count=args_count)

                emit_c(code)
                emit_h(getTemplateCodeDeclaredFunction(code))

            template = getTemplateC(
                "nuitka.code_generation", "CodeTemplateMakeListHinted.c.j2"
            )

            for args_count in range(
                make_list_constant_direct_threshold, make_list_constant_hinted_threshold
            ):
                code = template.render(args_count=args_count)

                emit_c(code)
                emit_h(getTemplateCodeDeclaredFunction(code))


def _makeHelperBuiltinTypeAttributes(
    type_prefix, type_name, python2_methods, python3_methods, emit_c, emit_h
):
    # many cases to deal with, pylint: disable=too-many-branches

    def getVarName(method_name):
        return "%s_builtin_%s" % (type_prefix, method_name)

    for method_name in sorted(set(python2_methods + python3_methods)):
        is_public = method_name in (
            "format",
            "fromkeys",
        )

        if method_name in python2_methods and method_name not in python3_methods:
            emit_c("#if PYTHON_VERSION < 0x300")
            if is_public:
                emit_h("#if PYTHON_VERSION < 0x300")
            needs_endif = True
        elif method_name not in python2_methods and method_name in python3_methods:
            emit_c("#if PYTHON_VERSION >= 0x300")
            if is_public:
                emit_h("#if PYTHON_VERSION >= 0x300")
            needs_endif = True
        else:
            needs_endif = False

        if not is_public:
            emit_c("static")

        emit_c("PyObject *%s = NULL;" % getVarName(method_name))

        if is_public:
            emit_h("extern PyObject *%s;" % getVarName(method_name))

        if needs_endif:
            emit_c("#endif")

            if is_public:
                emit_h("#endif")

    if not python3_methods:
        emit_c("#if PYTHON_VERSION < 0x300")
    if not python2_methods:
        emit_c("#if PYTHON_VERSION >= 0x300")

    emit_c("static void _init%sBuiltinMethods(void) {" % type_prefix.capitalize())
    for method_name in sorted(set(python2_methods + python3_methods)):
        if (
            method_name in python2_methods
            and method_name not in python3_methods
            and python3_methods
        ):
            emit_c("#if PYTHON_VERSION < 0x300")
            needs_endif = True
        elif (
            method_name not in python2_methods
            and method_name in python3_methods
            and python2_methods
        ):
            emit_c("#if PYTHON_VERSION >= 0x300")
            needs_endif = True
        else:
            needs_endif = False

        emit_c(
            '%s = PyObject_GetAttrString((PyObject *)&%s, "%s");'
            % (getVarName(method_name), type_name, method_name)
        )

        if needs_endif:
            emit_c("#endif")

    emit_c("}")

    if not python2_methods or not python3_methods:
        emit_c("#endif")


generate_builtin_type_operations = [
    # TODO: For these, we would need an implementation for adding/deleting dictionary values. That
    # has turned out to be too hard so far and these are very good friends, not doing hashing
    # multiple times when reading and writing, so can't do it unless we add something for the
    # MonolithPy eventually.
    (
        "tshape_dict",
        dict_desc,
        nuitka.specs.BuiltinDictOperationSpecs,
        ("pop", "popitem", "setdefault"),
    ),
    (
        "tshape_list",
        list_desc,
        nuitka.specs.BuiltinListOperationSpecs,
        (
            "pop",
            # TODO: template doesn't do positional only yet.
            # "sort",
        ),
    ),
    # TODO: These are very complex things using "string lib" code in CPython,
    # that we do not have easy access to, but we might one day for Nuitka-Python
    # expose it for the static linking of it and then we could in fact call
    # these directly.
    (
        "tshape_str",
        str_desc,
        nuitka.specs.BuiltinStrOperationSpecs,
        (
            "strip",
            "rstrip",
            "lstrip",
            "partition",
            "rpartition",
            "find",
            "rfind",
            "index",
            "rindex",
            "capitalize",
            "upper",
            "lower",
            "swapcase",
            "title",
            "isalnum",
            "isalpha",
            "isdigit",
            "islower",
            "isupper",
            "isspace",
            "istitle",
            "split",
            "rsplit",
            "startswith",
            "endswith",
            "replace",
            "encode",
            "decode",
            "count",
            "expandtabs",
            "translate",
            "ljust",
            "rjust",
            "center",
            "zfill",
            "splitlines",
        ),
    ),
    # TODO: This is using Python2 spec module for Python3 strings, that will be a problem down the
    # road, when version specifics come in.
    (
        "tshape_unicode",
        unicode_desc,
        nuitka.specs.BuiltinUnicodeOperationSpecs,
        (
            "strip",
            "rstrip",
            "lstrip",
            "find",
            "rfind",
            "index",
            "rindex",
            "capitalize",
            "upper",
            "lower",
            "swapcase",
            "title",
            "isalnum",
            "isalpha",
            "isdigit",
            "islower",
            "isupper",
            "isspace",
            "istitle",
            "split",
            "rsplit",
            "startswith",
            "endswith",
            "replace",
            "encode",
            "count",
            "expandtabs",
            "translate",
            "ljust",
            "rjust",
            "center",
            "zfill",
            "splitlines",
        ),
    ),
    (
        "tshape_bytes",
        bytes_desc,
        nuitka.specs.BuiltinBytesOperationSpecs,
        (
            "capitalize",
            "center",
            "count",
            "decode",
            "endswith",
            "expandtabs",
            "find",
            "index",
            "isalnum",
            "isalpha",
            "isdigit",
            "islower",
            "isspace",
            "istitle",
            "isupper",
            "join",
            "ljust",
            "lower",
            "lstrip",
            "partition",
            "replace",
            "rfind",
            "rindex",
            "rjust",
            "rpartition",
            "rsplit",
            "rstrip",
            "split",
            "splitlines",
            "startswith",
            "strip",
            "swapcase",
            "title",
            "translate",
            "upper",
            "zfill",
        ),
    ),
]


def makeDictCopyHelperCodes():
    filename_c = "nuitka/build/static_src/HelpersDictionariesGenerated.c"

    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:

        def emit(*args):
            writeLine(output_c, *args)

        emitIDE(emit)

        template = getDoExtensionUsingTemplateC("HelperDictionaryCopy.c.j2")

        emitGenerationWarning(emit, template.name)

        code = template.render()

        emit(code)


def _getCheckForShape(shape):
    # Return driven for better debugging experience, pylint: disable=too-many-return-statements

    if shape is tshape_str:
        return "Nuitka_String_CheckExact"
    elif shape is tshape_list:
        return "PyList_CheckExact"
    elif shape is tshape_tuple:
        return "PyTuple_CheckExact"
    elif shape is tshape_int:
        # TODO: Not defined a version independent one yet in prelude.h
        return None
    elif shape is tshape_bool:
        return "PyBool_Check"
    elif shape is tshape_none:
        # TODO: Not defined one in prelude.h yet
        return None
    elif shape is tshape_dict:
        return "PyDict_CheckExact"
    elif shape is tshape_bytes:
        return "PyBytes_CheckExact"
    else:
        assert False, shape


def makeHelperBuiltinTypeMethods():
    # Many details, pylint: disable=too-many-locals
    filename_c = "nuitka/build/static_src/HelpersBuiltinTypeMethods.c"
    filename_h = "nuitka/build/include/nuitka/helper/operations_builtin_types.h"
    with withFileOpenedAndAutoFormattedWithClaim(
        filename_c, claim=getLicenseGeneratedCode()
    ) as output_c:
        with withFileOpenedAndAutoFormattedWithClaim(
            filename_h, claim=getLicenseGeneratedCode()
        ) as output_h:

            def emit_h(*args):
                writeLine(output_h, *args)

            def emit_c(*args):
                writeLine(output_c, *args)

            def emit(*args):
                emit_h(*args)
                emit_c(*args)

            template = getDoExtensionUsingTemplateC("HelperBuiltinMethodOperation.c.j2")

            emitGenerationWarning(emit, template.name)

            emitIDE(emit)

            # TODO: Isn't this creating more than necessary, we don't use all of them, e.g.
            # not with lists and dicts.
            _makeHelperBuiltinTypeAttributes(
                "str", "PyString_Type", python2_str_methods, (), emit_c, emit_h
            )
            _makeHelperBuiltinTypeAttributes(
                "bytes", "PyBytes_Type", (), python3_bytes_methods, emit_c, emit_h
            )
            _makeHelperBuiltinTypeAttributes(
                "unicode",
                "PyUnicode_Type",
                python2_unicode_methods,
                python3_str_methods,
                emit_c,
                emit_h,
            )
            _makeHelperBuiltinTypeAttributes(
                "dict",
                "PyDict_Type",
                python2_dict_methods,
                python3_dict_methods,
                emit_c,
                emit_h,
            )
            _makeHelperBuiltinTypeAttributes(
                "list",
                "PyList_Type",
                python2_list_methods,
                python3_list_methods,
                emit_c,
                emit_h,
            )

            for (
                shape_name,
                type_desc,
                spec_module,
                method_names,
            ) in generate_builtin_type_operations:
                if type_desc.python_requirement:
                    emit("#if %s" % type_desc.python_requirement)

                for method_name in sorted(method_names):
                    (
                        present,
                        arg_names,
                        _arg_tests,
                        arg_name_mapping,
                        arg_counts,
                        result_shape,
                    ) = getMethodVariations(
                        spec_module=spec_module,
                        shape_name=shape_name,
                        method_name=method_name,
                        must_exist=True,
                    )

                    assert present, method_name

                    def formatArgumentDeclaration(arg_types, arg_names, starting):
                        return formatArgs(
                            [
                                arg_type.getVariableDecl(arg_name)
                                for arg_type, arg_name in zip(arg_types, arg_names)
                            ],
                            starting=starting,
                        )

                    # Function is used immediately in same loop, pylint: disable=cell-var-from-loop
                    def replaceArgNameForC(arg_name):
                        if arg_name in arg_name_mapping:
                            arg_name = arg_name_mapping[arg_name]

                        if arg_name in ("default", "new", "delete"):
                            return arg_name + "_value"
                        else:
                            return arg_name

                    for arg_count in arg_counts:
                        variant_args = [
                            replaceArgNameForC(arg_name)
                            for arg_name in arg_names[:arg_count]
                        ]

                        code = template.render(
                            object_desc=object_desc,
                            builtin_type=type_desc,
                            builtin_arg_name=type_desc.type_name,
                            method_name=method_name,
                            api_suffix=(
                                str(arg_count + 1) if len(arg_counts) > 1 else ""
                            ),
                            arg_names=variant_args,
                            arg_types=[object_desc] * len(variant_args),
                            formatArgumentDeclaration=formatArgumentDeclaration,
                            extra_check=(
                                _getCheckForShape(result_shape)
                                if result_shape is not None
                                else None
                            ),
                            zip=zip,
                            len=len,
                            name=template.name,
                        )

                        emit_c(code)
                        emit_h(getTemplateCodeDeclaredFunction(code))
                if type_desc.python_requirement:
                    emit("#endif")


def _getOffsetAssertionCode(key):
    if key.startswith("_PyRuntimeState_"):
        # spell-checker: ignore offsetof
        return "offsetof(_PyRuntimeState, %s)" % key[len("_PyRuntimeState_") :]
    return None


def _getCompiledOffsetsGroups():
    data_dir = getNormalizedPath(
        os.path.join(
            os.path.dirname(__file__), "..", "..", "build", "python_internal_offset"
        )
    )

    groups = {}
    for f in getFileList(data_dir, only_suffixes=(".json",)):
        basename = os.path.basename(f)

        match = re.match(
            r"^offsets_(?P<python_version_str>\d+\.\d+)"
            r"-(?P<os_name>[a-zA-Z]+)-(?P<arch_name>[a-zA-Z0-9_]+)-(?P<gil_str>gil|no-gil)\.json$",
            basename,
        )
        if not match:
            return tools_logger.sysexit(
                "Error, unrecognized JSON filename format '%s'." % basename
            )

        python_version_str = match.group("python_version_str")
        micro = 0
        os_name = match.group("os_name")
        arch_name = match.group("arch_name")
        gil_str = match.group("gil_str")

        expected_keys = {
            "_PyRuntimeState_" + k
            for k in getOffsetsJsonRequiredKeys(python_version_str)
        }
        data = loadJsonFromFilename(f)
        data_keys = set(data.keys())

        if expected_keys != data_keys:
            return tools_logger.sysexit(
                """\
Error, the JSON file '%s' has outdated keys. Expected: %r, but got: %r. Please
regenerate the headers via 'python%s bin/generate-specialized-offsets-code'."""
                % (f, expected_keys, data_keys, python_version_str)
            )

        python_version = tuple(int(x) for x in python_version_str.split("."))
        group_key = (python_version, gil_str, os_name, arch_name)
        if group_key not in groups:
            groups[group_key] = []
        groups[group_key].append((micro, data))

    return groups


def _mapOsAndArchToMacro(os_name, arch_name):
    macros = []
    if os_name == "Windows":
        macros.append("defined(_WIN32)")
    elif os_name == "Linux":
        macros.append("defined(__linux__)")
    elif os_name == "Darwin":
        macros.append("(defined(__APPLE__) && defined(__MACH__))")

    if arch_name == "x86_64":
        macros.append("(defined(__x86_64__) || defined(_M_X64))")
    elif arch_name in ("arm64", "aarch64"):
        macros.append("(defined(__aarch64__) || defined(_M_ARM64))")
    elif arch_name == "x86":
        macros.append("(defined(__i386__) || defined(_M_IX86))")

    if not macros:
        return "1"

    return " && ".join(macros)


def updateCompiledOffsetsHeader():
    groups = _getCompiledOffsetsGroups()

    template_groups = []
    for (python_version, gil_str, os_name, arch_name), versions in sorted(
        groups.items()
    ):

        versions.sort(key=lambda x: x[0])

        template_keys = []
        for key in sorted(versions[0][1].keys()):
            template_keys.append(
                {
                    "name": key,
                    "val": versions[0][1][key],
                }
            )

        template_groups.append(
            {
                "python_version_hex": "%x%x0" % python_version,
                "next_python_version_hex": "%x%x0"
                % (python_version[0], python_version[1] + 1),
                "is_gil": gil_str == "gil",
                "os_arch_macro": _mapOsAndArchToMacro(os_name, arch_name),
                "offset_keys": template_keys,
            }
        )

    _writeCompiledOffsetsHeader(template_groups)


def _writeCompiledOffsetsHeader(template_groups):
    template = getTemplateC(
        package_name="nuitka.tools.general.generate_header",
        template_name="python_internals_access.h.j2",
    )

    header_c_code = template.render(
        template_groups=template_groups,
        linux_keys=getOffsetsJsonRequiredKeys("3.14"),
    )

    out_path = os.path.join(
        "nuitka", "build", "include", "nuitka", "python_internals_access.h"
    )
    makePath(os.path.dirname(out_path))
    with withFileOpenedAndAutoFormattedWithClaim(
        out_path, claim=getLicenseGeneratedCode()
    ) as output_c:
        output_c.write(header_c_code)

    if not isCheckOnlyMode():
        tools_logger.info("Generated C header at %s" % out_path)


def main():
    parseOptions()

    makeHelpersBinaryDualOperation("+", "ADD")

    makeDictCopyHelperCodes()

    # Cover many things once first, then cover all for quicker turnaround during development.
    makeHelpersBinaryOperation("+", "ADD")
    makeHelpersInplaceOperation("+", "ADD")

    makeHelperBuiltinTypeMethods()

    makeHelpersImportHard()

    makeHelperCalls()
    makeHelperLists()

    makeHelpersBinaryOperation("-", "SUB")
    makeHelpersBinaryOperation("*", "MULT")
    makeHelpersBinaryOperation("%", "MOD")
    makeHelpersBinaryOperation("|", "BITOR")
    makeHelpersBinaryOperation("&", "BITAND")
    makeHelpersBinaryOperation("^", "BITXOR")
    makeHelpersBinaryOperation("<<", "LSHIFT")
    makeHelpersBinaryOperation(">>", "RSHIFT")
    makeHelpersBinaryOperation("//", "FLOORDIV")
    makeHelpersBinaryOperation("/", "TRUEDIV")
    makeHelpersBinaryOperation("/", "OLDDIV")
    makeHelpersBinaryOperation("divmod", "DIVMOD")
    makeHelpersBinaryOperation("**", "POW")
    makeHelpersBinaryOperation("@", "MATMULT")

    makeHelpersInplaceOperation("-", "SUB")
    makeHelpersInplaceOperation("*", "MULT")
    makeHelpersInplaceOperation("%", "MOD")
    makeHelpersInplaceOperation("|", "BITOR")
    makeHelpersInplaceOperation("&", "BITAND")
    makeHelpersInplaceOperation("^", "BITXOR")
    makeHelpersInplaceOperation("<<", "LSHIFT")
    makeHelpersInplaceOperation(">>", "RSHIFT")
    makeHelpersInplaceOperation("//", "FLOORDIV")
    makeHelpersInplaceOperation("/", "TRUEDIV")
    makeHelpersInplaceOperation("/", "OLDDIV")
    makeHelpersInplaceOperation("**", "POW")
    makeHelpersInplaceOperation("@", "MATMULT")

    makeHelpersComparisonOperation("==", "EQ")
    makeHelpersComparisonOperation("!=", "NE")
    makeHelpersComparisonOperation("<=", "LE")
    makeHelpersComparisonOperation(">=", "GE")
    makeHelpersComparisonOperation(">", "GT")
    makeHelpersComparisonOperation("<", "LT")

    makeHelpersComparisonDualOperation("==", "EQ")
    makeHelpersComparisonDualOperation("!=", "NE")
    makeHelpersComparisonDualOperation("<=", "LE")
    makeHelpersComparisonDualOperation(">=", "GE")
    makeHelpersComparisonDualOperation(">", "GT")
    makeHelpersComparisonDualOperation("<", "LT")

    updateCompiledOffsetsHeader()


#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the GNU Affero General Public License, Version 3 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        https://www.gnu.org/licenses/agpl-3.0.txt
#
#     See also: "Nuitka Runtime Library Exception, Version 1.0" in file
#     "LICENSE-RUNTIME.txt" for additional permissions granted under Section 7.
#
#     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.
