from __future__ import annotations
import llvmlite.ir as ir


class MemoryOpsMixin:

    def _register_local_heap_ptr(self, val: ir.Value, ClassName: str | None = None, VarName: str | None = None) -> None:
        if isinstance(val.type, ir.PointerType):
            pointee: ir.Type = val.type.pointee
            if isinstance(pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
                self._local_heap_ptrs.append((val, 'struct', ClassName))
            elif isinstance(pointee, ir.IntType) and pointee.width == 8:
                self._local_heap_ptrs.append((val, 'i8', ClassName))
        if VarName:
            self._var_to_heap_ptr[VarName] = val

    def _unregister_local_heap_ptr(self, val: ir.Value) -> None:
        self._local_heap_ptrs = [(v, t, cn) for v, t, cn in self._local_heap_ptrs if v is not val]

    def _emit_local_heap_frees(self) -> None:
        if not self._local_heap_ptrs or not self.builder:
            return
        deleted_objects: set[ir.Value] = set()
        for ptr, ptr_type, ClassName in self._local_heap_ptrs:
            if ptr_type == 'i8':
                Loaded_ptr: ir.Value = self.builder.load(ptr, name="load_ptr")
                null_ptr: ir.Constant = ir.Constant(ir.PointerType(ir.IntType(8)), None)
                is_null: ir.Value = self.builder.icmp_unsigned('==', Loaded_ptr, null_ptr, name="is_null")
                not_null_block: ir.Block = self.builder.append_basic_block(name="not_null")
                next_block: ir.Block = self.builder.append_basic_block(name="next")
                self.builder.cbranch(is_null, next_block, not_null_block)
                self.builder.position_at_end(not_null_block)
            if ClassName and self._has_function(f'{ClassName}.__del__'):
                cast_ptr: ir.Value
                if isinstance(ptr.type, ir.PointerType) and isinstance(ptr.type.pointee, ir.IntType) and ptr.type.pointee.width == 8:
                    cast_ptr = self.builder.bitcast(ptr, ir.PointerType(self.structs[ClassName]), name=f"cast_{ClassName}")
                else:
                    cast_ptr = ptr
                if cast_ptr not in deleted_objects:
                    self.builder.call(self._get_function(f'{ClassName}.__del__'), [cast_ptr], name=f"call_{ClassName}.__del__")
                    deleted_objects.add(cast_ptr)
            raw: ir.Value
            if ptr_type == 'struct':
                raw = self.builder.bitcast(ptr, ir.PointerType(ir.IntType(8)), name="local_free_cast")
            elif ptr_type == 'i8':
                raw = ptr
            else:
                continue
            free_type: ir.FunctionType = ir.FunctionType(ir.VoidType(), [ir.PointerType(ir.IntType(8))])
            free_func: ir.Function = self._get_or_declare_func('free', free_type)
            self.builder.call(free_func, [raw])
            if ptr_type == 'i8':
                self.builder.branch(next_block)
                self.builder.position_at_end(next_block)
        self._local_heap_ptrs = []

    def _RegisterTempPtr(self, val: ir.Value) -> None:
        if isinstance(val.type, ir.PointerType):
            pointee: ir.Type = val.type.pointee
            if isinstance(pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
                for CN, ST in self.structs.items():
                    if pointee == ST:
                        self._temp_struct_ptrs.append(val)
                        return
            elif isinstance(pointee, ir.IntType) and pointee.width == 8:
                self._temp_struct_ptrs.append(val)

    def _UnregisterTempPtr(self, val: ir.Value) -> None:
        self._temp_struct_ptrs = [t for t in self._temp_struct_ptrs if t is not val]

    def _EmitTempFrees(self) -> None:
        if not self._temp_struct_ptrs or not self.builder:
            return
        for ptr in self._temp_struct_ptrs:
            raw: ir.Value
            if isinstance(ptr.type, ir.PointerType) and isinstance(ptr.type.pointee, (ir.LiteralStructType, ir.IdentifiedStructType)):
                raw = self.builder.bitcast(ptr, ir.PointerType(ir.IntType(8)), name="free_cast")
            elif isinstance(ptr.type, ir.PointerType) and isinstance(ptr.type.pointee, ir.IntType) and ptr.type.pointee.width == 8:
                raw = ptr
            else:
                continue
            free_type: ir.FunctionType = ir.FunctionType(ir.VoidType(), [ir.PointerType(ir.IntType(8))])
            free_func: ir.Function = self._get_or_declare_func('free', free_type)
            self.builder.call(free_func, [raw])
        self._temp_struct_ptrs = []

    def _ZeroConst(self, typ: ir.Type) -> ir.Constant:
        if isinstance(typ, ir.PointerType):
            return ir.Constant(typ, None)
        if isinstance(typ, (ir.LiteralStructType, ir.IdentifiedStructType)):
            return ir.Constant(typ, None)
        if isinstance(typ, ir.ArrayType):
            return ir.Constant(typ, None)
        return ir.Constant(typ, 0)

    def _alloca(self, typ: ir.Type, name: str = '', align: int | None = None, size: ir.Value | None = None) -> ir.AllocaInstr:
        if align is None:
            align = self._get_align(typ)
        instr: ir.AllocaInstr
        if size is not None:
            instr = self.builder.alloca(typ, size=size, name=name)
        else:
            instr = self.builder.alloca(typ, name=name)
        if align:
            instr.align = align
        return instr

    def _allocaEntry(self, typ: ir.Type, name: str = '', align: int | None = None) -> ir.AllocaInstr:
        if align is None:
            align = self._get_align(typ)
        saved_block: ir.Block = self.builder.block
        entry_block: ir.Block = self.func.entry_basic_block
        instr: ir.AllocaInstr
        if entry_block.instructions:
            first_instr: ir.Instruction = entry_block.instructions[0]
            self.builder.position_before(first_instr)
            instr = self.builder.alloca(typ, name=name)
            if align:
                instr.align = align
            self.builder.position_at_end(saved_block)
        else:
            self.builder.position_at_start(entry_block)
            instr = self.builder.alloca(typ, name=name)
            if align:
                instr.align = align
            self.builder.position_at_end(saved_block)
        return instr

    def _load(self, ptr: ir.Value, name: str = '', align: int | None = None) -> ir.LoadInstr:
        if align is None:
            if isinstance(ptr.type, ir.PointerType):
                align = self._get_align(ptr.type.pointee)
            else:
                align = 0
        if isinstance(ptr.type, ir.PointerType):
            pointee_type: ir.Type = ptr.type.pointee
            if isinstance(pointee_type, ir.IdentifiedStructType) and pointee_type.name in self.typedef_int_widths:
                width: int = self.typedef_int_widths[pointee_type.name]
                if width > 0:
                    actual_type: ir.IntType = ir.IntType(width)
                    bitcast_ptr: ir.Value = self.builder.bitcast(ptr, ir.PointerType(actual_type), name="typedef_bitcast_Load")
                    return self.builder.load(bitcast_ptr, name=name, align=align)
        return self.builder.load(ptr, name=name, align=align)

    def _Load_var(self, name: str) -> ir.Value | None:
        if name in self._direct_values:
            return self._direct_values[name]
        if name in self.variables and self.variables[name] is not None:
            VarPtr: ir.Value = self.variables[name]
            if isinstance(VarPtr, ir.GlobalVariable) and isinstance(VarPtr.type, ir.PointerType) and isinstance(VarPtr.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
                return VarPtr
            if isinstance(VarPtr.type, ir.PointerType) and isinstance(VarPtr.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
                return VarPtr
            return self._load(VarPtr, name=name)
        if name in self.global_vars and name in self.module.globals:
            GVar: ir.GlobalVariable = self.module.globals[name]
            self.variables[name] = GVar
            if isinstance(GVar, ir.Function):
                return GVar
            if isinstance(GVar.type, ir.PointerType) and isinstance(GVar.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
                return GVar
            return self._load(GVar, name=name)
        if name in self.module.globals:
            GVar = self.module.globals[name]
            self.variables[name] = GVar
            if isinstance(GVar, ir.Function):
                return GVar
            if isinstance(GVar.type, ir.PointerType) and isinstance(GVar.type.pointee, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
                return GVar
            return self._load(GVar, name=name)
        return None

    def _coerce_value(self, val: ir.Value, target_type: ir.Type) -> ir.Value | None:
        if val.type == target_type:
            return val
        if isinstance(target_type, ir.IdentifiedStructType) and target_type.name in self.typedef_int_widths:
            if isinstance(val.type, ir.IntType):
                return val
        if isinstance(target_type, ir.IntType) and isinstance(val.type, ir.IntType):
            if target_type.width < val.type.width:
                return self.builder.trunc(val, target_type, name="trunc_store")
            elif val.type.width == 1:
                # i1 是布尔值（来自 icmp/fcmp/__contains__ 归一化），无符号语义，
                # 必须用 zext 而非 sext，否则 i1 的 1 会被符号扩展为 -1
                return self.builder.zext(val, target_type, name="zext_bool_store")
            else:
                return self.builder.sext(val, target_type, name="sext_store")
        if isinstance(target_type, ir.PointerType) and isinstance(val.type, ir.PointerType):
            if isinstance(val, ir.Constant) and val.constant is None:
                return ir.Constant(target_type, None)
            return self.builder.bitcast(val, target_type, name="ptr_bitcast_store")
        if isinstance(target_type, ir.PointerType) and isinstance(val.type, ir.IntType):
            if val.type.width < 64:
                val = self.builder.zext(val, ir.IntType(64), name="zext_ptr_store")
            return self.builder.inttoptr(val, target_type, name="int_to_ptr_store")
        if isinstance(target_type, ir.IntType) and isinstance(val.type, ir.PointerType):
            if isinstance(val.type.pointee, ir.IntType) and val.type.pointee.width == target_type.width:
                return self.builder.load(val, name="load_ptr_as_int_store")
            if target_type.width <= 8 and isinstance(val.type.pointee, ir.IntType):
                Loaded: ir.Value = self.builder.load(val, name="load_ptr_byte_store")
                if Loaded.type != target_type:
                    if isinstance(Loaded.type, ir.IntType) and isinstance(target_type, ir.IntType):
                        if target_type.width < Loaded.type.width:
                            return self.builder.trunc(Loaded, target_type, name="trunc_ptr_byte_store")
                        return self.builder.zext(Loaded, target_type, name="zext_ptr_byte_store")
                return Loaded
            return self.builder.ptrtoint(val, target_type, name="ptr_to_int_store")
        if isinstance(target_type, ir.FloatType) and isinstance(val.type, ir.DoubleType):
            return self.builder.fptrunc(val, target_type, name="fptrunc_store")
        if isinstance(target_type, ir.DoubleType) and isinstance(val.type, ir.FloatType):
            return self.builder.fpext(val, target_type, name="fpext_store")
        if isinstance(target_type, ir.IntType) and isinstance(val.type, (ir.FloatType, ir.DoubleType)):
            return self.builder.fptosi(val, target_type, name="fptosi_store")
        if isinstance(target_type, (ir.FloatType, ir.DoubleType)) and isinstance(val.type, ir.IntType):
            return self.builder.sitofp(val, target_type, name="sitofp_store")
        if isinstance(target_type, ir.ArrayType) and isinstance(val.type, ir.ArrayType):
            if target_type.count == val.type.count and target_type.element == val.type.element:
                return val
            if (isinstance(target_type.element, ir.IntType) and target_type.element.width == 8
                and isinstance(val.type.element, ir.IntType) and val.type.element.width == 8):
                if target_type.count > val.type.count:
                    padded: list[ir.Constant] = list(val.constant) if hasattr(val, 'constant') else []
                    if padded and len(padded) == val.type.count:
                        padded.extend([ir.Constant(ir.IntType(8), 0)] * (target_type.count - val.type.count))
                        return ir.Constant(target_type, padded)
                elif target_type.count < val.type.count:
                    trimmed: list[ir.Constant] = list(val.constant) if hasattr(val, 'constant') else []
                    if trimmed and len(trimmed) == val.type.count:
                        return ir.Constant(target_type, trimmed[:target_type.count])
        if isinstance(val.type, ir.PointerType) and not isinstance(target_type, ir.PointerType):
            if val.type.pointee == target_type:
                return self.builder.load(val, name="Load_struct_for_store")
            if isinstance(val.type.pointee, ir.IdentifiedStructType) and isinstance(target_type, ir.IdentifiedStructType):
                if val.type.pointee.name == target_type.name:
                    Loaded: ir.Value = self.builder.load(val, name="Load_struct_for_store")
                    if Loaded.type == target_type:
                        return Loaded
        return None

    def _store(self, val: ir.Value, ptr: ir.Value, align: int | None = None) -> ir.StoreInstr:
        if align is None:
            if isinstance(ptr.type, ir.PointerType):
                align = self._get_align(ptr.type.pointee)
            else:
                align = 0
        if isinstance(ptr.type, ir.PointerType):
            target_type: ir.Type = ptr.type.pointee
            if isinstance(target_type, ir.IdentifiedStructType) and target_type.name in self.typedef_int_widths:
                if isinstance(val.type, ir.IntType):
                    width: int = self.typedef_int_widths[target_type.name]
                    if width > 0:
                        actual_type: ir.IntType = ir.IntType(width)
                        bitcast_ptr: ir.Value = self.builder.bitcast(ptr, ir.PointerType(actual_type), name="typedef_bitcast")
                        if val.type.width != actual_type.width:
                            if val.type.width > actual_type.width:
                                val = self.builder.trunc(val, actual_type, name="trunc_typedef")
                            else:
                                val = self.builder.zext(val, actual_type, name="zext_typedef")
                        return self.builder.store(val, bitcast_ptr, align=align)
            if val.type != target_type:
                if isinstance(val, ir.Constant) and val.constant is None:
                    if isinstance(target_type, ir.PointerType):
                        val = ir.Constant(target_type, None)
                    elif isinstance(target_type, ir.IdentifiedStructType):
                        val = ir.Constant(ir.PointerType(target_type), None)
                        ptr = self.builder.bitcast(ptr, ir.PointerType(ir.PointerType(target_type)), name="null_struct_ptr_cast")
                else:
                    coerced: ir.Value | None = self._coerce_value(val, target_type)
                    if coerced is not None:
                        val = coerced
                    else:
                        src_info: str = self._get_node_info()
                        raise TypeError(f"cannot store {val.type} to {ptr.type}: mismatching types{src_info}")
        return self.builder.store(val, ptr, align=align)

    def _get_var_ptr(self, name: str) -> ir.Value | None:
        if name in self._reg_values:
            val: ir.Value = self._reg_values[name]
            var: ir.AllocaInstr = self._alloca(val.type, name=name)
            self._store(val, var)
            self.variables[name] = var
            del self._reg_values[name]
            return var
        if name in self.variables and self.variables[name] is not None:
            return self.variables[name]
        return None