#     Copyright 2022, 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.
#
""" Code templates for frames of all kinds.

"""

# Frame in a function
template_frame_guard_full_block = """\
if (isFrameUnusable(%(frame_cache_identifier)s)) {
    Py_XDECREF(%(frame_cache_identifier)s);

#if _DEBUG_REFCOUNTS
    if (%(frame_cache_identifier)s == NULL) {
        count_active_frame_cache_instances += 1;
    } else {
        count_released_frame_cache_instances += 1;
    }
    count_allocated_frame_cache_instances += 1;
#endif
    %(frame_cache_identifier)s = %(make_frame_code)s
#if _DEBUG_REFCOUNTS
} else {
    count_hit_frame_cache_instances += 1;
#endif
}
assert(%(frame_cache_identifier)s->m_type_description == NULL);
%(frame_identifier)s = %(frame_cache_identifier)s;
%(frame_init_code)s

// Push the new frame as the currently active one.
pushFrameStack(%(frame_identifier)s);

// Mark the frame object as in use, ref count 1 will be up for reuse.
assert(Py_REFCNT(%(frame_identifier)s) == 2); // Frame stack

// Framed code:
%(codes)s

#if %(needs_preserve)d
RESTORE_FRAME_EXCEPTION(%(frame_identifier)s);
#endif

// Put the previous frame back on top.
popFrameStack();
%(frame_exit_code)s

goto %(no_exception_exit)s;
"""

template_frame_guard_full_return_handler = """\
%(frame_return_exit)s:;
#if %(needs_preserve)d
RESTORE_FRAME_EXCEPTION(%(frame_identifier)s);
#endif

// Put the previous frame back on top.
popFrameStack();
%(frame_exit_code)s

goto %(return_exit)s;
"""

template_frame_attach_locals = """\
Nuitka_Frame_AttachLocals(
    %(frame_identifier)s,
    %(type_description)s%(frame_variable_refs)s
);
"""

template_frame_guard_full_exception_handler = """\
%(frame_exception_exit)s:;

#if %(needs_preserve)d
RESTORE_FRAME_EXCEPTION(%(frame_identifier)s);
#endif

if (%(exception_tb)s == NULL) {
    %(exception_tb)s = %(tb_making)s;
} else if (%(exception_tb)s->tb_frame != &%(frame_identifier)s->m_frame) {
    %(exception_tb)s = ADD_TRACEBACK(%(exception_tb)s, %(frame_identifier)s, %(exception_lineno)s);
}

// Attaches locals to frame if any.
%(attach_locals)s

// Release cached frame if used for exception.
if (%(frame_identifier)s == %(frame_cache_identifier)s) {
#if _DEBUG_REFCOUNTS
    count_active_frame_cache_instances -= 1;
    count_released_frame_cache_instances += 1;
#endif

    Py_DECREF(%(frame_cache_identifier)s);
    %(frame_cache_identifier)s = NULL;
}

assertFrameObject(%(frame_identifier)s);

// Put the previous frame back on top.
popFrameStack();
%(frame_exit_code)s

// Return the error.
goto %(parent_exception_exit)s;
"""

# Frame for a module. TODO: Use it for functions called only once.
# TODO: The once guard need not take a reference count in its frame class.
template_frame_guard_once_block = """\
// Frame without reuse.
%(frame_identifier)s = MAKE_MODULE_FRAME(%(code_identifier)s, %(module_identifier)s);

// Push the new frame as the currently active one, and we should be exclusively
// owning it.
pushFrameStack(%(frame_identifier)s);
assert(Py_REFCNT(%(frame_identifier)s) == 2);

// Framed code:
%(codes)s

// Restore frame exception if necessary.
#if %(needs_preserve)d
RESTORE_FRAME_EXCEPTION(%(frame_identifier)s);
#endif
popFrameStack();

goto %(no_exception_exit)s;
"""

template_frame_guard_once_exception_handler = """\
%(frame_exception_exit)s:;
#if %(needs_preserve)d
RESTORE_FRAME_EXCEPTION(%(frame_identifier)s);
#endif

if (%(exception_tb)s == NULL) {
    %(exception_tb)s = %(tb_making)s;
} else if (exception_tb->tb_frame != &%(frame_identifier)s->m_frame) {
    %(exception_tb)s = ADD_TRACEBACK(%(exception_tb)s, %(frame_identifier)s, %(exception_lineno)s);
}

// Put the previous frame back on top.
popFrameStack();

// Return the error.
goto %(parent_exception_exit)s;
"""

# Frame in a generator, coroutine or asyncgen.
template_frame_guard_generator = """\
if (isFrameUnusable(%(frame_cache_identifier)s)) {
    Py_XDECREF(%(frame_cache_identifier)s);

#if _DEBUG_REFCOUNTS
    if (%(frame_cache_identifier)s == NULL) {
        count_active_frame_cache_instances += 1;
    } else {
        count_released_frame_cache_instances += 1;
    }
    count_allocated_frame_cache_instances += 1;
#endif
    %(frame_cache_identifier)s = MAKE_FUNCTION_FRAME(%(code_identifier)s, %(module_identifier)s, %(locals_size)s);
#if _DEBUG_REFCOUNTS
} else {
    count_hit_frame_cache_instances += 1;
#endif
}
%(context_identifier)s->m_frame = %(frame_cache_identifier)s;

// Mark the frame object as in use, ref count 1 will be up for reuse.
Py_INCREF(%(context_identifier)s->m_frame);
assert(Py_REFCNT(%(context_identifier)s->m_frame) == 2); // Frame stack

#if PYTHON_VERSION >= 0x340
Nuitka_SetFrameGenerator(%(context_identifier)s->m_frame, (PyObject *)%(context_identifier)s);
#endif

assert(%(context_identifier)s->m_frame->m_frame.f_back == NULL);

pushFrameStack(%(context_identifier)s->m_frame);

// Taking a reference prevents reuse of generator frame while it is being used.
Py_INCREF(%(context_identifier)s->m_frame->m_frame.f_back);

#if PYTHON_VERSION >= 0x300
// Accept currently existing exception as the one to publish again when we
// yield or yield from.
{
    PyThreadState *thread_state = PyThreadState_GET();

#if PYTHON_VERSION < 0x3b0
    EXC_TYPE_F(%(context_identifier)s) = EXC_TYPE(thread_state);
    if (EXC_TYPE_F(%(context_identifier)s) == Py_None) EXC_TYPE_F(%(context_identifier)s) = NULL;
    Py_XINCREF(EXC_TYPE_F(%(context_identifier)s));
#endif
    EXC_VALUE_F(%(context_identifier)s) = EXC_VALUE(thread_state);
    Py_XINCREF(EXC_VALUE_F(%(context_identifier)s));
#if PYTHON_VERSION < 0x3b0
    ASSIGN_EXC_TRACEBACK_F(%(context_identifier)s, EXC_TRACEBACK(thread_state));
    Py_XINCREF(EXC_TRACEBACK_F(%(context_identifier)s));
#endif
}

#endif

// Framed code:
%(codes)s

Nuitka_Frame_MarkAsNotExecuting(%(context_identifier)s->m_frame);

#if PYTHON_VERSION >= 0x300
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TYPE_F(%(context_identifier)s));
#endif
Py_CLEAR(EXC_VALUE_F(%(context_identifier)s));
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TRACEBACK_F(%(context_identifier)s));
#endif
#endif

// Allow re-use of the frame again.
Py_DECREF(%(context_identifier)s->m_frame);
goto %(no_exception_exit)s;
"""


# TODO: This cannot happen, can it?
template_frame_guard_generator_return_handler = """\
%(frame_return_exit)s:;

#if PYTHON_VERSION >= 0x300
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TYPE_F(%(context_identifier)s));
#endif
Py_CLEAR(EXC_VALUE_F(%(context_identifier)s));
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TRACEBACK_F(%(context_identifier)s));
#endif
#endif

Py_DECREF(%(frame_identifier)s);
goto %(return_exit)s;
"""


template_frame_guard_generator_exception_handler = """\
%(frame_exception_exit)s:;

// If it's not an exit exception, consider and create a traceback for it.
if (!EXCEPTION_MATCH_GENERATOR(%(exception_type)s)) {
    if (%(exception_tb)s == NULL) {
        %(exception_tb)s = %(tb_making)s;
    } else if (%(exception_tb)s->tb_frame != &%(frame_identifier)s->m_frame) {
        %(exception_tb)s = ADD_TRACEBACK(%(exception_tb)s, %(frame_identifier)s, %(exception_lineno)s);
    }

%(attach_locals)s

    // Release cached frame if used for exception.
    if (%(frame_identifier)s == %(frame_cache_identifier)s) {
#if _DEBUG_REFCOUNTS
        count_active_frame_cache_instances -= 1;
        count_released_frame_cache_instances += 1;
#endif

        Py_DECREF(%(frame_cache_identifier)s);
        %(frame_cache_identifier)s = NULL;
    }

    assertFrameObject(%(frame_identifier)s);
}

#if PYTHON_VERSION >= 0x300
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TYPE_F(%(context_identifier)s));
#endif
Py_CLEAR(EXC_VALUE_F(%(context_identifier)s));
#if PYTHON_VERSION < 0x3b0
Py_CLEAR(EXC_TRACEBACK_F(%(context_identifier)s));
#endif
#endif

Py_DECREF(%(frame_identifier)s);

// Return the error.
goto %(parent_exception_exit)s;
"""

from . import TemplateDebugWrapper  # isort:skip

TemplateDebugWrapper.checkDebug(globals())
