use std::collections::BTreeMap;

use crate::{backend::{arm_instr::{ARMInstr, AddInstr, BlInstr, LoadInstr, LoadPseudoInstr, MoveInstr, MulInstr, PopInstr, PushInstr, RegisterOrImm, SDivInstr, StoreInstr, SubInstr}, register_allocator::{REG_R0, REG_R1, REG_R2, REG_R3, Register, RegisterAlloc, RegisterAllocator}, types::ARMAsmVar}, ir::types::{Function, IRInstr, MoveRValue, Variable, VariableType}};
use crate::ir::types::BinaryOp as IRBinaryOp;
pub const ARM_STACK_ALIGNMENT: usize = 8;
pub struct Generator {
    instrs: Vec<ARMInstr>,
    var_inited: Vec<ARMAsmVar>,
    var_uninited: Vec<ARMAsmVar>,
    register_allocator: RegisterAllocator,
}

const DEFAULT_VAR_ALIGN: usize = 4;
impl Generator {
    pub fn new() -> Self {
        Self {
            instrs: Vec::new(),
            var_inited: Vec::new(),
            var_uninited: Vec::new(),
            register_allocator: RegisterAllocator::new(),
        }
    }
    pub fn emit(&mut self, ir_instrs: Vec<IRInstr>) {
        for ir_instr in ir_instrs {
            match ir_instr {
                IRInstr::DefineFunc(func, args,  body) => self.emit_func_def(func, args, body),
                IRInstr::Declare(var) => self.emit_global_decl(var),
                _ => unreachable!(),
            }
        }
    }
    pub fn to_text(&self) -> String {
        let mut text = String::new();
        text.push_str(".arch armv7ve\n.arm\n.fpu vfpv4\n");
        for var in &self.var_uninited {
            text.push_str(&format!(".comm {}, {}, {}\n", var.name, var.size, var.align));
        }
        for var in &self.var_inited {
            text.push_str(&format!(".data\n.align {}\n.global {}\n.type {}, @object\n:{}\n", var.align, var.name, var.name, var.name));
            text.push_str(&format!(".word 0\n"));
        }
        text.push_str(".text\n");
        for instr in &self.instrs {
            text.push_str(&format!("{}\n", instr));
        }
        text
    }


    fn emit_global_decl(&mut self, var: Variable) {
        self.var_uninited.push(ARMAsmVar {
            name: format!("global_var_{}", var.index),
            size: var.data_type.size_in_bytes(),
            align: DEFAULT_VAR_ALIGN,
        });
    }

    fn emit_func_def(&mut self, func: Function, _args: Vec<Variable>, body: Vec<IRInstr>) {
        self.instrs.push(ARMInstr::FunctionHead(func.name.clone(), 4)); // Assuming 4-byte alignment for simplicity
        self.instrs.push(PushInstr::new_push_fp_lr());
        self.instrs.push(MoveInstr::new_sp_to_fp());
        self.emit_func(body);
    }

    fn emit_func(&mut self, instrs: Vec<IRInstr>) {
        let mut encounter_entry = false;
        let mut stack_size_needed = 0;
        let mut var_index_to_stack_offset = BTreeMap::new();
        for ir_instr in instrs {
            match ir_instr {
                IRInstr::Binary(dest, left, op, right) => self.emit_binary(dest, left, op, right, &var_index_to_stack_offset),
                IRInstr::Exit(v) => {
                    if let Some(v) = v {
                        let ret_alloc = self.register_allocator.alloc_reg(REG_R0).expect("Ran out of registers");
                        let ret_reg = ret_alloc.reg;
                        let v_alloc = self.register_allocator.alloc(v).expect("Ran out of registers");
                        let v_reg = v_alloc.reg;
                        if !v_alloc.is_reused {
                            let v_stack_offset = var_index_to_stack_offset.get(&v.index).expect("Variable not declared");
                            self.instrs.push(LoadInstr::new_stack(v_reg, *v_stack_offset as i32));
                        }
                        self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(v_reg)));
                    }
                    self.instrs.push(MoveInstr::new_fp_to_sp());
                    self.instrs.push(PopInstr::new_pop_fp_pc());
                },
                IRInstr::FuncCall(func, args, ret) => self.emit_func_call(func, args, ret, &var_index_to_stack_offset),
                IRInstr::Move(dest, src) => self.emit_move(dest, src, &var_index_to_stack_offset),
                IRInstr::Declare(variable) => {
                    assert!(!encounter_entry, "Variable declarations must come before entry instruction");
                    let size = variable.data_type.size_in_bytes();
                    stack_size_needed = (stack_size_needed + size).next_multiple_of(ARM_STACK_ALIGNMENT);
                    var_index_to_stack_offset.insert(variable.index, stack_size_needed);
                },
                IRInstr::Entry => {
                    assert!(!encounter_entry, "Multiple entry instructions are not allowed");
                    encounter_entry = true;
                    self.instrs.push(SubInstr::new_sp(stack_size_needed as i32));
                },
                IRInstr::DefineFunc(_, _, _) => unreachable!(),
            }
        }
    }
    fn emit_func_call(&mut self, func: Function, args: Vec<Variable>, ret: Option<Variable>, var_index_to_stack_offset: &BTreeMap<usize, usize>) {

        self.instrs.push(PushInstr::new_push_caller_save());
        if args.len() > 4 {
            todo!("More than 4 arguments not supported yet");
        }
        const ARG_REGS: [Register; 4] = [REG_R0, REG_R1, REG_R2, REG_R3];
        for (i, arg) in args.into_iter().enumerate() {
            let arg_alloc = self.register_allocator.alloc(arg).expect("Ran out of registers");
            let arg_reg = arg_alloc.reg;
            if !arg_alloc.is_reused {
                let arg_stack_offset = var_index_to_stack_offset.get(&arg.index).expect("Variable not declared");
                self.instrs.push(LoadInstr::new_stack(arg_reg, *arg_stack_offset as i32));
            }
            self.instrs.push(MoveInstr::new_uncond(ARG_REGS[i], RegisterOrImm::Reg(arg_reg)));
        }
        self.instrs.push(BlInstr::new(func.name.clone()));
        if let Some(ret) = ret {
            let ret_alloc = self.register_allocator.alloc(ret).expect("Ran out of registers");
            let ret_reg = ret_alloc.reg;
            self.instrs.push(MoveInstr::new_uncond(ret_reg, RegisterOrImm::Reg(REG_R0)));
        }


        self.instrs.push(PopInstr::new_pop_caller_save());
    }
    fn emit_move(&mut self, dest: Variable, src: MoveRValue, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
        let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers");
        let dest_register = dest_alloc.reg;
        match src {
            MoveRValue::Var(variable) => {
                if !dest_alloc.is_reused {
                    let var_stack_offset = var_index_to_stack_offset.get(&variable.index).expect("Variable not found");
                    self.instrs.push(LoadInstr::new_stack(dest_register, *var_stack_offset as i32));
                }
            },
            MoveRValue::ConstInt(literal_int) => self.instrs.push(MoveInstr::new_uncond(dest_register, RegisterOrImm::Imm(literal_int))),
        };
        match dest.var_type {
            VariableType::Global => {
                let address_reg = self.register_allocator.alloc_any().expect("Ran out of registers");
                self.instrs.push(LoadPseudoInstr::new(address_reg, format!("global_var_{}", dest.index)));
                self.instrs.push(StoreInstr::new(dest_register, address_reg, None));
            },
            VariableType::ParamTemp => {
                todo!()
            },
            _ => {
                let offset = *var_index_to_stack_offset.get(&dest.index).expect("Variable not declared");
                self.instrs.push(StoreInstr::new_stack(dest_register, offset as i32));
            }
        }
    }
    fn emit_binary(&mut self, dest: Variable, left: Variable, op: IRBinaryOp, right: Variable, var_index_to_stack_offset: &BTreeMap<usize, usize>) {
        let left_alloc = self.register_allocator.alloc(left).expect("Ran out of registers");
        let right_alloc = self.register_allocator.alloc(right).expect("Ran out of registers");
        let dest_alloc = self.register_allocator.alloc(dest).expect("Ran out of registers");
        let left_reg = left_alloc.reg;
        let right_reg = right_alloc.reg;
        let dest_reg = dest_alloc.reg;
        if !left_alloc.is_reused {
            let left_offset = var_index_to_stack_offset.get(&left.index).expect("Variable not declared");
            self.instrs.push(LoadInstr::new_stack(left_reg, *left_offset as i32));
        
        }
        if !right_alloc.is_reused {
            let right_offset = var_index_to_stack_offset.get(&right.index).expect("Variable not declared");
            self.instrs.push(LoadInstr::new_stack(right_reg, *right_offset as i32));
        }
        match op {
            IRBinaryOp::Add => {
                self.instrs.push(AddInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg)));
            },
            IRBinaryOp::Sub => {
                self.instrs.push(SubInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg)));
            },
            IRBinaryOp::Mul => {
                self.instrs.push(MulInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg)));
            },
            IRBinaryOp::Div => {
                self.instrs.push(SDivInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(right_reg)));
            },
            IRBinaryOp::Mod => {
                let temp_reg = self.register_allocator.alloc_any().expect("Ran out of registers");
                self.instrs.push(SDivInstr::new(temp_reg, left_reg, RegisterOrImm::Reg(right_reg)));
                self.instrs.push(MulInstr::new(temp_reg, temp_reg, RegisterOrImm::Reg(right_reg)));
                self.instrs.push(SubInstr::new(dest_reg, left_reg, RegisterOrImm::Reg(temp_reg)));
            },
            IRBinaryOp::Le => todo!(),
            IRBinaryOp::Lt => todo!(),
            IRBinaryOp::Gt => todo!(),
            IRBinaryOp::Ge => todo!(),
            IRBinaryOp::Ne => todo!(),
            IRBinaryOp::Eq => todo!(),
        }
        let dest_stack_offset = var_index_to_stack_offset.get(&dest.index).expect("Variable not declared");
        self.instrs.push(StoreInstr::new_stack(dest_reg, *dest_stack_offset as i32));
    }

}