from dataclasses import dataclass
from enum import Enum

import lldb
from jb_os_type import HOST_OS

_ENCODING_LOCALE = "__locale__"
_ENCODING_UTF_8 = "utf-8"
_ENCODING_UTF_16 = "utf-16"
_ENCODING_UTF_32 = "utf-32"


@dataclass(frozen=True)
class PrimitiveTypeSpec:
    lldb_basic_type: int
    byte_size: int
    signed: bool
    encoding: str
    literal_prefix: str


class PrimitiveType(Enum):
    @dataclass(frozen=True)
    class _DefaultSpec:
        lldb_basic_type: int
        byte_size: int
        signed: bool
        encoding: str = _ENCODING_LOCALE
        literal_prefix: str = ""

    Char = _DefaultSpec(lldb.eBasicTypeChar, 1, True)
    UnsignedChar = _DefaultSpec(lldb.eBasicTypeUnsignedChar, 1, False)
    SignedChar = _DefaultSpec(lldb.eBasicTypeSignedChar, 1, True)
    UnsignedChar8 = _DefaultSpec(lldb.eBasicTypeUnsignedChar, 1, False, _ENCODING_UTF_8, "")
    Char8 = _DefaultSpec(lldb.eBasicTypeChar8, 1, False, _ENCODING_UTF_8, "u8")
    Char16 = _DefaultSpec(lldb.eBasicTypeChar16, 2, False, _ENCODING_UTF_16, "u")
    Char32 = _DefaultSpec(lldb.eBasicTypeChar32, 4, False, _ENCODING_UTF_32, "U")
    WChar = _DefaultSpec(lldb.eBasicTypeWChar, 2, False, _ENCODING_UTF_16, "L")
    Short = _DefaultSpec(lldb.eBasicTypeShort, 2, True)
    UnsignedShort = _DefaultSpec(lldb.eBasicTypeUnsignedShort, 2, False)
    Int = _DefaultSpec(lldb.eBasicTypeInt, 4, True)
    UnsignedInt = _DefaultSpec(lldb.eBasicTypeUnsignedInt, 4, False)
    Long = _DefaultSpec(lldb.eBasicTypeLong, 4, True)
    UnsignedLong = _DefaultSpec(lldb.eBasicTypeUnsignedLong, 4, False)
    LongLong = _DefaultSpec(lldb.eBasicTypeLongLong, 8, True)
    UnsignedLongLong = _DefaultSpec(lldb.eBasicTypeUnsignedLongLong, 8, False)
    Int128 = _DefaultSpec(lldb.eBasicTypeInt128, 16, True)
    UnsignedInt128 = _DefaultSpec(lldb.eBasicTypeUnsignedInt128, 16, False)

    def resolve_spec(self, related_value: lldb.SBValue | None = None, related_type: lldb.SBType | None = None) -> PrimitiveTypeSpec:
        spec: PrimitiveType._DefaultSpec = self.value
        match self:
            case PrimitiveType.WChar:
                wchar_size = self._get_wchar_size(spec.lldb_basic_type, related_value, related_type)
                encoding = _ENCODING_UTF_32 if wchar_size == 4 else _ENCODING_UTF_16
                return PrimitiveTypeSpec(spec.lldb_basic_type, wchar_size, spec.signed, encoding, spec.literal_prefix)
            case PrimitiveType.Long | PrimitiveType.UnsignedLong:
                long_size = self._get_long_size(spec.lldb_basic_type, related_value, related_type)
                return PrimitiveTypeSpec(spec.lldb_basic_type, long_size, spec.signed, spec.encoding, spec.literal_prefix)
            case _:
                return PrimitiveTypeSpec(spec.lldb_basic_type, spec.byte_size, spec.signed, spec.encoding, spec.literal_prefix)

    @staticmethod
    def _get_basic_type_size(basic_type: int, related_value: lldb.SBValue | None, related_type: lldb.SBType | None):
        bytes_size = 0
        if related_value:
            bytes_size = related_value.GetTarget().GetBasicType(basic_type).GetByteSize()
        if not bytes_size and related_type:
            bytes_size = related_type.GetBasicType(basic_type).GetByteSize()
        return bytes_size

    @classmethod
    def _get_wchar_size(cls, basic_type: int, related_value: lldb.SBValue | None, related_type: lldb.SBType | None) -> int:
        if wchar_size := cls._get_basic_type_size(basic_type, related_value, related_type):
            return wchar_size
        return 2 if HOST_OS.is_windows else 4

    @classmethod
    def _get_long_size(cls, basic_type: int, related_value: lldb.SBValue | None, related_type: lldb.SBType | None) -> int:
        if long_size := cls._get_basic_type_size(basic_type, related_value, related_type):
            return long_size
        return 8 if HOST_OS.is_linux else 4
