{#     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.                                           #}
{#                                                                              #}
{% from 'HelperSlotsCommon.c.j2' import goto_exit %}
{% from 'HelperSlotsBinary.c.j2' import binary_operation, call_binary_slot with context %}
{% macro inplace_fallback_operation(props, left, right, type1, type2) %}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#endif
    NUITKA_MAY_BE_UNUSED bool cbool_result;
    NUITKA_MAY_BE_UNUSED PyObject *obj_result;
#ifdef _MSC_VER
#pragma warning(pop)
#endif

{% if left == object_desc or left.hasSlot(nb_inplace_slot) %}
    {{ left.getSlotType(nb_inplace_slot)}} islot = {{left.getSlotValueExpression("type1", nb_inplace_slot) }};

    if (islot != NULL) {
        PyObject *x = {{ left.getSlotCallExpression(nb_inplace_slot, "islot", "*operand1", "operand2") }};

        if (x != Py_NotImplemented) {
            {{ goto_exit(props, "exit_inplace_result_object", "x") }}
        }

        Py_DECREF(x);
    }
{% else %}
    // No inplace number slot {{ nb_inplace_slot }} available for this type.
{% endif %}

    {
        {{ binary_operation(props, operator, nb_slot, left, right, "type1", "type2", "*operand1", "operand2", "exit_inplace_result_object", "exit_inplace_result_cbool_ok", "exit_inplace_result_nbool", "exit_inplace_exception") }}
    }

{% if "exit_inplace_result_object" in props["exits"] %}
exit_inplace_result_object:
    if (unlikely(obj_result == NULL)) {
        return false;
    }

    // We got an object handed, that we have to release.
    Py_DECREF(*operand1);

    // That's our return value then. As we use a dedicated variable, it's
    // OK that way.
    *operand1 = obj_result;

    return true;
{% endif %}

{% if "exit_inplace_exception" in props["exits"] %}
{% if "all" not in props["exits"]["exit_inplace_exception"] %}
#if PYTHON_VERSION < 0x300
{% endif %}
exit_inplace_exception:
    return false;
{% if "all" not in props["exits"]["exit_inplace_exception"] %}
#endif
{% endif %}
{% endif %}
{% endmacro %}
{% if not left.hasTypeSpecializationCode(right, nb_slot, sq_slot) and left.getSameTypeType(right).hasSameTypeOperationSpecializationCode(right.getSameTypeType(left), nb_slot, sq_slot) %}
static HEDLEY_NEVER_INLINE bool __INPLACE_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("*operand1")}}, {{right.getVariableDecl("operand2")}}) {
{% set props = {"exits": {}} %}
{% if left == object_desc %}
    PyTypeObject *type1 = {{ left.getTypeValueExpression("*operand1") }};
{% endif %}
{% if right == object_desc %}
    PyTypeObject *type2 = {{ right.getTypeValueExpression("operand2") }};
{% endif %}

    {{ inplace_fallback_operation(props, left, right, "type1", "type2") }}
}
{% endif %}
static inline bool _INPLACE_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("*operand1")}}, {{right.getVariableDecl("operand2")}}) {
    assert(operand1); // Pointer must be non-null.

    {{ left.getCheckValueCode("*operand1") }}
    {{ right.getCheckValueCode("operand2") }}

{% if left.type_name == "object" and right.type_name == "object" %}
    {# CPython2 treats integer values with fast path. #}
#if PYTHON_VERSION < 0x300
    if ({{ left.getIntCheckExpression("*operand1") }} && {{ right.getIntCheckExpression("operand2") }}) {
        {% from 'HelperSlotsInt.c.j2' import int_slot with context %}

        {% set props = {"exits": {}} %}
        {{ int_slot(props, operator, nb_slot, None, int_desc, int_desc, "result", "*operand1", "operand2", "exit_result_ok", "exit_result_exception") }}

        exit_result_ok:
        return true;

{% if "exit_result_exception" in props["exits"] %}
        exit_result_exception:
        return false;
{% endif %}
    }
#endif
{% endif %}

{% if operator == "+" and (left.mayBothHaveType(right, "str") or left.mayBothHaveType(right, "unicode") or left.hasOneOrBothType(right, "bytes")) %}
    if (Py_REFCNT(*operand1) == 1) {
        // We more or less own the operand, so we might reuse its storage and
        // execute stuff in-place.
{% if operator == "+" and left.mayBothHaveType(right, "str") %}
#if PYTHON_VERSION < 0x300
        if ({{left.getStringCheckExpression("*operand1")}} && !PyString_CHECK_INTERNED(*operand1) && {{right.getStringCheckExpression("operand2")}}) {
            return STRING_ADD_INPLACE(operand1, operand2);
        }
#endif
{% endif %}

{% if operator == "+" and left.mayBothHaveType(right, "unicode") %}
#if PYTHON_VERSION >= 0x300
        if ({{ left.getUnicodeCheckExpression("*operand1")}} && !PyUnicode_CHECK_INTERNED(*operand1) && {{right.getUnicodeCheckExpression("operand2") }}) {
            // TODO: Push outward.
            PyThreadState *tstate = PyThreadState_GET();

            return UNICODE_ADD_INCREMENTAL(tstate, operand1, operand2);
        }
#endif
{% endif %}

{% if operator == "+" and left.hasOneOrBothType(right, "bytes") %}
#if PYTHON_VERSION >= 0x300
        if ({{ left.getBytesCheckExpression("*operand1")}} && {{right.getBytesCheckExpression("operand2") }}) {
            return BYTES_ADD_INCREMENTAL(operand1, operand2);
        }
#endif
{% endif %}
    }
{% endif %}

{% if left == object_desc and right == object_desc %}
{# Floats are very good for in-place operations. #}
    if (Py_TYPE(*operand1) == Py_TYPE(operand2)) {
{% if float_desc.hasTypeSpecializationCode(float_desc, nb_slot, sq_slot) %}
        if ({{left.getFloatCheckExpression("operand2")}}) {
            return _INPLACE_OPERATION_{{op_code}}_{{float_desc.getHelperCodeName()}}_{{float_desc.getHelperCodeName()}}(operand1, operand2);
        }
{% endif %}
{% if long_desc.hasTypeSpecializationCode(long_desc, nb_slot, sq_slot) %}
#if PYTHON_VERSION >= 0x300
        if ({{left.getLongCheckExpression("operand2")}}) {
            return _INPLACE_OPERATION_{{op_code}}_{{long_desc.getHelperCodeName()}}_{{long_desc.getHelperCodeName()}}(operand1, operand2);
        }
#endif
{% endif %}
    }
{% endif %}

{% if operator == "+" and left == right and left in (object_desc, str_desc) %}
{# Fast path for Python2 str values, very important for performance. #}
#if PYTHON_VERSION < 0x300
    // Python2 strings are to be treated differently, fall back to Python API here.
    if ({{ left.getStringCheckExpression("*operand1")}} && {{right.getStringCheckExpression("operand2") }}) {
        {# This could be inlined too, but it has some detail behind it. #}
        PyString_Concat(operand1, operand2);

        // TODO: Have this more globally passed in
        PyThreadState *tstate = PyThreadState_GET();

        return !HAS_ERROR_OCCURRED(tstate);
    }
#endif
{% endif %}
{% if operator == "+" and left == right and right == object_desc %}
{# Fast path for Python3 str values, very important for performance. #}
#if PYTHON_VERSION >= 0x300
    // Python3 Strings are to be treated differently.
    if ({{ left.getUnicodeCheckExpression("*operand1")}} && {{right.getUnicodeCheckExpression("operand2") }}) {
        // TODO: Have this more globally passed in
        PyThreadState *tstate = PyThreadState_GET();

        PyObject *result = UNICODE_CONCAT(tstate, *operand1, operand2);

        if (unlikely(result == NULL)) {
            return false;
        }

        Py_DECREF(*operand1);
        *operand1 = result;

        return true;
    }
#endif
{% endif %}

{# Fast path for list in-place add with at least one known list. #}
{% if operator == "+" and left.hasOneOrBothType(right, "list") %}
    if ({{ left.getListCheckExpression("*operand1")}} && {{ right.getListCheckExpression("operand2") }}) {
        return LIST_EXTEND_FROM_LIST(*operand1, operand2);
    }
{% endif %}

{# TODO: No special function for tuple += tuple yet. #}
{# Fast path for sequences in-place add with at least one known sequence type. #}
{% if operator == "+" and left.type_name in ("list", "tuple") and right.type_name in ("list", "tuple", "object") %}
    if ({{ left.getSequenceCheckExpression("*operand1", right)}} && {{ right.getSequenceCheckExpression("operand2", left) }}) {
        PyObject *result = PySequence_InPlaceConcat(*operand1, operand2);

        if (unlikely(result == NULL)) {
            return false;
        }

        Py_DECREF(*operand1);
        *operand1 = result;

        return true;
    }
{% endif %}

{% if left.hasTypeSpecializationCode(right, nb_slot, sq_slot) %}
{% set props = {"exits": {}} %}
    {{ call_binary_slot(props, operator, nb_slot, nb_inplace_slot, left, right, "result", "*operand1", "operand2", "exit_result_ok", "exit_result_exception") }}

    exit_result_ok:
    return true;

{% if "exit_result_exception" in props["exits"] %}
    exit_result_exception:
    return false;
{% endif %}
{% elif left.getSameTypeType(right).hasSameTypeOperationSpecializationCode(right.getSameTypeType(left), nb_slot, sq_slot) %}
{% set props = {"exits": {}} %}

{% if left == object_desc %}
    PyTypeObject *type1 = {{ left.getTypeValueExpression("*operand1") }};
{% endif %}
{% if right == object_desc %}
    PyTypeObject *type2 = {{ right.getTypeValueExpression("operand2") }};
{% endif %}

    if ({{ left.getTypeIdenticalCheckExpression(right, "type1", "type2") }}) {
        {# There is special code for same types. #}
        // return _BINARY_OPERATION_{{op_code}}_{{left.getSameTypeType(right).getHelperCodeName()}}_{{right.getSameTypeType(left).getHelperCodeName()}}_INPLACE(operand1, operand2);

        {{ call_binary_slot(props, operator, nb_slot, nb_inplace_slot, left.getSameTypeType(right), right.getSameTypeType(left), "result", "*operand1", "operand2", "exit_result_ok", "exit_result_exception") }}

        exit_result_ok:
        return true;

{% if "exit_result_exception" in props["exits"] %}
        exit_result_exception:
        return false;
{% endif %}
    }

    return __INPLACE_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}(operand1, operand2);
{% else %}
{% set props = {"exits": {}} %}
{% if left == object_desc %}
    PyTypeObject *type1 = {{ left.getTypeValueExpression("*operand1") }};
{% endif %}
{% if right == object_desc %}
    PyTypeObject *type2 = {{ right.getTypeValueExpression("operand2") }};
{% endif %}

    {{ inplace_fallback_operation(props, left, right, "type1", "type2") }}
{% endif %}
}

bool INPLACE_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}({{left.getVariableDecl("*operand1")}}, {{right.getVariableDecl("operand2")}}) {
    return _INPLACE_OPERATION_{{op_code}}_{{left.getHelperCodeName()}}_{{right.getHelperCodeName()}}(operand1, operand2);
}
