from ast import Dict
import base64
from ctypes import Union
from datetime import datetime
from email.headerregistry import Address
from Types.Timestamp import Timestamp
from VMType import VMType
import big_int  # Assuming big_int is a Python equivalent of big-integer library


class VMObject:
    TimeFormat = "%m/%d/%Y %H:%M:%S"

    def __init__(self):
        self.Type = VMType.NONE
        self.Data = None
        self._local_size = 0

    @property
    def IsEmpty(self):
        return self.Data is None

    def GetChildren(self) -> Union[Dict['VMObject', 'VMObject'], None]:
        return self.Data if self.Type == VMType.Struct else None

    @property
    def Size(self):
        if self.Type == VMType.Object:
            children = self.GetChildren()
            total = sum(entry.Size for entry in children.values())
        else:
            total = self._localSize

        return total

    def AsType(self, type):
        if type == VMType.Bool:
            return self.AsBool()
        elif type == VMType.String:
            return self.AsString()
        elif type == VMType.Bytes:
            return self.AsByteArray()
        elif type == VMType.Number:
            return self.AsNumber()
        elif type == VMType.Timestamp:
            return self.AsTimestamp()
        else:
            raise ValueError("Unsupported VM cast")

    def AsEnum(self, enum_type):
        if not issubclass(enum_type, Enum):
            raise ValueError("enum_type must be an enumerated type")

        if self.Type != VMType.Enum:
            num = self.AsNumber()
            self.Data = int(num)

        return enum_type(self.Data)

    def AsNumber(self):
        if (self.Type in [VMType.Object, VMType.Timestamp]) and isinstance(self.Data, Timestamp):
            return self.Data.Value

        if self.Type == VMType.NoneType:
            return 0

        if self.Type == VMType.String:
            if str(self.Data).isnumeric():
                return int(self.Data)
            else:
                raise Exception(f"Cannot convert String '{self.Data}' to BigInteger.")

        if self.Type == VMType.Bytes:
            bytes_data = bytes(self.Data)
            return int.from_bytes(bytes_data, byteorder='big', signed=False)

        if self.Type == VMType.Enum:
            return int(self.Data)

        if self.Type == VMType.Bool:
            return int(self.Data)

        if self.Type == VMType.Object and isinstance(self.Data, Hash):
            hash_obj = self.Data
            return hash_obj.AsNumber()

        if self.Type != VMType.Number:
            raise Exception(f"Invalid cast: expected number, got {self.Type}")

        return self.Data

    def AsTimestamp(self):
        if self.Type != VMType.Timestamp:
            raise Exception(f"Invalid cast: expected timestamp, got {self.Type}")

        return self.Data
    
    def AsString(self):
        if self.Type == VMType.String:
            return str(self.Data)

        if self.Type == VMType.Number:
            return str(int(self.Data))

        if self.Type == VMType.Bytes:
            return self.Data.decode('utf-8')

        if self.Type == VMType.Enum:
            return str(self.Data)

        if self.Type == VMType.Object:
            if isinstance(self.Data, Address):
                return self.Data.Text
            elif isinstance(self.Data, Hash):
                return str(self.Data)
            else:
                return "Interop:" + type(self.Data).__name__

        if self.Type == VMType.Struct:
            array_type = self.GetArrayType()
            if array_type == VMType.Number:
                children = self.GetChildren()
                result = ""
                for i in range(len(children)):
                    key = VMObject.FromObject(i)
                    val = children[key]
                    ch = chr(int(val.AsNumber()))
                    result += ch
                return result
            else:
                with np.nditer(self.Data, flags=['multi_index'], op_flags=['readonly']) as it:
                    result = bytearray()
                    while not it.finished:
                        index = it.multi_index
                        element = self.Data[index]
                        if isinstance(element, bytes):
                            result.extend(element)
                        else:
                            result.append(element)
                        it.iternext()
                    return base64.b64encode(result).decode('utf-8')

        if self.Type == VMType.Bool:
            return "true" if self.Data else "false"

        if self.Type == VMType.Timestamp:
            date = self.Data
            return date.Value.strftime("%m/%d/%Y %H:%M:%S")

        raise Exception(f"Invalid cast: expected string, got {self.Type}")

    def AsArray(self, type):
        if self.Type != VMType.Struct:
            raise Exception(f"Invalid cast: expected struct, got {self.Type}")

        if self.Data is None:
            return []

        values = self.Data

        if not isinstance(values, dict):
            raise Exception("Invalid struct data")

        result = []

        for i in range(len(values)):
            key = VMObject.FromObject(i)

            if key in values:
                val = values[key]
                result.append(self.CastTo(val, type))

        return result
    
    def GetArrayType(self):
        if self.Type != VMType.Struct:
            return VMType.NoneType

        children = self.GetChildren()

        result = VMType.NoneType

        for i in range(len(children)):
            key = VMObject.FromObject(i)

            if key not in children:
                return VMType.NoneType

            val = children[key]

            if result == VMType.NoneType:
                result = val.Type
            elif val.Type != result:
                return VMType.NoneType

        return result

    def AsByteArray(self):
        if self.Type == VMType.Bytes:
            return bytes(self.Data)

        if self.Type == VMType.Bool:
            return bytes([int(self.Data)])

        if self.Type == VMType.String:
            str_data = self.AsString()
            return str_data.encode('utf-8')

        if self.Type == VMType.Number:
            num_data = self.AsNumber()
            return num_data.to_bytes((num_data.bit_length() + 7) // 8, byteorder='big', signed=True)

        if self.Type == VMType.Enum:
            num_data = int(self.AsNumber())
            return num_data.to_bytes(4, byteorder='big', signed=False)

        if self.Type == VMType.Timestamp:
            time_data = self.AsTimestamp()
            return time_data.Value.to_bytes(8, byteorder='big', signed=False)

        if self.Type == VMType.Struct:
            return self.Serialize()

        if self.Type == VMType.Object:
            serializable = self.Data
            if isinstance(serializable, ISerializable):
                serial_data = serializable.Serialize()[1:]
                return bytearray(serial_data)

            raise Exception("Complex object type can't be converted to bytes")

        raise Exception(f"Invalid cast: expected bytes, got {self.Type}")
    
    def AsAddress(self):
        if self.Type == VMType.String:
            temp = str(self.Data)
            if Address.IsValidAddress(temp):
                return Address.FromText(temp)

        if self.Type == VMType.Bytes:
            temp = bytes(self.Data)

            if len(temp) == Address.LengthInBytes + 1:
                temp = temp[1:]  # Remove the first byte

            if len(temp) == Address.LengthInBytes:
                return Address.FromBytes(temp)

            raise Exception(f"Invalid address size, expected {Address.LengthInBytes} got {len(temp)}")

        if self.Type == VMType.Object and isinstance(self.Data, Address):
            return self.Data

        raise Exception(f"Invalid cast: expected address, got {self.Type}")

    def AsBool(self):
        if self.Type == VMType.Bytes:
            bytes_data = bytes(self.Data)
            if len(bytes_data) == 1:
                return bytes_data[0] != 0

        if self.Type == VMType.Bool:
            return bool(self.Data)

        if self.Type == VMType.Number:
            val = self.AsNumber()
            return val != 0

        raise Exception(f"Invalid cast: expected bool, got {self.Type}")

    def AsStruct(self, struct_type):
        if self.Type == VMType.Object:
            if self.Data is not None and isinstance(self.Data, struct_type):
                return self.Data
            else:
                raise Exception(f"Invalid cast: expected VMObject of type {struct_type.__name__}")

        if self.Type != VMType.Struct:
            raise Exception(f"Invalid cast: expected struct, got {self.Type}")

        if self.Data is None:
            return None

        values = self.Data

        if not isinstance(values, dict):
            raise Exception("Invalid struct data")

        result = struct_type()

        # WARNING: This code is experimental and might not work in every situation.
        for entry in values.items():
            key = entry[0]
            field_name = key.AsString()

            if not field_name:
                raise Exception("Key with null or non-string name found in struct")

            if not hasattr(struct_type, field_name):
                raise Exception("Unknown field: " + field_name)

            field = getattr(struct_type, field_name)
            field_type = field.__annotations__['value']

            field_value = entry[1].ToStruct(field_type) if entry[1].Type == VMType.Struct else entry[1].ToObject()
            field_value = field_value if field_value is not None else ''

            try:
                setattr(result, field_name, field_value)
            except Exception as e:
                raise Exception(f"Could not set field value: {field_name}, {e}")

        return result
    

    @staticmethod
    def ConvertObjectInternal(field_value, field_type):
        if field_type == VMObject:
            # Edge case: happens when calling ToArray<VMObject>()
            return VMObject.FromObject(field_value)
        elif inspect.isclass(field_type) or inspect.isclass(field_value):
            # Check if it's a struct or class and the value is a byte array
            if isinstance(field_value, bytes):
                field_value = Serialization.Unserialize(field_value, field_type)
        elif inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
            # If it's an enum, parse it based on its type
            field_value = field_type(field_value)

        return field_value

    def AsInterop(self, interop_type):
        if interop_type == Hash and self.Type != VMType.Object:
            bytes_data = self.AsByteArray()

            if len(bytes_data) == 33 and bytes_data[0] == 32:
                bytes_data = bytes_data[1:]

            if len(bytes_data) == 32:
                return Hash(bytes_data)

        if self.Type != VMType.Object:
            raise Exception(f"Invalid cast: expected object, got {self.Type}")

        if self.Data is None:
            return None

        if not isinstance(self.Data, interop_type):
            raise Exception("Invalid interop type")

        return self.Data
    
    def SetValue(self, val, vm_type):
        self.Type = vm_type
        self._localSize = len(val) if val is not None else 0

        if vm_type == VMType.Bytes:
            self.Data = val
        elif vm_type == VMType.Number:
            self.Data = BigInteger(0) if val is None or len(val) == 0 else BigInteger(val)
        elif vm_type == VMType.String:
            self.Data = val.decode('utf-8') if val is not None else ""
        elif vm_type == VMType.Enum:
            self.Data = int.from_bytes(val, byteorder='big', signed=False)
        elif vm_type == VMType.Timestamp:
            temp = 0 if val is None else int.from_bytes(val, byteorder='big', signed=False)
            self.Data = Timestamp(temp)
        elif vm_type == VMType.Bool:
            self.Data = bool.from_bytes(val, byteorder='big', signed=False)
        else:
            if isinstance(val, bytes):
                bytes_val = val
                length = len(bytes_val) if bytes_val is not None else 0

                if length == Address.LengthInBytes:
                    self.Data = Address.FromBytes(bytes_val)
                elif length == Hash.Length:
                    self.Data = Hash.FromBytes(bytes_val)
                else:
                    try:
                        self.UnserializeData(bytes_val)
                    except Exception as e:
                        raise Exception("Cannot decode interop object from bytes with length: " + str(length))
            else:
                raise Exception("Cannot set value for VMType: " + vm_type)

        return self
    
    def SetValue(self, val):
        if isinstance(val, BigInteger):
            self.Type = VMType.Number
            self.Data = val
            self._localSize = len(val.to_bytes((val.bit_length() + 7) // 8, byteorder='big', signed=True))
        elif isinstance(val, dict):
            for key in val.keys():
                self.ValidateStructKey(key)
            self.Type = VMType.Struct
            self.Data = val
            self._localSize = 4  # TODO: Update this value
        elif isinstance(val, Hash):
            self.Type = VMType.Object
            self.Data = val
            self._localSize = 4
        elif isinstance(val, Address):
            self.Type = VMType.Object
            self.Data = val
            self._localSize = 4
        elif isinstance(val, Timestamp):
            self.Type = VMType.Timestamp
            self.Data = val
            self._localSize = 4
        elif isinstance(val, str):
            self.Type = VMType.String
            self.Data = val
            self._localSize = len(val)
        elif isinstance(val, bool):
            self.Type = VMType.Bool
            self.Data = val
            self._localSize = 1
        elif isinstance(val, bytes):
            self.Type = VMType.Bytes
            self.Data = val
            self._localSize = len(val)
        elif isinstance(val, enum.Enum):
            self.Type = VMType.Enum
            self.Data = val
            self._localSize = 4
        else:
            val_type = type(val)
            if val is not None and val_type == Timestamp:
                return self.SetValue(val)
            if not inspect.isclass(val_type) or not val_type.__annotations__ or 'value' not in val_type.__annotations__:
                raise Exception(f"Invalid cast: expected struct or class, got {val_type.__name__}")
            self.Type = VMType.Object
            self.Data = val
            self._localSize = 4
        return self

    @staticmethod
    def ValidateStructKey(key):
        if key.Type in [VMType.NoneType, VMType.Struct, VMType.Object]:
            raise Exception(f"Cannot use value of type {key.Type} as key for struct field")

    def SetDefaultValue(self, vm_type):
        self.Type = vm_type
        self._localSize = 1  # TODO: Fixme

        if vm_type == VMType.Bytes:
            self.Data = bytearray()
        elif vm_type == VMType.Number:
            self.Data = BigInteger(0)
        elif vm_type == VMType.String:
            self.Data = ""
        elif vm_type == VMType.Enum:
            self.Data = 0
        elif vm_type == VMType.Timestamp:
            self.Data = Timestamp(0)
        elif vm_type == VMType.Bool:
            self.Data = False
        elif vm_type == VMType.Object:
            self.Data = None
        else:
            raise Exception("Cannot initialize default value for VMType: " + vm_type)

        return self
    
    def SetKey(self, key, obj):
        self.ValidateStructKey(key)
        children = {}

        # NOTE: In Python, we don't need to create a new VMObject instance for the key
        if self.Type == VMType.Struct:
            children = self.GetChildren()
        elif self.Type == VMType.NoneType:
            self.Type = VMType.Struct
            children = {}
            self.Data = children
            self._localSize = 0
        else:
            raise Exception(f"Invalid cast from {self.Type} to struct")

        children[key] = obj

    def GetField(self, key):
        key_obj = VMObject.FromObject(key)
        return self.GetField(key_obj)

    def GetField(self, key):
        if self.Type != VMType.Struct:
            raise Exception(f"Invalid cast: expected struct, got {self.Type}")

        children = self.GetChildren()

        return children.get(key, VMObject())
    
    def GetHashCode(self):
        if self.Type == VMType.Struct:
            hash_value = 2166136261
            children = self.GetChildren()
            for child in children:
                hash_value = (hash_value * 16777619 + child.GetHashCode()) & 0xFFFFFFFF  # Ensure the hash value is within 32-bit bounds
            return hash_value
        else:
            return hash(self.Data) if self.Data is not None else 0

    def Equals(self, other):
        if other is None:
            return False

        if not isinstance(other, VMObject):
            return False

        if self.Type != other.Type:
            return False

        if self.Type == VMType.Struct:
            children = self.GetChildren()
            other_children = other.GetChildren()

            for key, value in children.items():
                if key not in other_children:
                    return False

                if value != other_children[key]:
                    return False

            for key, value in other_children.items():
                if key not in children:
                    return False

            return True
        else:
            return self.Data == other.Data

    def Copy(self, other):
        if other is None or other.Type == VMType.NoneType:
            self.Type = VMType.NoneType
            self.Data = None
            return

        self.Type = other.Type

        if other.Type == VMType.Struct:
            children = {}
            other_children = other.GetChildren()
            for key in other_children.keys():
                temp = VMObject()
                temp.Copy(other_children[key])
                children[key] = temp

            self.Data = children
        else:
            self.Data = other.Data

    def ToString(self):
        if self.Type == VMType.NoneType:
            return "Null"
        elif self.Type == VMType.Struct:
            return "[Struct]"
        elif self.Type == VMType.Bytes:
            return f"[Bytes] => {Base16.Encode(self.Data)}"
        elif self.Type == VMType.Number:
            return f"[Number] => {self.Data}"
        elif self.Type == VMType.Timestamp:
            return f"[Time] => {self.Data.strftime(TimeFormat)}"
        elif self.Type == VMType.String:
            return f"[String] => {self.Data}"
        elif self.Type == VMType.Bool:
            return f"[Bool] => {self.Data}"
        elif self.Type == VMType.Enum:
            return f"[Enum] => {self.Data}"
        elif self.Type == VMType.Object:
            return f"[Object] => {self.Data.__class__.__name__ if self.Data is not None else 'null'}"
        else:
            return "Unknown"

    @staticmethod
    def CastTo(src_obj, type):
        if src_obj.Type == type:
            result = VMObject()
            result.Copy(src_obj)
            return result

        if type == VMType.NoneType:
            return VMObject()

        result = VMObject()

        if type == VMType.String:
            result.SetValue(src_obj.AsString())
        elif type == VMType.Timestamp:
            result.SetValue(src_obj.AsTimestamp())
        elif type == VMType.Bool:
            result.SetValue(src_obj.AsBool())
        elif type == VMType.Bytes:
            result.SetValue(src_obj.AsByteArray())
        elif type == VMType.Number:
            result.SetValue(src_obj.AsNumber())
        elif type == VMType.Struct:
            if src_obj.Type == VMType.String:
                str_value = src_obj.AsString()
                chars = [VMObject.FromObject(BigInteger(ord(c))) for c in str_value]
                return VMObject.FromArray(chars)
            elif src_obj.Type == VMType.Object:
                # You'll need to implement the equivalent of CastViaReflection here.
                # This code depends on your specific class and object structure.
                # Please provide additional information about CastViaReflection to complete this part.
                pass
            else:
                raise Exception(f"Invalid cast: {src_obj.Type} to {type}")
        else:
            raise Exception(f"Invalid cast: {src_obj.Type} to {type}")

        return result
    
    @staticmethod
    def GetVMType(type):
        if type == VMType.Enum:
            return VMType.Enum
        elif type == bool:
            return VMType.Bool
        elif type == str:
            return VMType.String
        elif type == bytes:
            return VMType.Bytes
        elif type == int or type == BigInteger:
            return VMType.Number
        elif type == Timestamp or type == uint:
            return VMType.Timestamp
        elif type.__class__.__name__ == 'Enum':
            return VMType.Enum
        elif type.__class__.__name__ == 'list':
            return VMType.Struct
        elif hasattr(type, '__class__') and (type.__class__.__name__ == 'classobj' or type.__class__.__name__ == 'type'):
            return VMType.Object
        else:
            return VMType.NoneType

    @staticmethod
    def IsVMType(type):
        result = VMObject.GetVMType(type)
        return result != VMType.NoneType

    @staticmethod
    def FromArray(array):
        result = VMObject()
        for i in range(len(array)):
            key = VMObject.FromObject(i)

            temp = array[i]
            val = VMObject.FromObject(temp)

            result.SetKey(key, val)

        return result
    
    @staticmethod
    def FromObject(obj):
        if obj is None:
            # Returns the VMObject equivalent of None
            return VMObject()

        obj_type = type(obj)
        type_enum = VMObject.GetVMType(obj_type)

        if type_enum == VMType.NONE:
            raise Exception("Not a valid object")

        result = VMObject()

        if type_enum == VMType.Bool:
            result.SetValue(bool(obj))
        elif type_enum == VMType.Bytes:
            result.SetValue(bytes(obj), VMType.Bytes)
        elif type_enum == VMType.String:
            result.SetValue(str(obj))
        elif type_enum == VMType.Enum:
            result.SetValue(obj)
        elif type_enum == VMType.Object:
            result.SetValue(obj)
        elif type_enum == VMType.Number:
            if obj_type == int:
                obj = VMObject(int(obj))  # HACK
            result.SetValue(obj)
        elif type_enum == VMType.Timestamp:
            if obj_type == uint:
                obj = Timestamp(uint(obj))  # HACK
            result.SetValue(obj)
        elif type_enum == VMType.Struct:
            if isinstance(obj, list):
                return VMObject.FromArray(obj)
        else:
            return None

        return result

    def ToObject(self):
        if self.Type == VMType.NONE:
            raise Exception("Not a valid object")

        if self.Type == VMType.Bool:
            return self.AsBool()
        elif self.Type == VMType.Bytes:
            return self.AsByteArray()
        elif self.Type == VMType.String:
            return self.AsString()
        elif self.Type == VMType.Number:
            return self.AsNumber()
        elif self.Type == VMType.Timestamp:
            return self.AsTimestamp()
        elif self.Type == VMType.Object:
            return self.Data
        elif self.Type == VMType.Enum:
            return self.Data
        elif self.Type == VMType.Struct:
            if not self.IsEmpty:
                return self.Data
            else:
                raise Exception(f"Cannot cast {self.Type} to object")
        else:
            raise Exception(f"Cannot cast {self.Type} to object")

    def ToObjectWithType(self, type):
        if self.Type == VMType.Struct:
            if isinstance(self.Data, list):
                element_type = type.__args__[0]
                return self.ToArray(element_type)
            else:
                return self.ToStruct(type)
        else:
            temp = self.ToObject()
            return temp
        
    def ToArray(self, array_element_type):
        if self.IsEmpty:
            return []

        if self.Type != VMType.Struct:
            raise Exception("Not a valid source struct")

        children = self.GetChildren()
        max_index = -1
        for child in children:
            if child.Key.Type != VMType.Number:
                raise Exception("Source contains an element with an invalid array index")

            temp = child.Key.AsNumber()

            if temp >= 1024:  # TODO: Use a constant for VM max array size
                raise Exception("Source contains an element with a very large array index")

            index = int(temp)
            if index < 0:
                raise Exception("Source contains an array index with a negative value")

            max_index = max(index, max_index)

        length = max_index + 1
        array = [None] * length

        for child in children:
            temp = child.Key.AsNumber()
            index = int(temp)

            val = child.Value.ToObjectWithType(array_element_type)

            val = ConvertObjectInternal(val, array_element_type)

            array[index] = val

        return array
    
    def ToStruct(self, struct_type):
        if self.Type != VMType.Struct:
            raise Exception("Not a valid source struct")

        if not inspect.isclass(struct_type) or not hasattr(struct_type, '__dict__'):
            raise Exception("Not a valid destination struct")

        struct_fields = struct_type.__dict__
        result = struct_type()

        for field_name, field_type in struct_fields.items():
            key = VMObject.FromObject(field_name)

            if key in self.Data:
                val = self.Data[key].ToObjectWithType(field_type)
            else:
                if inspect.isclass(field_type) or inspect.isclass(field_type.__class__):
                    val = None
                else:
                    raise Exception("Field not present in source struct: " + field_name)

            # Here we check if the types mismatch.
            # In case of getting a bytes object instead of an object, we try deserializing the bytes in a different approach.
            # This should not be necessary often.
            if val is not None and field_type != bytes and isinstance(val, bytes):
                if issubclass(field_type, ISerializable):
                    temp = field_type()
                    bytes = val
                    stream = io.BytesIO(bytes)
                    reader = BinaryReader(stream)
                    temp.UnserializeData(reader)
                    val = temp

            # HACK: Allows treating uints as enums, without this it is impossible to transform between C# objects and VM objects.
            if hasattr(field_type, '__enum__') and not isinstance(val, Enum):
                val = field_type(val)

            setattr(result, field_name, val)

        return result
    
    def CastViaReflection(src_obj, level=0, dont_convert_serializables=True):
        src_type = type(src_obj)

        if isinstance(src_obj, pyarray.Array):
            children = {}

            for i, val in enumerate(src_obj):
                key = VMObject()
                key.SetValue(i)
                vm_val = CastViaReflection(val, level + 1)
                children[key] = vm_val

            result = VMObject()
            result.SetValue(children)
            return result
        else:
            target_type = GetVMType(src_type)

            result = None
            is_known_type = src_type in [int, bool, str, bytes, BigInteger, Timestamp]

            if not is_known_type and dont_convert_serializables and issubclass(src_type, ISerializable):
                is_known_type = True

            if is_known_type is False and src_type.__class__.__name__ not in ["type", "EnumMeta"]:
                children = {}

                fields = [attr for attr in dir(src_obj) if not callable(getattr(src_obj, attr)) and not attr.startswith("__")]

                if len(fields) > 0:
                    for field_name in fields:
                        key = VMObject()
                        key.SetValue(field_name)
                        ValidateStructKey(key)

                        val = getattr(src_obj, field_name)
                        vm_val = CastViaReflection(val, level + 1, True)
                        children[key] = vm_val

                    result = VMObject()
                    result.SetValue(children)
                    return result

            if not is_known_type:
                result = VMObject.FromObject(src_obj)
                if result is not None:
                    return result

            raise Exception(f"Invalid cast: Interop.{src_type.__name__} to vm object")
    
    def SerializeData(writer):
        writer.write(bytes([self.Type]))
        
        if self.Type == VMType.NONE:
            return

        data_type = type(self.Data)

        if self.Type == VMType.Struct:
            children = self.GetChildren()
            writer.write_var_int(len(children))
            
            for entry in children:
                entry[0].SerializeData(writer)
                entry[1].SerializeData(writer)
        
        elif self.Type == VMType.Object:
            obj = self.Data
            if isinstance(obj, ISerializable):
                bytes_data = Serialization.Serialize(obj)
                writer.write_byte_array(bytes_data)
            elif isinstance(obj, list):
                writer.write_var_int(len(obj))
                for item in obj:
                    obj2 = CastViaReflection(item, 0, False)
                    obj2.SerializeData(writer)
            else:
                raise Exception(f"Objects of type {data_type.__name__} cannot be serialized")
        
        elif self.Type == VMType.Enum:
            temp2 = int(self.Data) if isinstance(self.Data, Enum) else self.Data
            writer.write_var_int(temp2)
        
        else:
            if self.Type == VMType.String:
                data = self.Data.encode('utf-8')
                writer.write_var_int(len(data))
                writer.write(data)
            elif self.Type == VMType.Number:
                temp1 = str(self.Data)
                temp2 = int(temp1) if isinstance(self.Data, int) else self.Data
                writer.write_var_int(temp2)
            else:
                Serialization.Serialize(writer, self.Data)

    @staticmethod
    def FromBytes(bytes):
        result = VMObject()
        result.UnserializeData(bytes)
        return result

    @staticmethod
    def FromStruct(obj):
        return CastViaReflection(obj, 0, False)
    
    def UnserializeData(bytes_data):
        stream = io.BytesIO(bytes_data)
        reader = BinaryReader(stream)
        self.Type = VMType(reader.read_byte())
        
        if self.Type == VMType.Bool:
            self.Data = Serialization.Unserialize[bool](reader)
            self._localSize = 1
        elif self.Type == VMType.Bytes:
            self.Data = Serialization.Unserialize[bytearray](reader)
            self._localSize = len(self.Data)
        elif self.Type == VMType.Number:
            self.Data = Serialization.Unserialize[bigint](reader)
            self._localSize = 32
        elif self.Type == VMType.Timestamp:
            self.Data = Serialization.Unserialize[Timestamp](reader)
            self._localSize = 8
        elif self.Type == VMType.String:
            self.Data = Serialization.Unserialize[str](reader)
            self._localSize = len(self.Data) if self.Data is not None else 0
        elif self.Type == VMType.Struct:
            child_count = reader.read_var_int()
            self._localSize = child_count if child_count is not None else 0
            children = {}
            
            while child_count > 0:
                key = VMObject()
                key.UnserializeData(reader)
                ValidateStructKey(key)
                
                val = VMObject()
                val.UnserializeData(reader)
                children[key] = val
                child_count -= 1
            
            self.Data = children
        elif self.Type == VMType.Object:
            bytes_data = reader.read_byte_array()
            self._localSize = len(bytes_data) if bytes_data is not None else 0
            
            if len(bytes_data) == 35:
                addr = Serialization.Unserialize[Address](bytes_data)
                self.Data = addr
                self.Type = VMType.Object
            else:
                self.Type = VMType.Bytes
                self.Data = bytes_data
        elif self.Type == VMType.Enum:
            self.Type = VMType.Enum
            self.Data = reader.read_var_int()
            self._localSize = 1
        elif self.Type == VMType.NONE:
            self.Type = VMType.NONE
            self.Data = None
        else:
            raise Exception(f"Invalid unserialize: type {self.Type}")
    
    def __str__(self):
        if self.Type == VMType.String:
            return str(self.Data)
        elif self.Type == VMType.Number:
            return str(self.Data)
        elif self.Type == VMType.Bytes:
            return base64.b64encode(self.Data).decode('utf-8')  # or any other suitable representation
        elif self.Type == VMType.Enum:
            return str(self.Data)
        elif self.Type == VMType.Bool:
            return "true" if self.Data else "false"
        elif self.Type == VMType.Timestamp:
            return str(self.Data)  # Assuming Data holds a datetime object
        elif self.Type == VMType.Struct or self.Type == VMType.Object:
            # Represent Struct or Object as a string (customize as needed)
            return f"Object of type {self.Type} with data: {self.Data}"
        else:
            raise ValueError(f"Invalid cast: expected string, got {self.Type}")


