import gdb
import re

from gdb_providers import *
from rust_types import *


rust_enabled = 'set language rust' in gdb.execute('complete set language ru', to_string=True)
_gdb_version_matched = re.search('([0-9]+)\\.([0-9]+)', gdb.VERSION)
gdb_version = [int(num) for num in _gdb_version_matched.groups()] if _gdb_version_matched else []

def register_printers(objfile):
    objfile.pretty_printers.append(lookup)


# BACKCOMPAT: rust 1.35
def is_hashbrown_hashmap(hash_map):
    return len(hash_map.type.fields()) == 1


def classify_rust_type(type):
    type_class = type.code
    if type_class == gdb.TYPE_CODE_STRUCT:
        return classify_struct(type.tag, type.fields())
    if type_class == gdb.TYPE_CODE_UNION:
        return classify_union(type.fields())

    return RustType.OTHER


def check_enum_discriminant(valobj):
    content = valobj[valobj.type.fields()[0]]
    fields = content.type.fields()
    if len(fields) > 1:
        discriminant = int(content[fields[0]]) + 1
        if discriminant > len(fields):
            # invalid discriminant
            return False
    return True


def lookup(valobj):
    rust_type = classify_rust_type(valobj.type)

    if rust_type == RustType.ENUM:
        # use enum provider only for GDB <7.12
        if gdb_version[0] < 7 or (gdb_version[0] == 7 and gdb_version[1] < 12):
            if check_enum_discriminant(valobj):
                return EnumProvider(valobj)

    if rust_type == RustType.STD_STRING:
        return StdStringProvider(valobj)
    if rust_type == RustType.STD_OS_STRING:
        return StdOsStringProvider(valobj)
    if rust_type == RustType.STD_STR and not rust_enabled:
        return StdStrProvider(valobj)

    if rust_type == RustType.STD_VEC:
        return StdVecProvider(valobj)
    if rust_type == RustType.STD_VEC_DEQUE:
        return StdVecDequeProvider(valobj)
    if rust_type == RustType.STD_BTREE_SET:
        return StdBTreeSetProvider(valobj)
    if rust_type == RustType.STD_BTREE_MAP:
        return StdBTreeMapProvider(valobj)
    if rust_type == RustType.STD_HASH_MAP:
        if is_hashbrown_hashmap(valobj):
            return StdHashMapProvider(valobj)
        else:
            return StdOldHashMapProvider(valobj)
    if rust_type == RustType.STD_HASH_SET:
        hash_map = valobj[valobj.type.fields()[0]]
        if is_hashbrown_hashmap(hash_map):
            return StdHashMapProvider(valobj, show_values=False)
        else:
            return StdOldHashMapProvider(hash_map, show_values=False)

    if rust_type == RustType.STD_RC:
        return StdRcProvider(valobj)
    if rust_type == RustType.STD_ARC:
        return StdRcProvider(valobj, is_atomic=True)

    if rust_type == RustType.STD_CELL:
        return StdCellProvider(valobj)
    if rust_type == RustType.STD_REF:
        return StdRefProvider(valobj)
    if rust_type == RustType.STD_REF_MUT:
        return StdRefProvider(valobj)
    if rust_type == RustType.STD_REF_CELL:
        return StdRefCellProvider(valobj)

    return None
