#
# Copyright BitBake Contributors
#
# SPDX-License-Identifier: GPL-2.0-only
#

import inspect
import traceback
import bb.namedtuple_with_abc
from collections import namedtuple


class TracebackEntry(namedtuple.abc):
    """Pickleable representation of a traceback entry"""
    _fields = 'filename lineno function args code_context index'
    _header = '  File "{0.filename}", line {0.lineno}, in {0.function}{0.args}'

    def format(self, formatter=None):
        if not self.code_context:
            return self._header.format(self) + '\n'

        formatted = [self._header.format(self) + ':\n']

        for lineindex, line in enumerate(self.code_context):
            if formatter:
                line = formatter(line)

            if lineindex == self.index:
                formatted.append('    >%s' % line)
            else:
                formatted.append('     %s' % line)
        return formatted

    def __str__(self):
        return ''.join(self.format())

def _get_frame_args(frame):
    """Get the formatted arguments and class (if available) for a frame"""
    arginfo = inspect.getargvalues(frame)

    try:
        if not arginfo.args:
            return '', None
    # There have been reports from the field of python 2.6 which doesn't 
    # return a namedtuple here but simply a tuple so fallback gracefully if
    # args isn't present.
    except AttributeError:
        return '', None

    firstarg = arginfo.args[0]
    if firstarg == 'self':
        self = arginfo.locals['self']
        cls = self.__class__.__name__

        arginfo.args.pop(0)
        del arginfo.locals['self']
    else:
        cls = None

    formatted = inspect.formatargvalues(*arginfo)
    return formatted, cls

def extract_traceback(tb, context=1):
    frames = inspect.getinnerframes(tb, context)
    for frame, filename, lineno, function, code_context, index in frames:
        formatted_args, cls = _get_frame_args(frame)
        if cls:
            function = '%s.%s' % (cls, function)
        yield TracebackEntry(filename, lineno, function, formatted_args,
                             code_context, index)

def format_extracted(extracted, formatter=None, limit=None):
    if limit:
        extracted = extracted[-limit:]

    formatted = []
    for tracebackinfo in extracted:
        formatted.extend(tracebackinfo.format(formatter))
    return formatted


def format_exception(etype, value, tb, context=1, limit=None, formatter=None):
    formatted = ['Traceback (most recent call last):\n']

    if hasattr(tb, 'tb_next'):
        tb = extract_traceback(tb, context)

    formatted.extend(format_extracted(tb, formatter, limit))
    formatted.extend(traceback.format_exception_only(etype, value))
    return formatted

def to_string(exc):
    if isinstance(exc, SystemExit):
        if not isinstance(exc.code, str):
            return 'Exited with "%d"' % exc.code
    return str(exc)
