# Copyright 1999-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import os
import sys
import threading

import portage.const
from portage.util import writemsg


def set_trace(on=True):
    if on:
        t = trace_handler()
        threading.settrace(t.event_handler)
        sys.settrace(t.event_handler)
    else:
        sys.settrace(None)
        threading.settrace(None)


class trace_handler:
    def __init__(self):
        python_system_paths = []
        for x in sys.path:
            if os.path.basename(x) == "python%s.%s" % sys.version_info[:2]:
                python_system_paths.append(x)

        self.ignore_prefixes = []
        for x in python_system_paths:
            self.ignore_prefixes.append(x + os.sep)

        self.trim_filename = prefix_trimmer(
            os.path.join(portage.const.PORTAGE_BASE_PATH, "lib") + os.sep
        ).trim
        self.show_local_lines = False
        self.max_repr_length = 200

    def event_handler(self, *args):
        frame, event, _arg = args
        if "line" == event:
            if self.show_local_lines:
                self.trace_line(*args)
        else:
            if not self.ignore_filename(frame.f_code.co_filename):
                self.trace_event(*args)
                return self.event_handler

    def trace_event(self, frame, event, arg):
        writemsg(
            "%s line=%d name=%s event=%s %slocals=%s\n"
            % (
                self.trim_filename(frame.f_code.co_filename),
                frame.f_lineno,
                frame.f_code.co_name,
                event,
                self.arg_repr(frame, event, arg),
                self.locals_repr(frame, event, arg),
            )
        )

    def arg_repr(self, _frame, event, arg):
        my_repr = None
        if "return" == event:
            my_repr = repr(arg)
            if len(my_repr) > self.max_repr_length:
                my_repr = "'omitted'"
            return f"value={my_repr} "
        if "exception" == event:
            my_repr = repr(arg[1])
            if len(my_repr) > self.max_repr_length:
                my_repr = "'omitted'"
            return f"type={arg[0]} value={my_repr} "

        return ""

    def trace_line(self, frame, _event, _arg):
        writemsg(
            "%s line=%d\n"
            % (self.trim_filename(frame.f_code.co_filename), frame.f_lineno)
        )

    def ignore_filename(self, filename):
        if filename:
            for x in self.ignore_prefixes:
                if filename.startswith(x):
                    return True
        return False

    def locals_repr(self, frame, _event, _arg):
        """Create a representation of the locals dict that is suitable for
        tracing output."""

        my_locals = frame.f_locals.copy()

        # prevent unsafe  __repr__ call on self when __init__ is called
        # (method calls aren't safe until after __init__  has completed).
        if frame.f_code.co_name == "__init__" and "self" in my_locals:
            my_locals["self"] = "omitted"

        # We omit items that will lead to unreasonable bloat of the trace
        # output (and resulting log file).
        for k, v in my_locals.items():
            my_repr = repr(v)
            if len(my_repr) > self.max_repr_length:
                my_locals[k] = "omitted"
        return my_locals


class prefix_trimmer:
    def __init__(self, prefix):
        self.prefix = prefix
        self.cut_index = len(prefix)
        self.previous = None
        self.previous_trimmed = None

    def trim(self, s):
        """Remove a prefix from the string and return the result.
        The previous result is automatically cached."""
        if s == self.previous:
            return self.previous_trimmed

        if s.startswith(self.prefix):
            self.previous_trimmed = s[self.cut_index :]
        else:
            self.previous_trimmed = s
        return self.previous_trimmed
