import gdb.printing
import re

def parseCompressedPairElement(elem: gdb.Value) -> gdb.Value:
    return elem[elem.type.fields()[0]]

def parseCompressedPair(cpair: gdb.Value) -> tuple[gdb.Value, gdb.Value]:
    fields = cpair.type.fields()

    first = cpair[fields[0]]
    second = cpair[fields[1]]

    return parseCompressedPairElement(first), parseCompressedPairElement(second)

def delegateChildren(val: gdb.Value):
    try:
        for field in val.type.fields():
            yield field.name, val[field]
    except TypeError:
        yield '[value]', val

class vectorPrinter:
    class _iterator:
        def __init__(self, item, end, idx):
            self.item = item
            self.end = end
            self.size = self.end - self.item
            self.idx = idx

        def __iter__(self):
            return self

        def __next__(self):
            if self.item >= self.end:
                raise StopIteration
            key = str(self.idx)
            iter = self.item.dereference()
            self.item += 1
            self.idx += 1
            return key, iter

    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "std::vector of size %d, capacity %d" % (self.val['m_size'], self.val['m_capacity'])

    def display_hint(self):
        return 'array'

    def children(self):
        if self.val['m_size'] == 0:
            return []
        data, alloc = parseCompressedPair(self.val['m_data'])
        return self._iterator(data, data + self.val['m_size'], 0)

def _leftmost(node):
    ret = node
    while ret['left'] != 0:
        ret = ret['left'].dereference()
    return ret

def _next(node):
    if node['right']:
        return _leftmost(node['right'].dereference())
    else:
        if node['parent'] == 0:
            return None
        parent = node['parent'].dereference()
        if parent['left'] == node.address:
            return parent
        ret = node
        while True:
            ret = ret['parent'].dereference()
            if ret['parent'] == 0:
                return None
            if ret['parent'].dereference()['left'] == ret.address:
                break
        return ret['parent'].dereference()

class rbtreePrinter:
    def __init__(self, type, val):
        self.type: gdb.Type = type
        self.val: gdb.Value = val['tree']

    def to_string(self):
        return "%s of size %d" % (self.type, self.num_children())

    def display_hint(self):
        return 'array'

    def num_children(self):
        size, _ = parseCompressedPair(self.val['size_data'])
        return size

    def children(self):
        root, alloc = parseCompressedPair(self.val['root_data'])
        size, comp = parseCompressedPair(self.val['size_data'])

        # yield '[alloc]', alloc
        # yield '[comp]', comp
        # yield '[root]', root
        if root == 0:
            return

        nd = _leftmost(root.dereference())
        for i in range(size):
            yield str(i), nd['value']
            nd = _next(nd)
            if nd == None:
                break

class stringPrinter:
    def __init__(self, val: gdb.Value):
        self.val = val

    def to_string(self):
        data, alloc = parseCompressedPair(self.val['m_data'])
        data = data['in']

        if data['stackdata']['end'] == 0:
            return data['stackdata']['str'].string()
        return data['heapdata']['m_ptr'].string()

    def display_hint(self):
        return 'string'

class stringViewPrinter:
    def __init__(self, val: gdb.Value):
        self.val = val
        self.string = val['m_str']
        self.length = val['m_len']

    def to_string(self):
        return self.string.string(length=self.length)

    def display_hint(self):
        return 'string'

class listPrinter:
    def __init__(self, val):
        self.val: gdb.Value = val
        self.type: gdb.Type = val.type

        this_type = self.type.unqualified().strip_typedefs()
        if this_type.tag == None:
            this_type = this_type.target()

        self.value_node_type = gdb.lookup_type(this_type.tag + '::node').pointer()

    def to_string(self):
        size, alloc = parseCompressedPair(self.val['m_pair'])
        return 'std::list of size %d' % size

    def display_hint(self):
        return 'array'

    def num_children(self):
        size, alloc = parseCompressedPair(self.val['m_pair'])
        return size

    def children(self):
        head = self.val['m_head']

        node = head['next']
        idx = 0
        while node != head.address:
            nodeval = node.reinterpret_cast(self.value_node_type)
            yield str(idx), nodeval['value']
            idx += 1
            node = node['next']

class listIteratorPrinter:
    def __init__(self, val):
        self.val = val

        this_type: gdb.Type = val.type
        this_type = this_type.unqualified().strip_typedefs()

        if this_type.tag == None:
            this_type = this_type.target()

        type_tag: str = this_type.tag
        type_tag = type_tag[:type_tag.rfind('::')]

        self.value_node_type = gdb.lookup_type(type_tag + '::node').pointer()

    def children(self):
        yield 'addr', self.val['p']
        if self.val['p'] == 0:
            return

        nodeptr = self.val['p'].cast(self.value_node_type)

        yield 'value', nodeptr['value']

class rbtreeIteratorPrinter:
    def __init__(self, val):
        self.val = val

    def children(self):
        yield 'addr', self.val['p']
        if self.val['p'] == 0:
            return

        yield 'value', self.val['p']['value']

class vectorIteratorPrinter:
    def __init__(self, val):
        self.val = val

    def children(self):
        yield 'value', self.val['m_ptr'].dereference()

class pairPrinter:
    def __init__(self, val):
        self.val = val

    def children(self):
        yield 'first', self.val['first']
        yield 'second', self.val['second']

class tuplePrinter:
    def __init__(self, val):
        self.val = val

    def children(self):
        i = 0
        try:
            cur = self.val
            while True:
                yield '<%d>' % i, cur['val']
                i += 1
                cur = cur['next']
        except Exception:
            if i == 0:
                yield 'tuple of size 0', ''

class functionPrinter:
    def __init__(self, val: gdb.Value):
        self.val = val

    def to_string(self):
        return self.val.type.name

class referenceWrapperPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "std::reference_wrapper to %x" % self.val['_ptr']

    def children(self):
        yield 'addr', self.val['_ptr'].cast(gdb.lookup_type('void').pointer())
        yield 'reference', self.val['_ptr']

class sharedPointerPrinter:
    def __init__(self, val: gdb.Value):
        self.val = val
        self.pointer = val['ptr']
        self.controlBlock = val['cb']

    def to_string(self):
        if self.pointer == 0:
            return 'nullptr of %s' % self.val.type.name

        refCount = self.controlBlock['ref_count']
        weakCount = self.controlBlock['weak_count']
        realPointer = self.controlBlock['ptr']
        return '%s to 0x%x, ref(%d), wref(%d), cb(0x%x), memp(0x%x)' % (
                self.val.type.name,
                self.pointer,
                refCount,
                weakCount,
                self.controlBlock,
                realPointer)

    def children(self):
        if self.pointer == 0:
            return []

        content = self.pointer.dereference()
        return delegateChildren(content)

class uniquePointerPrinter:
    def __init__(self, val: gdb.Value):
        self.val = val
        self.data = val['data']

    def to_string(self):
        pointer, deleter = parseCompressedPair(self.data)
        if pointer == 0:
            return 'nullptr of %s' % self.val.type.name

        return "%s to 0x%x" % (self.val.type.name, pointer)

    def children(self):
        pointer, deleter = parseCompressedPair(self.data)
        yield '[deleter]', deleter

        if pointer == 0:
            return

        for item in delegateChildren(pointer.dereference()):
            yield item

def build_pretty_printer(val: gdb.Value):
    type: gdb.Type = val.type.unqualified().strip_typedefs()
    typename = type.tag

    if typename == None:
        return None

    if re.compile(r"^std::pair<.*, .*>$").match(typename):
        return pairPrinter(val)

    if re.compile(r"^std::tuple<.*>$").match(typename):
        return tuplePrinter(val)

    if re.compile(r"^std::function<.*>$").match(typename):
        return functionPrinter(val)

    if re.compile(r"^std::reference_wrapper<.*>$").match(typename):
        return referenceWrapperPrinter(val)

    if re.compile(r"^std::list<.*, .*>::_iterator<.*?>$").match(typename):
        return listIteratorPrinter(val)

    if re.compile(r"^std::vector<.*, .*>::_iterator<.*?>$").match(typename):
        return vectorIteratorPrinter(val)

    if re.compile(r"^std::list<.*, .*>$").match(typename):
        return listPrinter(val)

    if re.compile(r"^std::vector<.*, .*>$").match(typename):
        return vectorPrinter(val)

    if re.compile(r"^std::map<.*, .*, .*, .*>$").match(typename):
        return rbtreePrinter("std::map", val)

    if re.compile(r"^std::set<.*, .*, .*>$").match(typename):
        return rbtreePrinter("std::set", val)

    if re.compile(r"^std::impl::rbtree<.*, .*, .*>::_iterator<.*?>$").match(typename):
        return rbtreeIteratorPrinter(val)

    if re.compile(r"^std::basic_string<.*>$").match(typename):
        return stringPrinter(val)

    if re.compile(r"^types::string_view$").match(typename):
        return stringViewPrinter(val)

    if re.compile(r"^std::shared_ptr<.*>$").match(typename):
        return sharedPointerPrinter(val)

    if re.compile(r"^std::unique_ptr<.*>$").match(typename):
        return uniquePointerPrinter(val)

    return None

gdb.pretty_printers.append(build_pretty_printer)
