import gdb
from freertos.List import ListManager, get_list_container
import json


def get_task_name_by_handle(handle, tcb_type):
    tcb_ptr = gdb.Value(handle).cast(tcb_type.pointer())
    tcb = tcb_ptr.dereference()
    return tcb['pcTaskName'].string()


def get_curr_task(config, types):
    if config.is_smp:
        core_id = int(gdb.parse_and_eval("$_gthread")) - 1
        curr_tcb_ptr, _ = gdb.lookup_symbol("pxCurrentTCBs")
        curr_tcb_val = curr_tcb_ptr.value()
        if curr_tcb_val[core_id] == 0:
            return json.dumps("{}")
        curr_tcb = curr_tcb_val[core_id].cast(types.tcb_type.pointer()).dereference()
        state = "Running on core " + str(core_id)
    else:
        curr_tcb_ptr, _ = gdb.lookup_symbol("pxCurrentTCB")
        curr_tcb_val = curr_tcb_ptr.value()
        if curr_tcb_val == 0:
            return json.dumps("{}")
        curr_tcb = curr_tcb_val.cast(types.tcb_type.pointer()).dereference()
        state = "Running"
    task = prepare_task_data(None, curr_tcb, state, config, types, 1)
    if not config.use_trace_facility:
        task['id'] = config.get_task_id(task['name'])
    return json.dumps(task)


def get_freertos_tasks(config, types):
    total_runtime = None
    if config.generate_runtime_stats:
        total_runtime, _ = gdb.lookup_symbol("ulTotalRunTime")
        if total_runtime is None:
            config.generate_runtime_stats = False
        else:
            try:
                total_runtime = int(total_runtime.value())
            except gdb.error:
                config.generate_runtime_stats = False

    result = {}
    if config.is_smp:
        running_task_ptr = int(gdb.parse_and_eval("pxCurrentTCBs[$_gthread - 1]"))
        curr_tcb_ptr = "pxCurrentTCBs"
        curr_tcb_ptr, _ = gdb.lookup_symbol(curr_tcb_ptr)
        curr_tcb_val = curr_tcb_ptr.value()
        min_index, max_index = curr_tcb_val.type.range()
        for i in range(min_index, max_index + 1):
            curr_tcb = curr_tcb_val[i].cast(types.tcb_type.pointer()).dereference()
            result[int(curr_tcb.address)] = prepare_task_data(None, curr_tcb, "Running on core " + str(i), config, types, total_runtime)
    else:
        running_task_ptr = int(gdb.parse_and_eval("pxCurrentTCB"))

    ready_lists = [ListManager("xPendingReadyList", types)]

    ready_tasks_lists_name = "pxReadyTasksLists"
    ready_lists_sym, _ = gdb.lookup_symbol(ready_tasks_lists_name)
    if ready_lists_sym is not None:
        _ready_lists = ready_lists_sym.value()  # List_t [configMAX_PRIORITIES]
        min_index, max_index = _ready_lists.type.range()  # 0, configMAX_PRIORITIES - 1
        for i in range(min_index, max_index + 1):
            ready_lists.append(ListManager(_ready_lists[i], types))
    else:
        raise ValueError("Failed to Find Symbol: %s" % ready_tasks_lists_name)

    for i, rlist in enumerate(ready_lists):
        extract_tasks(rlist, result, running_task_ptr, "Ready", config, types, total_runtime)

    extract_tasks(ListManager("xSuspendedTaskList", types), result, running_task_ptr, "Blocked", config, types, total_runtime)
    extract_tasks(ListManager("xDelayedTaskList1", types), result, running_task_ptr, "Delayed", config, types, total_runtime)
    extract_tasks(ListManager("xDelayedTaskList2", types), result, running_task_ptr, "Delayed", config, types, total_runtime)

    tasks = list(result.values())
    if not config.use_trace_facility:  # have to take care of the identifiers ourselves
        tasks.sort(key=lambda tsk: tsk['name'])
        for task in tasks:
            task['id'] = config.get_task_id(task['name'])

    tasks.sort(key=lambda tsk: tsk['id'])
    return json.dumps(tasks)


def extract_tasks(values_list, result_dict, running_task_ptr, state, config, types, total_runtime):
    items = values_list.get_elements(types.tcb_type)
    for tcb, val in items:
        if int(tcb.address) not in result_dict:
            result_dict[int(tcb.address)] = prepare_task_data(running_task_ptr, tcb, state, config, types, total_runtime)


def prepare_task_data(running_task_ptr, task, state, config, types, total_runtime):  # : gdb.Value, : str
    task_addr = int(task.address)

    if task_addr == running_task_ptr:
        state = "Running"

    if config.use_trace_facility:
        task_number = int(task['uxTCBNumber'])
    else:
        task_number = -1

    if config.use_mutexes:
        base_priority = int(task['uxBasePriority'])
    else:
        base_priority = 0

    try:
        pc_buff = gdb.selected_inferior().read_memory(int(task['pxTopOfStack']) + 56, 4)
        pc = ''.join('%.2x' % ord(pc_buff[i]) for i in reversed(range(len(pc_buff))))
    except gdb.error:
        pc = 0

    stack_base_address = int(task['pxStack'])

    result = {
        'name': task['pcTaskName'].string(),
        'currentPriority': int(task['uxPriority']),
        'basePriority': base_priority,
        'handle': task_addr,
        'state': state,
        'id': task_number,
        'stackTop': int(task['pxTopOfStack']),
        'stackBase': stack_base_address,
        'eventObjectPtr': get_list_container(task['xEventListItem']),
        'programCounter': int('0x%s' % pc, 0)
    }

    if config.is_posix:
        thread = (task['pxTopOfStack'] + 1).cast(types.thread_type.pointer()).dereference()
        result['posixThreadId'] = hex(thread['pthread'])

    if config.record_stack_high_address:
        stack_end_address = int(task['pxEndOfStack'])
        result['stackEnd'] = stack_end_address
        result['stackHWMark'] = __get_stack_high_water_address(stack_base_address, stack_end_address)

    if config.generate_runtime_stats and total_runtime > 0:
        result['runtime'] = round(100.0 * int(task['ulRunTimeCounter']) / total_runtime, 2)
    return result


def __get_stack_high_water_address(stack_base_address, stack_end_address):
    stack_data_block = gdb.selected_inferior().read_memory(stack_base_address,
                                                           abs(stack_end_address - stack_base_address))
    free_bytes_count = 0
    empty_byte_char = b'\xa5'
    for data_byte in stack_data_block:
        if data_byte != empty_byte_char:
            break
        free_bytes_count += 1

    return stack_base_address + free_bytes_count
