from __future__ import print_function
import sys

import gdb

import mcgdb_lite as mcgdb

# logging

PRINT_DEBUG_CAPTURE = False
PRINT_INFO_REPRESENTATION = True

def debug_capture(*objects, **kwargs):
    if not PRINT_DEBUG_CAPTURE: return
    print("<capture>", end=" ")
    print(*objects, **kwargs)

def info_representation(*objects, **kwargs):
    if not PRINT_INFO_REPRESENTATION: return
    print("[", end="")
    kwargs["end"] = "]\n"
    print(*objects, **kwargs)
    pass

#############
## Capture ##
#############

RETURN_VALUE_REGISTER = "$eax"

## <Documentation capture example># Capture `task_new` function calls
class TaskNewBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_new")

    def prepare_before (self):
        data = {}
        data["body"] = gdb.parse_and_eval("body")
        TaskBodyExecutionBreakpoint(str(data["body"]).split(" ")[0])
        
        return False, True, data
    
    def prepare_after (self, data):
        data["task_handle"] = gdb.parse_and_eval("(struct task_s *) {}".format(RETURN_VALUE_REGISTER))

        debug_capture("New task created: {} (id: {})".format(data["body"], data["task_handle"]))
        Task(data["task_handle"], data["body"])
## </Documentation capture example> ##
        
class TaskBodyExecutionBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self, addr):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "*{}".format(addr))

    def prepare_before (self):
        data = {}
        data["task_handle"] = gdb.newest_frame().older().read_var("task")
        task = data["task"] = Task.tasks[str(data["task_handle"])]
        task.start_execution()
        
        return False, True, data

    def prepare_after (self, data):
        data["task"].finish_execution()
    
class TaskDestroyedBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_destroy")

    def prepare_before (self):
        data = {}
        data["task_handle"] = gdb.parse_and_eval("task")

        debug_capture("Task destroyed: {}".format(data["task_handle"]))

        task = Task.tasks[str(data["task_handle"])]
        task.destroy()
        
        return False, False, None

class TaskRunBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_run")

    def prepare_before (self):
        data = {}
        data["task_handle"] = gdb.parse_and_eval("task")

        debug_capture("Task running: {}".format(data["task_handle"]))

        task = Task.tasks[str(data["task_handle"])]
        task.run()
        
        return False, False, None

class TaskSetDependencyBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_set_dependency")

    def prepare_before (self):
        data = {}
        data["src"] = gdb.parse_and_eval("src")
        data["dst"] = gdb.parse_and_eval("dst")

        debug_capture("Task dependency: {} --> {}".format(data["src"], data["dst"]))

        dst_task = Task.tasks[str(data["dst"])]

        if str(data["src"]) != "0x0":
            src_task = Task.tasks[str(data["src"])]
        else:
            src_task = None

        dst_task.depends_of(src_task)
        
        return False, False, None

class TaskGetResultBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_get_result")

    def prepare_before (self):
        data = {}
        data["task_handle"] = gdb.parse_and_eval("task")
        
        return False, True, data
    
    def prepare_after (self, data):
        data["return_value"] = gdb.parse_and_eval("(int) {}".format(RETURN_VALUE_REGISTER))
        debug_capture("Task result: {} {}".format(data["task_handle"], int(data["return_value"])))
        task = Task.tasks[str(data["task_handle"])]
        task.got_result(int(data["return_value"]))
        
class TaskGetFutureBreakpoint(mcgdb.capture.FunctionBreakpoint):
    def __init__(self):
        mcgdb.capture.FunctionBreakpoint.__init__(self, "task_get_future")

    def prepare_before (self):
        data = {}
        data["future"] = gdb.parse_and_eval("future")

        # Not very clean, assumes that `task_get_future`s parent (=`body`)'s
        # parent is `task_get_result`.
        # That won't work if task_get_future is called from a subfunction.
        # We could look for `body` in the stack, and then we know for sure
        # that its parent frame is `task_get_result`.
        
        data["task_handle"] = gdb.newest_frame().older().older().read_var("task")
        
        debug_capture("Task {} get future result from : {} ".format(
                data["task_handle"],
                data["future"]["task"]))

        task = Task.tasks[str(data["task_handle"])]
        remote_task = data["remote_task"] = Task.tasks[str(data["future"]["task"])]
        
        task.ask_result_from(remote_task)
        
        return False, True, data

    def prepare_after (self, data):
        data["return_value"] = gdb.parse_and_eval("(int) {}".format(RETURN_VALUE_REGISTER))
        debug_capture("Task {} got future result : {} ".format(
                data["task_handle"],
                data["return_value"]))

        task = Task.tasks[str(data["task_handle"])]
        remote_task = data["remote_task"]

        task.got_result_from(remote_task, int(data["return_value"]))

## <Documentation capture 2nd example># Instantiate capture breakpoints
def init_capture():
    TaskNewBreakpoint()
    TaskSetDependencyBreakpoint()
    TaskRunBreakpoint()
    TaskGetResultBreakpoint()
    TaskGetFutureBreakpoint()
    TaskDestroyedBreakpoint()
## </Documentation capture 2nd example>#

####################
## Representation ##
####################

## <Documentation representation example># Debugger representation for tasks
class Task:
    tasks = {}
    uids = 0
    
    def __init__(self, handle, body):
        self.body = body
        self.alive = True
        self.result = None
        self.depends_of_task = None
        self.executing = False
        self.running = False
        self.uid = Task.uids
        Task.uids += 1
        
        handle = str(handle)
        self.handle = handle
        Task.tasks[handle] = self
        info_representation("New task {} #{}".format(handle, self.uid))
        
    def run(self):
        self.running = True

    ## <Documentation representation start_exec example>    # called when the task starts its execution
    def start_execution(self):
        if mcgdb.toolbox.catchable.is_set("catch_exec", self.uid):
            mcgdb.capture.FunctionBreakpoint.push_stop_request(
                "Execution catchpoint triggered by Task #{}".format(self.uid))
        self.executing = True
## </Documentation representation start_exec example>#
        
    def finish_execution(self):
        self.executing = False
    
    def depends_of(self, src):
        self.depends_of_task = src

    def got_result(self, res):
        self.running = False
        self.result = res

    def ask_result_from(self, rmt):
        self.running = False
        pass

    def got_result_from(self, rmt, res):
        self.running = True
    
    def destroy(self):
        self.alive = False
        info_representation("Task {} #{} destroyed".format(self.handle, self.uid))
## </Documentation representation example>#
        
#################
## Interaction ##
#################

## Tasks listing
        
class cmd_task(gdb.Command):
    def __init__(self):
        gdb.Command.__init__(self, "task", gdb.COMMAND_OBSCURE, prefix=True)

## <Documentation interaction example># List the tasks of an application
class cmd_task_info(gdb.Command):
    def __init__(self):
        gdb.Command.__init__(self, "task info", gdb.COMMAND_OBSCURE, prefix=True)

    def invoke(self, arg, from_tty):
        for t in reversed(Task.tasks.values()):
            print("#{} Task {} {}".format(t.uid, t.handle, str(t.body).split(" ")[1]))
            if t.depends_of_task is not None:
                print("\tDepends of: #{} Task {}".format(t.depends_of_task.uid, t.depends_of_task.handle))
            print("\t{},\t{}, \tResult: {}".format(
                    "Alive" if t.alive else "Dead",
                    "Running" if t.running else "Not running",
                    t.result))
## </Documentation interaction example>#

def init_interaction_struct():
    cmd_task()
    cmd_task_info()

## Execution control

## <Documentation interaction 2nd example># Offer catchpoint on task body execution
class cmd_task_catch_exec(gdb.Command):
    """task catch_exec on|off [task id]*
Sets (on) or remove (off) a catchpoint on one (task id), several or all (empty) the tasks.
`task info` not implemented ...
"""
    
    def __init__ (self):
        gdb.Command.__init__(self, "task catch_exec", gdb.COMMAND_OBSCURE)
        mcgdb.toolbox.catchable.register("catch_exec")
        
    def invoke(self, arg, from_tty):
        args = gdb.string_to_argv(arg)
        if not args:
            print("Invalid parameters ...")
            print(cmd_task_catch_exec.__doc__)
            return
        do_activate = args.pop(0) == "on"

        tids = [mcgdb.toolbox.catchable.ALL_ENTITIES] if not args else args
            
        for tid in args:
            try:
                mcgdb.toolbox.catchable.activateRemove("catch_exec",
                                                       int(tid),
                                                       do_activate)
                print("{} execution catchpoint on task #{}.".format("Set" if do_activate else "Unset",
                                                                    tid))
            except Exception as e:
                print("Didn't work for task {}: {}".format(tid, e))
## </Documentation interaction 2nd example>#
def init_interaction_exec():
    cmd_task_catch_exec()
            
####################

def initialize():
    init_capture()
    init_interaction_struct()
    init_interaction_exec()
    
    print("[Model-Centric Debugging for 'task model' enabled]")
