{#     Copyright 2023, Kay Hayen, mailto:kay.hayen@gmail.com                    #}
{#                                                                              #}
{#     Part of "Nuitka", an optimizing Python compiler that is compatible and   #}
{#     integrates with CPython, but also works on its own.                      #}
{#                                                                              #}
{#     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.                                           #}
{#                                                                              #}
{# macro for code to updating one child value #}
{% macro named_child_setter_code(named_child, value, needs_parent) %}
{% if named_child in named_children_checkers %}
    {{value}} = {{named_children_checkers[named_child]}}({{value}})
{% endif %}
{% if needs_parent %}
{% if named_children_types.get(named_child) == "tuple" %}
    assert type({{value}}) is tuple, type(value)

    for val in {{value}}:
        val.parent = self
{% elif named_children_types.get(named_child) == "optional" %}
    if {{value}} is not None:
        {{value}}.parent = self
{% else %}
    {{value}}.parent = self
{% endif %}
{% endif %}

    self.subnode_{{named_child}} = {{value}}
{% endmacro %}

{% if is_statement %}
{% set base_class = "StatementBase" %}
from nuitka.nodes.NodeBases import StatementBase
{% elif is_compute_final or is_compute_final_children or node_attributes or raise_mode in ("no_raise", "raise") or awaited_constant_attributes %}
{% set base_class = "ExpressionBase" %}
from nuitka.nodes.ExpressionBases import ExpressionBase
{% else %}
{% set base_class = "object" %}
{% endif %}

{% if named_children and is_expression %}
from nuitka.nodes.NodeMakingHelpers import wrapExpressionWithSideEffects
{% endif %}
{% if not is_compute_final and named_children and base_class == "ExpressionBase" %}
from abc import abstractmethod
{% endif %}
{% if is_compute_statement %}
from abc import abstractmethod
{% endif %}

class {{mixin_name}}({{base_class}}):
    # Mixins are not allowed to specify slots, pylint: disable=assigning-non-slot
    __slots__ = ()

    # This is generated for use in
{% for node_class_name in intended_for %}
    #   {{node_class_name}}
{% endfor %}

{% if named_children or node_attributes %}
    def __init__(self,
{% for named_child in named_children %}
                 {{named_child}},
{% endfor %}
{% for node_attribute in node_attributes %}
                 {{node_attribute}},
{% endfor %}
{% if base_class in ("ExpressionBase", "StatementBase") %}
                 source_ref
{% endif %}
    ):
{% for named_child in named_children %}
{% if named_child in named_children_checkers %}
        {{named_child}} = {{named_children_checkers[named_child]}}({{named_child}})
{% endif %}
{% if named_children_types.get(named_child) == "tuple" %}
        assert type({{named_child}}) is tuple

        for val in {{named_child}}:
            val.parent = self
{% elif named_children_types.get(named_child) == "optional" %}
        if {{named_child}} is not None:
            {{named_child}}.parent = self
{% else %}
        {{named_child}}.parent = self
{% endif %}

        self.subnode_{{named_child}} = {{named_child}}

{% endfor %}
{% for node_attribute in node_attributes %}
        self.{{node_attribute}} = {{node_attribute}}
{% endfor %}

{% if base_class != "object" %}
        {{base_class}}.__init__(self, source_ref)
{% endif %}

{% if has_post_node_init %}
        self.postInitNode()
{% endif %}

{% if has_post_node_init %}
    @abstractmethod
    def postInitNode(self):
        """For overload"""
{% endif %}

{% if node_attributes %}
    def getDetails(self):
        return {
{% for node_attribute in node_attributes %}
            "{{node_attribute}}" : self.{{node_attribute}},
{% endfor %}
        }
{% endif %}

{% for named_child in children_mixing_setters_needed %}
    def setChild{{named_child.title().replace("_", "")}}(self, value):
{{ named_child_setter_code(named_child, "value", needs_parent=True) | indent(8, True) }}
{% endfor %}

    def getVisitableNodes(self):
        """ The visitable nodes, with tuple values flattened. """

{% if "tuple" not in named_children_types.values() and "optional" not in named_children_types.values() %}
        return (
{% for named_child in named_children %}
            self.subnode_{{named_child}},
{% endfor %}
        )
{% elif len(named_children) == 1 and named_children_types.get(named_children[0]) == "tuple" %}
        return self.subnode_{{named_children[0]}}
{% elif len(named_children) == 1 and named_children_types.get(named_children[0]) == "optional" %}
        value = self.subnode_{{named_children[0]}}

        {# In this case, generator is not faster. #}
        if value is None:
            return ()
        else:
            return (value,)
{% elif len(named_children) == 1 %}
        return (self.subnode_{{named_children[0]}},)
{% else %}
        result = []
{% for named_child in named_children %}
{% if named_children_types.get(named_child) == "optional" %}
        value = self.subnode_{{named_child}}
        if value is None:
            pass
        else:
            result.append(value)
{% elif named_children_types.get(named_child) == "tuple" %}
        result.extend(self.subnode_{{named_child}})
{% else %}
        result.append(self.subnode_{{named_child}})
{% endif %}
{% endfor %}
        return tuple(result)
{% endif %}

    def getVisitableNodesNamed(self):
        """Named children dictionary.

        For use in cloning nodes, debugging and XML output.
        """

{% if len(named_children) == 1 %}
        return (
            ("{{named_children[0]}}", self.subnode_{{named_children[0]}}),
        )
{% else %}
        return (
{% for named_child in named_children %}
            ("{{named_child}}", self.subnode_{{named_child}}),
{% endfor %}
        )
{% endif %}

    def replaceChild(self, old_node, new_node):
{% for named_child in named_children %}
        value = self.subnode_{{named_child}}
{% if named_children_types.get(named_child) == "tuple" %}
        if old_node in value:
            if new_node is not None:
                new_node.parent = self

                self.subnode_{{named_child}} = tuple(
                    (val if val is not old_node else new_node)
                    for val in value
                )
            else:
                self.subnode_{{named_child}} = tuple(
                    val
                    for val in value
                    if val is not old_node
                )


            return
{% else %}
        if old_node is value:
{{ named_child_setter_code(named_child, "new_node", needs_parent=True) | indent(8, True) }}
            return
{% endif %}

{% endfor %}
        raise AssertionError("Didn't find child", old_node, "in", self)

    def getCloneArgs(self):
        """ Get clones of all children to pass for a new node.

            Needs to make clones of child nodes too.
        """

        values = {
{% for named_child in named_children %}
{% if named_children_types.get(named_child) == "tuple" %}
            "{{named_child}}" : tuple(v.makeClone() for v in self.subnode_{{named_child}}),
{% elif named_children_types.get(named_child) == "optional" %}
            "{{named_child}}" : self.subnode_{{named_child}}.makeClone() if self.subnode_{{named_child}} is not None else None,
{% else %}
            "{{named_child}}" : self.subnode_{{named_child}}.makeClone(),
{% endif %}
{% endfor %}
        }

        values.update(self.getDetails())

        return values

{% endif %}

    def finalize(self):
        del self.parent

{% for named_child in named_children %}
{% if named_children_types.get(named_child) == "optional" %}
        if self.subnode_{{named_child}} is not None:
            self.subnode_{{named_child}}.finalize()
{% elif named_children_types.get(named_child) == "tuple" %}
        for c in self.subnode_{{named_child}}:
            c.finalize()
{% else %}
        self.subnode_{{named_child}}.finalize()
{% endif %}
        del self.subnode_{{named_child}}
{% endfor %}

{% for node_attribute in node_attributes %}
{% if node_attribute in ("locals_scope", "target_scope", "variable") %}
        del self.{{node_attribute}}
{% endif %}
{% endfor %}


{% if is_expression %}
    def computeExpressionRaw(self, trace_collection):
        """Compute an expression.

        Default behavior is to just visit the child expressions first, and
        then the node "computeExpression". For a few cases this needs to
        be overloaded, e.g. conditional expressions.
        """

{% if named_children and not is_compute_final_children %}
{% if len(named_children) == 1 %}
{% if named_children_types.get(named_children[0]) == "tuple" %}
        # First apply the sub-expressions, as they are evaluated before
        # the actual operation.
        {# Keep this around, so we can look up its index, even after it got replaced. This
           avoids an enumeration that will not be used unless something will raise an
           exception. #}
        old_subnode_{{named_children[0]}} = self.subnode_{{named_children[0]}}

        for sub_expression in old_subnode_{{named_children[0]}}:
            expression = trace_collection.onExpression(sub_expression)

            if expression.willRaiseAnyException():
                wrapped_expression = wrapExpressionWithSideEffects(
                    side_effects=self.subnode_{{named_children[0]}}[:old_subnode_{{named_children[0]}}.index(sub_expression)],
                    old_node=sub_expression,
                    new_node=expression,
                )

                return (
                    wrapped_expression,
                    "new_raise",
                    lambda: "For '%s' the child expression '%s' will raise."
                    % (self.getChildNameNice(), expression.getChildNameNice()),
                )
{% else %}
        # First apply the sub-expression, as they it's evaluated before.
{% if named_children_types.get(named_children[0]) == "optional" %}
        expression = self.subnode_{{named_children[0]}}

        if expression is not None:
            expression = trace_collection.onExpression(expression)

            if expression.willRaiseAnyException():
                return (
                    expression,
                    "new_raise",
                    lambda: "For '%s' the child expression '%s' will raise."
                    % (self.getChildNameNice(), expression.getChildNameNice()),
                )
{% else %}
        expression = trace_collection.onExpression(self.subnode_{{named_children[0]}})

        if expression.willRaiseAnyException():
            return (
                expression,
                "new_raise",
                lambda: "For '%s' the child expression '%s' will raise."
                % (self.getChildNameNice(), expression.getChildNameNice()),
            )
{% endif %}
{% endif %}
{% else %}
        # First apply the sub-expressions, as they are evaluated before
        # the actual operation.
        for count, sub_expression in enumerate(self.getVisitableNodes()):
            expression = trace_collection.onExpression(sub_expression)

            if expression.willRaiseAnyException():
                sub_expressions = self.getVisitableNodes()

                wrapped_expression = wrapExpressionWithSideEffects(
                    side_effects=sub_expressions[:count],
                    old_node=sub_expression,
                    new_node=expression,
                )

                return (
                    wrapped_expression,
                    "new_raise",
                    lambda: "For '%s' the child expression '%s' will raise."
                    % (self.getChildNameNice(), expression.getChildNameNice()),
                )
{% endif %}
{% endif %}

{% for named_child in awaited_constant_attributes %}
        if self.subnode_{{named_child}}.isCompileTimeConstant():
{% if raise_mode in ("raise", "raise_operation") %}
            try:
                return self.computeExpressionConstant{{named_child.title()}}(trace_collection)
            finally:
{% if raise_mode =="raise" %}
                trace_collection.onExceptionRaiseExit(BaseException)
{% elif raise_mode =="raise_operation" %}
                if self.mayRaiseExceptionOperation():
                    trace_collection.onExceptionRaiseExit(BaseException)
{% endif %}
{% else %}
            return self.computeExpressionConstant{{named_child.title()}}(trace_collection)
{% endif %}
{% endfor %}

{% if is_compute_final %}
{% if raise_mode != "no_raise" %}
        trace_collection.onExceptionRaiseExit(BaseException)
{% endif %}
        return self, None, None
{% else %}
        # Then ask ourselves to work on it.
        return self.computeExpression(trace_collection)
{% endif %}

{% if raise_mode == "no_raise" %}
    @staticmethod
    def mayRaiseExceptionOperation():
        return False

{% if named_children %}
    def mayRaiseException(self, exception_type):
        return \
{% for named_child in named_children %}
{% if named_children_types.get(named_child) == "optional" %}
            (self.subnode_{{named_child}} is not None and self.subnode_{{named_child}}.mayRaiseException(exception_type)) \
{% elif named_children_types.get(named_child) == "tuple" %}
            any(value.mayRaiseException(exception_type) for value in self.subnode_{{named_child}}) \
{% else %}
            self.subnode_{{named_child}}.mayRaiseException(exception_type) \
{% endif %}
{% if named_child != named_children[-1] %}
            or \
{% endif %}
{% endfor %}
{% else %}
    @staticmethod
    def mayRaiseException(exception_type):
        return False
{% endif %}

{% endif %}

{# TODO: Should make it abstract for mere mixins too, but too much work now. #}
{% if not is_compute_final and named_children and base_class == "ExpressionBase" %}
    @abstractmethod
    def computeExpression(self, trace_collection):
        """Must be overloaded for non-final node."""
{% endif %}

{% endif %}

{% if is_compute_statement %}
    def computeStatement(self, trace_collection):
        {# TODO: Role this out for better performance and eliminate computeStatementSubExpressions globally #}
        result, change_tags, change_desc = self.computeStatementSubExpressions(
            trace_collection=trace_collection
        )

        if result is not self:
            return result, change_tags, change_desc

        return self.computeStatementOperation(trace_collection)

    @abstractmethod
    def computeStatementOperation(self, trace_collection):
        """Must be overloaded for non-final node."""
{% endif %}

    def collectVariableAccesses(self, emit_read, emit_write):
        """ Collect variable reads and writes of child nodes."""

{% for named_child in named_children %}
{% if named_children_types.get(named_child) == "optional" %}
        subnode_{{named_child}} = self.subnode_{{named_child}}

        if subnode_{{named_child}} is not None:
            self.subnode_{{named_child}}.collectVariableAccesses(emit_read, emit_write)
{% elif named_children_types.get(named_child) == "tuple" %}
        for element in self.subnode_{{named_child}}:
            element.collectVariableAccesses(emit_read, emit_write)
{% else  %}
        self.subnode_{{named_child}}.collectVariableAccesses(emit_read, emit_write)
{% endif %}
{% endfor %}

{% for named_child in awaited_constant_attributes %}
    @abstractmethod
    def computeExpressionConstant{{named_child.title()}}(self, trace_collection):
        """Called when attribute {{named_child}} is constant."""
{% endfor %}

# Assign the names that are easier to import with a stable name.
{% for node_class_name in intended_for %}
{% if base_class in ("ExpressionBase", "StatementBase") %}
{{node_class_name}}Base = {{mixin_name}}
{% else %}
Children{{node_class_name}}Mixin = {{mixin_name}}
{% endif %}
{% endfor %}
