import gdb
import ompdModule
import itertools
from gdb.FrameDecorator import FrameDecorator
import ompd
from ompd_handles import ompd_task, ompd_parallel, ompd_thread
import traceback
from tempfile import NamedTemporaryFile


class OmpdFrameDecorator(FrameDecorator):
    def __init__(self, fobj, curr_task_handle):
        """Initializes a FrameDecorator with the given GDB Frame object. The global OMPD address space defined in
        ompd.py is set as well.
        """
        super(OmpdFrameDecorator, self).__init__(fobj)
        self.addr_space = ompd.addr_space
        self.fobj = None
        if isinstance(fobj, gdb.Frame):
            self.fobj = fobj
        elif isinstance(fobj, FrameDecorator):
            self.fobj = fobj.inferior_frame()
        self.curr_task_handle = curr_task_handle

    def function(self):
        """This appends the name of a frame that is printed with the information whether the task started in the frame
        is implicit or explicit. The ICVs are evaluated to determine that.
        """
        name = str(self.fobj.name())

        if self.curr_task_handle is None:
            return name

        icv_value = ompdModule.call_ompd_get_icv_from_scope(
            self.curr_task_handle,
            ompd.icv_map["implicit-task-var"][1],
            ompd.icv_map["implicit-task-var"][0],
        )
        if icv_value == 0:
            name = '@thread %i: %s "#pragma omp task"' % (
                gdb.selected_thread().num,
                name,
            )
        elif icv_value == 1:
            name = '@thread %i: %s "#pragma omp parallel"' % (
                gdb.selected_thread().num,
                name,
            )
        else:
            name = "@thread %i: %s" % (gdb.selected_thread().num, name)
        return name


class OmpdFrameDecoratorThread(FrameDecorator):
    def __init__(self, fobj):
        """Initializes a FrameDecorator with the given GDB Frame object."""
        super(OmpdFrameDecoratorThread, self).__init__(fobj)
        if isinstance(fobj, gdb.Frame):
            self.fobj = fobj
        elif isinstance(fobj, FrameDecorator):
            self.fobj = fobj.inferior_frame()

    def function(self):
        name = str(self.fobj.name())
        return "@thread %i: %s" % (gdb.selected_thread().num, name)


class FrameFilter:
    def __init__(self, addr_space):
        """Initializes the FrameFilter, registers is in the GDB runtime and saves the given OMPD address space capsule."""
        self.addr_space = addr_space
        self.name = "Filter"
        self.priority = 100
        self.enabled = True
        gdb.frame_filters[self.name] = self
        self.switched_on = False
        self.continue_to_master = False

    def set_switch(self, on_off):
        """Prints output when executing 'ompd bt on' or 'ompd bt off'."""
        self.switched_on = on_off
        if self.switched_on:
            print('Enabled filter for "bt" output successfully.')
        else:
            print('Disabled filter for "bt" output successfully.')

    def set_switch_continue(self, on_off):
        """Prints output when executing 'ompd bt on continued'." """
        self.continue_to_master = on_off
        if self.continue_to_master:
            print(
                'Enabled "bt" mode that continues backtrace on to master thread for worker threads.'
            )
        else:
            print('Disabled "bt" mode that continues onto master thread.')

    def get_master_frames_for_worker(self, past_thread_num, latest_sp):
        """Prints master frames for worker thread with id past_thread_num."""
        gdb.execute("t 1")
        gdb.execute("ompd bt on")
        gdb.execute("bt")

        frame = gdb.newest_frame()

        while frame.older() is not None:
            print("master frame sp:", str(frame.read_register("sp")))
            yield OmpdFrameDecorator(frame)
            frame = frame.older()
        print("latest sp:", str(latest_sp))

        gdb.execute("ompd bt on continued")
        gdb.execute("t %d" % int(past_thread_num))

    def filter_frames(self, frame_iter):
        """Iterates through frames and only returns those that are relevant to the application
        being debugged. The OmpdFrameDecorator is applied automatically.
        """
        curr_thread_num = gdb.selected_thread().num
        is_no_omp_thread = False
        if curr_thread_num in self.addr_space.threads:
            curr_thread_obj = self.addr_space.threads[curr_thread_num]
            self.curr_task = curr_thread_obj.get_current_task()
            self.frames = self.curr_task.get_task_frame()
        else:
            is_no_omp_thread = True
            print(
                "Thread %d is no OpenMP thread, printing all frames:" % curr_thread_num
            )

        stop_iter = False
        for x in frame_iter:
            if is_no_omp_thread:
                yield OmpdFrameDecoratorThread(x)
                continue

            if x.inferior_frame().older() is None:
                continue
            if self.curr_task.task_handle is None:
                continue

            gdb_sp = int(str(x.inferior_frame().read_register("sp")), 16)
            gdb_sp_next_new = int(
                str(x.inferior_frame()).split(",")[0].split("=")[1], 16
            )
            if x.inferior_frame().older():
                gdb_sp_next = int(
                    str(x.inferior_frame().older().read_register("sp")), 16
                )
            else:
                gdb_sp_next = int(str(x.inferior_frame().read_register("sp")), 16)
            while 1:
                (ompd_enter_frame, ompd_exit_frame) = self.frames

                if ompd_enter_frame != 0 and gdb_sp_next_new < ompd_enter_frame:
                    break
                if ompd_exit_frame != 0 and gdb_sp_next_new < ompd_exit_frame:
                    if (
                        x.inferior_frame().older().older()
                        and int(
                            str(x.inferior_frame().older().older().read_register("sp")),
                            16,
                        )
                        < ompd_exit_frame
                    ):
                        if self.continue_to_master:
                            yield OmpdFrameDecoratorThread(x)
                        else:
                            yield OmpdFrameDecorator(x, self.curr_task.task_handle)
                    else:
                        yield OmpdFrameDecorator(x, self.curr_task.task_handle)
                    break
                sched_task_handle = self.curr_task.get_scheduling_task_handle()

                if sched_task_handle is None:
                    stop_iter = True
                    break

                self.curr_task = self.curr_task.get_scheduling_task()
                self.frames = self.curr_task.get_task_frame()
            if stop_iter:
                break

        # implementation of "ompd bt continued"
        if self.continue_to_master:

            orig_thread = gdb.selected_thread().num
            gdb_threads = dict([(t.num, t) for t in gdb.selected_inferior().threads()])

            # iterate through generating tasks until outermost task is reached
            while 1:
                # get OMPD thread id for master thread (systag in GDB output)
                try:
                    master_num = (
                        self.curr_task.get_task_parallel()
                        .get_thread_in_parallel(0)
                        .get_thread_id()
                    )
                except:
                    break
                # search for thread id without the "l" for long via "thread find" and get GDB thread num from output
                hex_str = str(hex(master_num))
                thread_output = gdb.execute(
                    "thread find %s" % hex_str[0 : len(hex_str) - 1], to_string=True
                ).split(" ")
                if thread_output[0] == "No":
                    raise ValueError("Master thread num could not be found!")
                gdb_master_num = int(thread_output[1])
                # get task that generated last task of worker thread
                try:
                    self.curr_task = (
                        self.curr_task.get_task_parallel()
                        .get_task_in_parallel(0)
                        .get_generating_task()
                    )
                except:
                    break
                self.frames = self.curr_task.get_task_frame()
                (enter_frame, exit_frame) = self.frames
                if exit_frame == 0:
                    print("outermost generating task was reached")
                    break

                # save GDB num for worker thread to change back to it later
                worker_thread = gdb.selected_thread().num

                # use InferiorThread.switch()
                gdb_threads = dict(
                    [(t.num, t) for t in gdb.selected_inferior().threads()]
                )
                gdb_threads[gdb_master_num].switch()
                print("#### switching to thread %i ####" % gdb_master_num)

                frame = gdb.newest_frame()
                stop_iter = False

                while not stop_iter:
                    if self.curr_task.task_handle is None:
                        break
                    self.frames = self.curr_task.get_task_frame()

                    while frame:
                        if self.curr_task.task_handle is None:
                            break

                        gdb_sp_next_new = int(
                            str(frame).split(",")[0].split("=")[1], 16
                        )

                        if frame.older():
                            gdb_sp_next = int(
                                str(frame.older().read_register("sp")), 16
                            )
                        else:
                            gdb_sp_next = int(str(frame.read_register("sp")), 16)

                        while 1:
                            (ompd_enter_frame, ompd_exit_frame) = self.frames

                            if (
                                ompd_enter_frame != 0
                                and gdb_sp_next_new < ompd_enter_frame
                            ):
                                break
                            if (
                                ompd_exit_frame == 0
                                or gdb_sp_next_new < ompd_exit_frame
                            ):
                                if (
                                    ompd_exit_frame == 0
                                    or frame.older()
                                    and frame.older().older()
                                    and int(
                                        str(frame.older().older().read_register("sp")),
                                        16,
                                    )
                                    < ompd_exit_frame
                                ):
                                    yield OmpdFrameDecoratorThread(frame)
                                else:
                                    yield OmpdFrameDecorator(
                                        frame, self.curr_task.task_handle
                                    )
                                break
                            sched_task_handle = (
                                ompdModule.call_ompd_get_scheduling_task_handle(
                                    self.curr_task.task_handle
                                )
                            )

                            if sched_task_handle is None:
                                stop_iter = True
                                break
                            self.curr_task = self.curr_task.get_generating_task()
                            self.frames = self.curr_task.get_task_frame()

                        frame = frame.older()
                    break

                gdb_threads[worker_thread].switch()

            gdb_threads[orig_thread].switch()

    def filter(self, frame_iter):
        """Function is called automatically with every 'bt' executed. If switched on, this will only let revelant frames be printed
        or all frames otherwise. If switched on, a FrameDecorator will be applied to state whether '.ompd_task_entry.' refers to an
        explicit or implicit task.
        """
        if self.switched_on:
            return self.filter_frames(frame_iter)
        else:
            return frame_iter
