use std::collections::HashMap;

use cranelift_codegen::{
    Context,
    control::ControlPlane,
    ir::{self, AbiParam, InstBuilder, types},
    settings::{self, Configurable},
};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift_module::{Linkage, Module, default_libcall_names};
use cranelift_object::{ObjectBuilder, ObjectModule};

use crate::frontend::{
    ast::{BinaryOp, UnaryOp},
    sema::Ty,
};
use crate::middle::mir::*;

/// The backend responsible for lowering a `TypedModule` into Cranelift IR and
/// generating native machine code object files.
pub struct CraneliftBackend {
    ctx: Context,
    builder_context: FunctionBuilderContext,
    module: ObjectModule,
    func_ids: HashMap<String, cranelift_module::FuncId>,
}

impl CraneliftBackend {
    /// Initializes the Cranelift backend with the host's native instruction set architecture (ISA),
    /// enabling speed optimizations and position-independent code (PIC) generation.
    pub fn new() -> Self {
        let mut flag_builder = settings::builder();
        flag_builder.set("is_pic", "true").unwrap();
        flag_builder.set("opt_level", "speed").unwrap();

        let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
            panic!("host machine is not supported: {}", msg);
        });

        let isa = isa_builder
            .finish(settings::Flags::new(flag_builder))
            .unwrap();

        let builder = ObjectBuilder::new(isa, "module", default_libcall_names()).unwrap();

        Self {
            ctx: Context::new(),
            builder_context: FunctionBuilderContext::new(),
            module: ObjectModule::new(builder),
            func_ids: HashMap::new(),
        }
    }

    /// Compiles a MIR module into native object code.
    ///
    /// Returns a tuple containing the generated Cranelift IR (as a human-readable string) and the assembled object file bytes.
    pub fn compile_module(mut self, module: &MirModule) -> (String, Vec<u8>) {
        let mut ir_output = String::new();

        for ext_func in &module.extern_functions {
            let mut sig = self.module.make_signature();
            for param_ty in &ext_func.params {
                sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
            }
            if ext_func.return_type != Ty::Unit {
                sig.returns
                    .push(AbiParam::new(Self::lower_type(&ext_func.return_type)));
            }

            let func_id = self
                .module
                .declare_function(&ext_func.name, Linkage::Import, &sig)
                .unwrap();
            self.func_ids.insert(ext_func.name.clone(), func_id);
        }

        for func in &module.functions {
            let mut sig = self.module.make_signature();
            for param_id in &func.params {
                let param_ty = &func.locals[param_id.0].ty;
                sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
            }
            if func.return_type != Ty::Unit {
                sig.returns
                    .push(AbiParam::new(Self::lower_type(&func.return_type)));
            }
            let func_id = self
                .module
                .declare_function(&func.name, Linkage::Export, &sig)
                .unwrap();
            self.func_ids.insert(func.name.clone(), func_id);
        }

        for func in &module.functions {
            self.compile_function(func, &module.structs);

            // Run Cranelift's optimization passes before emitting the text IR
            let mut ctrl_plane = ControlPlane::default();
            self.ctx
                .optimize(self.module.isa(), &mut ctrl_plane)
                .unwrap();

            ir_output.push_str(&format!("; Function: {}\n{}", func.name, self.ctx.func));
            ir_output.push('\n');

            let id = self.func_ids[&func.name];
            self.module.define_function(id, &mut self.ctx).unwrap();
            self.module.clear_context(&mut self.ctx);
        }

        let obj_bytes = self.module.finish().emit().unwrap();
        (ir_output, obj_bytes)
    }

    /// Lowers a single MIR function into Cranelift IR.
    fn compile_function(
        &mut self,
        func: &MirFunction,
        structs: &HashMap<String, Vec<(String, Ty)>>,
    ) {
        let mut sig = self.module.make_signature();

        for param_id in &func.params {
            let param_ty = &func.locals[param_id.0].ty;
            sig.params.push(AbiParam::new(Self::lower_type(param_ty)));
        }

        if func.return_type != Ty::Unit {
            sig.returns
                .push(AbiParam::new(Self::lower_type(&func.return_type)));
        }

        self.ctx.func.signature = sig;
        self.ctx.func.name = ir::UserFuncName::user(0, 0);

        let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);

        let mut block_map = HashMap::new();
        for block in &func.blocks {
            block_map.insert(block.id, builder.create_block());
        }

        let mut var_map = HashMap::new();
        let mut stack_slot_map = HashMap::new();
        for local in &func.locals {
            if local.address_taken || matches!(local.ty, Ty::Struct(_) | Ty::Array(_, _)) {
                let bytes = Self::type_size(&local.ty, structs);
                let slot = builder.create_sized_stack_slot(ir::StackSlotData::new(
                    ir::StackSlotKind::ExplicitSlot,
                    bytes,
                    0,
                ));
                stack_slot_map.insert(local.id, slot);
            } else {
                let var = builder.declare_var(Self::lower_type(&local.ty));
                var_map.insert(local.id, var);
            }
        }

        let mut trans = FunctionTranslator {
            builder,
            var_map,
            stack_slot_map,
            block_map,
            locals: &func.locals,
            module: &mut self.module,
            func_ids: &self.func_ids,
            structs,
        };

        if let Some(first_block) = func.blocks.first() {
            let entry_block = trans.block_map[&first_block.id];
            trans
                .builder
                .append_block_params_for_function_params(entry_block);
        }

        for (i, block) in func.blocks.iter().enumerate() {
            let cl_block = trans.block_map[&block.id];
            trans.builder.switch_to_block(cl_block);

            // Retrieve function arguments explicitly if this is the entry block
            if i == 0 {
                for (j, param_id) in func.params.iter().enumerate() {
                    let val = trans.builder.block_params(cl_block)[j];

                    if matches!(func.locals[param_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
                        let slot = trans.stack_slot_map[param_id];
                        let dest_addr = trans.builder.ins().stack_addr(types::I64, slot, 0);
                        let size =
                            CraneliftBackend::type_size(&func.locals[param_id.0].ty, trans.structs);
                        trans.emit_memcpy(dest_addr, val, size);
                    } else if let Some(&slot) = trans.stack_slot_map.get(param_id) {
                        trans.builder.ins().stack_store(val, slot, 0);
                    } else {
                        trans.builder.def_var(trans.var_map[param_id], val);
                    }
                }
            }

            for stmt in &block.statements {
                trans.translate_stmt(stmt);
            }

            trans.translate_terminator(&block.terminator);
        }

        // Seal all blocks now that all branches are translated and predecessors are known
        for cl_block in trans.block_map.values() {
            trans.builder.seal_block(*cl_block);
        }

        trans.builder.finalize();
    }

    /// Maps our semantic types (`Ty`) to Cranelift's internal types (`ir::Type`).
    fn lower_type(ty: &Ty) -> ir::Type {
        match ty {
            Ty::I8 | Ty::U8 => types::I8,
            Ty::I16 | Ty::U16 => types::I16,
            Ty::I32 | Ty::U32 => types::I32,
            Ty::I64 | Ty::U64 => types::I64,
            Ty::F32 => types::F32,
            Ty::F64 => types::F64,
            Ty::Bool => types::I8, // Booleans are represented as 8-bit integers
            Ty::Pointer(_) | Ty::Struct(_) | Ty::Array(_, _) => types::I64, // Aggregates and pointers are passed by reference
            _ => unimplemented!("Unsupported type for Cranelift lowering: {:?}", ty),
        }
    }

    fn type_size(ty: &Ty, structs: &HashMap<String, Vec<(String, Ty)>>) -> u32 {
        match ty {
            Ty::I8 | Ty::U8 | Ty::Bool => 1,
            Ty::I16 | Ty::U16 => 2,
            Ty::I32 | Ty::U32 | Ty::F32 => 4,
            Ty::I64 | Ty::U64 | Ty::F64 | Ty::Pointer(_) => 8,
            Ty::Struct(name) => {
                let mut size = 0;
                if let Some(fields) = structs.get(name) {
                    for (_, f_ty) in fields {
                        let f_size = Self::type_size(f_ty, structs);
                        let align = f_size.min(8);
                        size = (size + align - 1) & !(align - 1);
                        size += f_size;
                    }
                    size = (size + 7) & !7;
                }
                size
            }
            Ty::Array(inner, len) => {
                let elem_size = Self::type_size(inner, structs);
                elem_size * (*len as u32)
            }
            Ty::Unit => 0,
            Ty::Var(_) | Ty::Function(_, _) => unimplemented!(),
        }
    }

    fn field_offset(
        struct_name: &str,
        field_name: &str,
        structs: &HashMap<String, Vec<(String, Ty)>>,
    ) -> u32 {
        let mut offset = 0;
        if let Some(fields) = structs.get(struct_name) {
            for (f_name, f_ty) in fields {
                let f_size = Self::type_size(f_ty, structs);
                let align = f_size.min(8);
                offset = (offset + align - 1) & !(align - 1);

                if f_name == field_name {
                    return offset;
                }

                offset += f_size;
            }
        }
        panic!("Field not found");
    }
}

/// A visitor that traverses MIR basic blocks and instructions, emitting Cranelift IR instructions
/// into the current function builder.
struct FunctionTranslator<'a> {
    builder: FunctionBuilder<'a>,
    var_map: HashMap<LocalId, Variable>,
    stack_slot_map: HashMap<LocalId, ir::StackSlot>,
    block_map: HashMap<BlockId, ir::Block>,
    locals: &'a [LocalDecl],
    module: &'a mut ObjectModule,
    func_ids: &'a HashMap<String, cranelift_module::FuncId>,
    structs: &'a HashMap<String, Vec<(String, Ty)>>,
}

impl<'a> FunctionTranslator<'a> {
    fn translate_stmt(&mut self, stmt: &Statement) {
        match &stmt.kind {
            StatementKind::Assign(local_id, rvalue) => {
                if let Some(&slot) = self.stack_slot_map.get(local_id) {
                    if matches!(self.locals[local_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
                        let dest_addr = self.builder.ins().stack_addr(types::I64, slot, 0);
                        if let Some(src_addr) = self.translate_rvalue(rvalue) {
                            let size = CraneliftBackend::type_size(
                                &self.locals[local_id.0].ty,
                                self.structs,
                            );
                            self.emit_memcpy(dest_addr, src_addr, size);
                        }
                    } else if let Some(v) = self.translate_rvalue(rvalue) {
                        self.builder.ins().stack_store(v, slot, 0);
                    }
                } else {
                    let var = self.var_map[local_id];
                    if let Some(v) = self.translate_rvalue(rvalue) {
                        self.builder.def_var(var, v);
                    }
                }
            }
            StatementKind::SideEffect(rvalue) => {
                self.translate_rvalue(rvalue);
            }
            StatementKind::Store { ptr, val } => {
                let ptr_val = self.translate_operand(ptr);
                let rval_ty = self.get_rvalue_type(val);
                if matches!(rval_ty, Ty::Struct(_) | Ty::Array(_, _)) {
                    if let Some(src_addr) = self.translate_rvalue(val) {
                        let size = CraneliftBackend::type_size(&rval_ty, self.structs);
                        self.emit_memcpy(ptr_val, src_addr, size);
                    }
                } else {
                    if let Some(v) = self.translate_rvalue(val) {
                        self.builder
                            .ins()
                            .store(ir::MemFlags::trusted(), v, ptr_val, 0);
                    }
                }
            }
        }
    }

    fn translate_terminator(&mut self, term: &Terminator) {
        match &term.kind {
            TerminatorKind::Goto { target } => {
                self.builder.ins().jump(self.block_map[target], &[]);
            }
            TerminatorKind::CondBranch {
                cond,
                target_true,
                target_false,
            } => {
                let cond_val = self.translate_operand(cond);
                self.builder.ins().brif(
                    cond_val,
                    self.block_map[target_true],
                    &[],
                    self.block_map[target_false],
                    &[],
                );
            }
            TerminatorKind::Return { value } => {
                if let Some(op) = value {
                    let val = self.translate_operand(op);
                    self.builder.ins().return_(&[val]);
                } else {
                    self.builder.ins().return_(&[]);
                }
            }
            TerminatorKind::Unreachable => {
                self.builder.ins().trap(ir::TrapCode::user(5).unwrap());
            }
        }
    }

    fn get_operand_type(&self, op: &Operand) -> Ty {
        match op {
            Operand::Copy(local_id) => self.locals[local_id.0].ty.clone(),
            Operand::Constant(ConstantValue::Integer(_, ty)) => ty.clone(),
            Operand::Constant(ConstantValue::Float(_, ty)) => ty.clone(),
            Operand::Constant(ConstantValue::Boolean(_)) => Ty::Bool,
        }
    }

    fn translate_operand(&mut self, op: &Operand) -> ir::Value {
        match op {
            Operand::Copy(local_id) => {
                if matches!(self.locals[local_id.0].ty, Ty::Struct(_) | Ty::Array(_, _)) {
                    let slot = self.stack_slot_map[local_id];
                    self.builder.ins().stack_addr(types::I64, slot, 0)
                } else if let Some(&slot) = self.stack_slot_map.get(local_id) {
                    let cl_ty = CraneliftBackend::lower_type(&self.locals[local_id.0].ty);
                    self.builder.ins().stack_load(cl_ty, slot, 0)
                } else {
                    let var = self.var_map[local_id];
                    self.builder.use_var(var)
                }
            }
            Operand::Constant(ConstantValue::Integer(val, ty)) => {
                let cl_ty = CraneliftBackend::lower_type(ty);
                self.builder.ins().iconst(cl_ty, *val as i64)
            }
            Operand::Constant(ConstantValue::Float(val, ty)) => match ty {
                Ty::F32 => self.builder.ins().f32const(*val as f32),
                Ty::F64 => self.builder.ins().f64const(*val),
                _ => unreachable!(),
            },
            Operand::Constant(ConstantValue::Boolean(val)) => self
                .builder
                .ins()
                .iconst(types::I8, if *val { 1 } else { 0 }),
        }
    }

    fn translate_rvalue(&mut self, rvalue: &Rvalue) -> Option<ir::Value> {
        match rvalue {
            Rvalue::Use(op) => Some(self.translate_operand(op)),
            Rvalue::UnaryOp(op, inner) => {
                let inner_val = self.translate_operand(inner);
                let ty = self.get_operand_type(inner);

                match op {
                    UnaryOp::Neg => {
                        if ty.is_float() {
                            Some(self.builder.ins().fneg(inner_val))
                        } else {
                            Some(self.builder.ins().ineg(inner_val))
                        }
                    }
                    UnaryOp::Not => {
                        let cl_ty = CraneliftBackend::lower_type(&ty);
                        let zero = self.builder.ins().iconst(cl_ty, 0);
                        Some(
                            self.builder
                                .ins()
                                .icmp(ir::condcodes::IntCC::Equal, inner_val, zero),
                        )
                    }
                    UnaryOp::Deref | UnaryOp::AddressOf => {
                        unreachable!("handled as distinct Rvalues in MIR")
                    }
                }
            }
            Rvalue::BinaryOp(op, lhs, rhs) => {
                let lhs_val = self.translate_operand(lhs);
                let rhs_val = self.translate_operand(rhs);

                let ty = self.get_operand_type(lhs);
                let is_signed = matches!(ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64);
                let is_float = ty.is_float();

                Some(match op {
                    BinaryOp::Add => {
                        if is_float {
                            self.builder.ins().fadd(lhs_val, rhs_val)
                        } else {
                            self.builder.ins().iadd(lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Sub => {
                        if is_float {
                            self.builder.ins().fsub(lhs_val, rhs_val)
                        } else {
                            self.builder.ins().isub(lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Mul => {
                        if is_float {
                            self.builder.ins().fmul(lhs_val, rhs_val)
                        } else {
                            self.builder.ins().imul(lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Div => {
                        if is_float {
                            self.builder.ins().fdiv(lhs_val, rhs_val)
                        } else if is_signed {
                            self.builder.ins().sdiv(lhs_val, rhs_val)
                        } else {
                            self.builder.ins().udiv(lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Rem => {
                        if is_float {
                            unimplemented!("float remainder is not natively supported in Cranelift")
                        } else if is_signed {
                            self.builder.ins().srem(lhs_val, rhs_val)
                        } else {
                            self.builder.ins().urem(lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Eq => {
                        if is_float {
                            self.builder
                                .ins()
                                .fcmp(ir::condcodes::FloatCC::Equal, lhs_val, rhs_val)
                        } else {
                            self.builder
                                .ins()
                                .icmp(ir::condcodes::IntCC::Equal, lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Neq => {
                        if is_float {
                            self.builder.ins().fcmp(
                                ir::condcodes::FloatCC::NotEqual,
                                lhs_val,
                                rhs_val,
                            )
                        } else {
                            self.builder.ins().icmp(
                                ir::condcodes::IntCC::NotEqual,
                                lhs_val,
                                rhs_val,
                            )
                        }
                    }
                    BinaryOp::Lt => {
                        if is_float {
                            self.builder.ins().fcmp(
                                ir::condcodes::FloatCC::LessThan,
                                lhs_val,
                                rhs_val,
                            )
                        } else {
                            let cc = if is_signed {
                                ir::condcodes::IntCC::SignedLessThan
                            } else {
                                ir::condcodes::IntCC::UnsignedLessThan
                            };
                            self.builder.ins().icmp(cc, lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Le => {
                        if is_float {
                            self.builder.ins().fcmp(
                                ir::condcodes::FloatCC::LessThanOrEqual,
                                lhs_val,
                                rhs_val,
                            )
                        } else {
                            let cc = if is_signed {
                                ir::condcodes::IntCC::SignedLessThanOrEqual
                            } else {
                                ir::condcodes::IntCC::UnsignedLessThanOrEqual
                            };
                            self.builder.ins().icmp(cc, lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Gt => {
                        if is_float {
                            self.builder.ins().fcmp(
                                ir::condcodes::FloatCC::GreaterThan,
                                lhs_val,
                                rhs_val,
                            )
                        } else {
                            let cc = if is_signed {
                                ir::condcodes::IntCC::SignedGreaterThan
                            } else {
                                ir::condcodes::IntCC::UnsignedGreaterThan
                            };
                            self.builder.ins().icmp(cc, lhs_val, rhs_val)
                        }
                    }
                    BinaryOp::Ge => {
                        if is_float {
                            self.builder.ins().fcmp(
                                ir::condcodes::FloatCC::GreaterThanOrEqual,
                                lhs_val,
                                rhs_val,
                            )
                        } else {
                            let cc = if is_signed {
                                ir::condcodes::IntCC::SignedGreaterThanOrEqual
                            } else {
                                ir::condcodes::IntCC::UnsignedGreaterThanOrEqual
                            };
                            self.builder.ins().icmp(cc, lhs_val, rhs_val)
                        }
                    }
                })
            }
            Rvalue::Cast(to_ty, inner) => {
                let inner_val = self.translate_operand(inner);
                let from_ty = self.get_operand_type(inner);
                let cl_to_ty = CraneliftBackend::lower_type(to_ty);

                if from_ty == *to_ty {
                    Some(inner_val)
                } else {
                    match (from_ty.is_float(), to_ty.is_float()) {
                        (false, false) => {
                            // Integer <-> Integer
                            let from_width = from_ty.bit_width();
                            let to_width = to_ty.bit_width();

                            if to_width > from_width {
                                if from_ty.is_signed() {
                                    Some(self.builder.ins().sextend(cl_to_ty, inner_val))
                                } else {
                                    Some(self.builder.ins().uextend(cl_to_ty, inner_val))
                                }
                            } else if to_width < from_width {
                                Some(self.builder.ins().ireduce(cl_to_ty, inner_val))
                            } else {
                                Some(inner_val) // e.g. bitcasting between same-sized int and uint
                            }
                        }
                        (true, true) => {
                            // Float <-> Float
                            if to_ty.bit_width() > from_ty.bit_width() {
                                Some(self.builder.ins().fpromote(cl_to_ty, inner_val))
                            } else {
                                Some(self.builder.ins().fdemote(cl_to_ty, inner_val))
                            }
                        }
                        (false, true) => {
                            // Integer -> Float
                            if from_ty.is_signed() {
                                Some(self.builder.ins().fcvt_from_sint(cl_to_ty, inner_val))
                            } else {
                                Some(self.builder.ins().fcvt_from_uint(cl_to_ty, inner_val))
                            }
                        }
                        (true, false) => {
                            // Float -> Integer
                            if to_ty.is_signed() {
                                Some(self.builder.ins().fcvt_to_sint_sat(cl_to_ty, inner_val))
                            } else {
                                Some(self.builder.ins().fcvt_to_uint_sat(cl_to_ty, inner_val))
                            }
                        }
                    }
                }
            }
            Rvalue::Call(name, args, ret_ty) => {
                let func_id = self.func_ids[name];
                let local_callee = self.module.declare_func_in_func(func_id, self.builder.func);

                let mut arg_vals = Vec::new();
                for arg in args {
                    arg_vals.push(self.translate_operand(arg));
                }

                let call_inst = self.builder.ins().call(local_callee, &arg_vals);

                if *ret_ty == Ty::Unit {
                    None
                } else {
                    Some(self.builder.inst_results(call_inst)[0])
                }
            }
            Rvalue::AddressOf(local_id) => {
                let slot = self.stack_slot_map[local_id];
                Some(self.builder.ins().stack_addr(types::I64, slot, 0))
            }
            Rvalue::ReadPointer(ptr_op) => {
                let ptr_val = self.translate_operand(ptr_op);
                let ptr_ty = self.get_operand_type(ptr_op);
                let inner_ty = match ptr_ty {
                    Ty::Pointer(inner) => *inner,
                    _ => unreachable!(),
                };
                if matches!(inner_ty, Ty::Struct(_) | Ty::Array(_, _)) {
                    Some(ptr_val)
                } else {
                    let cl_ty = CraneliftBackend::lower_type(&inner_ty);
                    Some(
                        self.builder
                            .ins()
                            .load(cl_ty, ir::MemFlags::trusted(), ptr_val, 0),
                    )
                }
            }
            Rvalue::GetFieldPtr {
                base_ptr,
                struct_name,
                field_name,
            } => {
                let base = self.translate_operand(base_ptr);
                let offset = CraneliftBackend::field_offset(struct_name, field_name, self.structs);
                Some(self.builder.ins().iadd_imm(base, offset as i64))
            }
            Rvalue::GetElementPtr {
                base_ptr,
                index,
                element_ty,
            } => {
                let base = self.translate_operand(base_ptr);
                let idx = self.translate_operand(index);

                let idx_ty = self.get_operand_type(index);
                let idx_64 = if idx_ty.bit_width() < 64 {
                    if idx_ty.is_signed() {
                        self.builder.ins().sextend(types::I64, idx)
                    } else {
                        self.builder.ins().uextend(types::I64, idx)
                    }
                } else {
                    idx
                };

                let size = CraneliftBackend::type_size(element_ty, self.structs);
                let offset = self.builder.ins().imul_imm(idx_64, size as i64);
                Some(self.builder.ins().iadd(base, offset))
            }
        }
    }

    fn get_rvalue_type(&self, rvalue: &Rvalue) -> Ty {
        match rvalue {
            Rvalue::Use(op) => self.get_operand_type(op),
            Rvalue::UnaryOp(op, inner) => match op {
                UnaryOp::Deref => {
                    if let Ty::Pointer(inner) = self.get_operand_type(inner) {
                        *inner
                    } else {
                        unreachable!()
                    }
                }
                UnaryOp::AddressOf => Ty::Pointer(Box::new(self.get_operand_type(inner))),
                _ => self.get_operand_type(inner),
            },
            Rvalue::BinaryOp(_, lhs, _) => self.get_operand_type(lhs),
            Rvalue::Cast(ty, _) => ty.clone(),
            Rvalue::Call(_, _, ty) => ty.clone(),
            Rvalue::AddressOf(local) => Ty::Pointer(Box::new(self.locals[local.0].ty.clone())),
            Rvalue::ReadPointer(ptr) => {
                if let Ty::Pointer(inner) = self.get_operand_type(ptr) {
                    *inner
                } else {
                    unreachable!()
                }
            }
            Rvalue::GetFieldPtr {
                struct_name,
                field_name,
                ..
            } => {
                let fields = self.structs.get(struct_name).unwrap();
                let ty = fields
                    .iter()
                    .find(|(n, _)| n == field_name)
                    .unwrap()
                    .1
                    .clone();
                Ty::Pointer(Box::new(ty))
            }
            Rvalue::GetElementPtr { element_ty, .. } => Ty::Pointer(Box::new(element_ty.clone())),
        }
    }

    fn emit_memcpy(&mut self, dest: ir::Value, src: ir::Value, mut size: u32) {
        let mut offset = 0;
        while size >= 8 {
            let val = self
                .builder
                .ins()
                .load(types::I64, ir::MemFlags::trusted(), src, offset);
            self.builder
                .ins()
                .store(ir::MemFlags::trusted(), val, dest, offset);
            size -= 8;
            offset += 8;
        }
        if size >= 4 {
            let val = self
                .builder
                .ins()
                .load(types::I32, ir::MemFlags::trusted(), src, offset);
            self.builder
                .ins()
                .store(ir::MemFlags::trusted(), val, dest, offset);
            size -= 4;
            offset += 4;
        }
        if size >= 2 {
            let val = self
                .builder
                .ins()
                .load(types::I16, ir::MemFlags::trusted(), src, offset);
            self.builder
                .ins()
                .store(ir::MemFlags::trusted(), val, dest, offset);
            size -= 2;
            offset += 2;
        }
        if size == 1 {
            let val = self
                .builder
                .ins()
                .load(types::I8, ir::MemFlags::trusted(), src, offset);
            self.builder
                .ins()
                .store(ir::MemFlags::trusted(), val, dest, offset);
        }
    }
}
