import gdb
import json

HEAP_UNKNOWN = -1
STATIC_MEMORY = 0
HEAP_1 = 1
HEAP_2 = 2
HEAP_3 = 3
HEAP_4 = 4
HEAP_5 = 5

HeapTypeMap = {
  HEAP_1: "HEAP_1",
  HEAP_2: "HEAP_2",
  HEAP_3: "HEAP_3",
  HEAP_4: "HEAP_4",
  HEAP_5: "HEAP_5"
}


def get_freertos_heap_info(config, heap_block_link_type):
    heap_type = determine_memory_scheme(config)
    if heap_type == HEAP_UNKNOWN or heap_type == STATIC_MEMORY or heap_type == HEAP_3:
        return "{}"

    heap = {
      'type': HeapTypeMap[heap_type],
    }

    if heap_type != HEAP_5:
        heap_size = int(gdb.parse_and_eval("sizeof(ucHeap)"))
        heap_addr = int(gdb.parse_and_eval("&ucHeap"))
        heap['start'] = heap_addr
        heap['end'] = heap_addr + heap_size - 1
        heap['size'] = heap_size
    else:
        heap_size = 0
        regions = []
        heap_regions_sym, _ = gdb.lookup_symbol("xHeapRegions")
        if heap_regions_sym is not None:
            heap_regions = heap_regions_sym.value()
            min_index, max_index = heap_regions.type.range()
            for i in range(min_index, max_index):
                region = heap_regions[i]
                size = int(region['xSizeInBytes'])
                if size > 0:
                    heap_size += size
                    regions.append({
                      'number': i,
                      'start': int(region['pucStartAddress']),
                      'size': int(region['xSizeInBytes'])
                    })
        else:
            return "{\"error\": \"xHeapRegions is not defined\"}"

        heap['start'] = regions[0]['start']
        heap['end'] = heap['start'] + heap_size - 1
        heap['size'] = heap_size
        heap['regions'] = regions

    if heap_type == HEAP_1:
        heap['free'] = heap['size'] - int(gdb.parse_and_eval("xNextFreeByte"))
        heap['used'] = heap['size'] - heap['free']
        return json.dumps(heap)

    if heap_type != HEAP_2:
        try:
            heap['allocationsCount'] = int(gdb.parse_and_eval("xNumberOfSuccessfulAllocations"))
            heap['freesCount'] = int(gdb.parse_and_eval("xNumberOfSuccessfulFrees"))
        except gdb.error:
            pass

    heap['free'] = int(gdb.parse_and_eval("xFreeBytesRemaining"))
    heap['used'] = heap['size'] - heap['free']
    heap['blocks'] = __get_heap_data(heap, heap_block_link_type)

    return json.dumps(heap)


def determine_memory_scheme(config):
    if config.heap_type is not None:
        return config.heap_type
    uc_heap_s, _ = gdb.lookup_symbol("ucHeap")
    heap_struct_size_s, _ = gdb.lookup_symbol("xHeapStructSize")
    heap_5_heap_regions_s, _ = gdb.lookup_symbol("xHeapRegions")
    block_allocated_bit, _ = gdb.lookup_symbol("xBlockAllocatedBit")
    if uc_heap_s is not None:
        heap_2_struct_size_s, _ = gdb.lookup_symbol("heapSTRUCT_SIZE")
        if heap_struct_size_s is not None or block_allocated_bit is not None:
            config.heap_type = HEAP_4
            return HEAP_4
        elif heap_2_struct_size_s is not None:
            config.heap_type = HEAP_2
            return HEAP_2
        else:
            config.heap_type = HEAP_1
            return HEAP_1
    elif heap_struct_size_s is not None and heap_5_heap_regions_s is not None:
        config.heap_type = HEAP_5
        return HEAP_5
    else:
        config.heap_type = HEAP_3
        return HEAP_3


def __get_heap_data(heap, block_link_type):
    start_var_s, _ = gdb.lookup_symbol("xStart")
    data = []
    if start_var_s is not None:
        start_var = start_var_s.value()
        data = __get_free_heap_blocks(start_var, heap['end'], block_link_type)
    return data


def __get_free_heap_blocks(next_block_link, heap_end, block_link_type):  # gdb.Value, int, gdb.Type
    blocks = []
    while next_block_link is not None and next_block_link['pxNextFreeBlock'] != 0 \
            and next_block_link['pxNextFreeBlock'] < heap_end:
        block_size = int(next_block_link['pxNextFreeBlock']['xBlockSize'])
        if block_size > 0:
            blocks.append({
              'start': int(next_block_link['pxNextFreeBlock']),
              'size': block_size
            })
        next_block_link = next_block_link['pxNextFreeBlock'].cast(block_link_type.pointer()).dereference()
    return blocks
