from enum import Enum
from typing import List
import re


class RustType(Enum):
    Other = 0
    Struct = 1
    Tuple = 2
    CStyleVariant = 3
    TupleVariant = 4
    StructVariant = 5
    Enum = 6
    Empty = 7
    SingletonEnum = 8
    RegularEnum = 9
    CompressedEnum = 10
    Union = 11
    Indirection = 12

    StdString = 13
    StdOsString = 14
    StdStr = 15
    StdSlice = 16
    StdVec = 17
    StdVecDeque = 18
    StdBTreeSet = 19
    StdBTreeMap = 20
    StdHashMap = 21
    StdHashSet = 22
    StdRc = 23
    StdArc = 24
    StdCell = 25
    StdRef = 26
    StdRefMut = 27
    StdRefCell = 28
    StdNonZeroNumber = 29
    StdPath = 30
    StdPathBuf = 31
    StdBoxStr = 32


STD_STRING_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)String$")
STD_STR_REGEX = re.compile(r"^&(mut )?str$")
STD_SLICE_REGEX = re.compile(r"^&(mut )?\[.+\]$")
STD_OS_STRING_REGEX = re.compile(r"^(std::ffi::([a-z_]+::)+)OsString$")
STD_BOX_STR_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Box<str,.+>$")
STD_VEC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Vec<.+>$")
STD_VEC_DEQUE_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)VecDeque<.+>$")
STD_BTREE_SET_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)BTreeSet<.+>$")
STD_BTREE_MAP_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)BTreeMap<.+>$")
STD_HASH_MAP_REGEX = re.compile(r"^(std::collections::([a-z_]+::)+)HashMap<.+>$")
STD_HASH_SET_REGEX = re.compile(r"^(std::collections::([a-z_]+::)+)HashSet<.+>$")
STD_RC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Rc<.+>$")
STD_ARC_REGEX = re.compile(r"^(alloc::([a-z_]+::)+)Arc<.+>$")
STD_CELL_REGEX = re.compile(r"^(core::([a-z_]+::)+)Cell<.+>$")
STD_REF_REGEX = re.compile(r"^(core::([a-z_]+::)+)Ref<.+>$")
STD_REF_MUT_REGEX = re.compile(r"^(core::([a-z_]+::)+)RefMut<.+>$")
STD_REF_CELL_REGEX = re.compile(r"^(core::([a-z_]+::)+)RefCell<.+>$")
STD_NONZERO_NUMBER_REGEX = re.compile(r"^(core::([a-z_]+::)+)NonZero<.+>$")
STD_PATHBUF_REGEX = re.compile(r"^(std::([a-z_]+::)+)PathBuf$")
STD_PATH_REGEX = re.compile(r"^&(mut )?(std::([a-z_]+::)+)Path$")

ENCODED_ENUM_PREFIX = "RUST$ENCODED$ENUM$"
ENUM_DISR_FIELD_NAME = "<<variant>>"
ENUM_LLDB_ENCODED_VARIANTS = "$variants$"

STD_TYPE_TO_REGEX = {
    RustType.StdString: STD_STRING_REGEX,
    RustType.StdOsString: STD_OS_STRING_REGEX,
    RustType.StdStr: STD_STR_REGEX,
    RustType.StdBoxStr: STD_BOX_STR_REGEX,
    RustType.StdSlice: STD_SLICE_REGEX,
    RustType.StdVec: STD_VEC_REGEX,
    RustType.StdVecDeque: STD_VEC_DEQUE_REGEX,
    RustType.StdHashMap: STD_HASH_MAP_REGEX,
    RustType.StdHashSet: STD_HASH_SET_REGEX,
    RustType.StdBTreeSet: STD_BTREE_SET_REGEX,
    RustType.StdBTreeMap: STD_BTREE_MAP_REGEX,
    RustType.StdRc: STD_RC_REGEX,
    RustType.StdArc: STD_ARC_REGEX,
    RustType.StdRef: STD_REF_REGEX,
    RustType.StdRefMut: STD_REF_MUT_REGEX,
    RustType.StdRefCell: STD_REF_CELL_REGEX,
    RustType.StdCell: STD_CELL_REGEX,
    RustType.StdNonZeroNumber: STD_NONZERO_NUMBER_REGEX,
    RustType.StdPath: STD_PATH_REGEX,
    RustType.StdPathBuf: STD_PATHBUF_REGEX,
}


def is_tuple_fields(fields: List) -> bool:
    for f in fields:
        name = str(f.name)
        if not name.startswith("__") or not name[2:].isdigit():
            return False

    return True


def classify_struct(name: str, fields: List) -> RustType:
    if len(fields) == 0:
        return RustType.Empty

    for ty, regex in STD_TYPE_TO_REGEX.items():
        if regex.match(name):
            return ty

    # <<variant>> is emitted by GDB while LLDB(18.1+) emits "$variants$"
    if (
        fields[0].name == ENUM_DISR_FIELD_NAME
        or fields[0].name == ENUM_LLDB_ENCODED_VARIANTS
    ):
        return RustType.Enum

    if is_tuple_fields(fields):
        return RustType.Tuple

    return RustType.Struct


def classify_union(fields: List) -> RustType:
    if len(fields) == 0:
        return RustType.Empty

    first_variant_name = fields[0].name
    if first_variant_name is None:
        if len(fields) == 1:
            return RustType.SingletonEnum
        else:
            return RustType.RegularEnum
    elif first_variant_name.startswith(ENCODED_ENUM_PREFIX):
        assert len(fields) == 1
        return RustType.CompressedEnum
    else:
        return RustType.Union
