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 = 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['ompd-implicit-var'][1], ompd.icv_map['ompd-implicit-var'][0])
                if icv_value == 0:
                        name = '@thread %i: "#pragma omp task"' % gdb.selected_thread().num
                elif icv_value == 1:
                        name = '@thread %i: "#pragma omp parallel"' % gdb.selected_thread().num
                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)
                                master_num = self.curr_task.get_task_parallel().get_thread_in_parallel(0).get_thread_id()

                                # 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
                                self.curr_task = self.curr_task.get_task_parallel().get_task_in_parallel(0).get_generating_task()
                                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
