use std::{collections::BTreeMap, vec};

use crate::{ir::types::{BinaryOp as IRBinaryOp, CmpOp, Function, IRInstr, IRType, MoveRValue, UnaryOp, Variable, VariableOrIntLit, VariableType}, sema::{analyzer::Analyzer as SemaAnalyzer, hir::{HirBlockStmt, HirBreakStmt, HirCompileUnit, HirContinueStmt, HirExpr, HirExprValue, HirForInit, HirForStmt, HirFuncDeclStmt, HirGlobalDeclStmt, HirIfElseBranch, HirIfStmt, HirReturnStmt, HirStatement, HirVarDeclStmt, HirWhileStmt}, symbol::{FunctionId, SymbolId, SymbolKind}}};
use crate::ast::types::BinaryOp as AstBinaryOp;
use crate::ast::types::UnaryOp as AstUnaryOp;
pub struct Generator<'a> {
    sema: &'a SemaAnalyzer,
    var_manager: VariableManager,
    current_exit_label: Vec<Option<(usize, usize)>>, // true exit, false exit
    // About exit label passing:
    // for non-logical parent expr, current_exit_label is None
    // this means we need to create label for logical child expr, and calculate the value
    // for logical parent expr, we can directly use the label for logical child expr, and no need to calculate
    // if child expr isn't logical, we need to do cmp to decide which label to goto
    while_exit_label: Vec<(usize, usize)>, // continue exit, break exit
    func_exit: Option<(usize, Option<Variable>)>, // (label, return_var)
    extra_global_instrs: Vec<IRInstr>,
    label_counter: usize
}

impl<'a> Generator<'a> {
    pub fn new(sema: &'a SemaAnalyzer) -> Self {
        Self {
            sema,
            var_manager: VariableManager::new(),
            current_exit_label: vec![],
            while_exit_label: vec![],
            func_exit: None,
            extra_global_instrs: vec![],
            label_counter: 0,
        }
    }
    fn request_label(&mut self) -> usize {
        let label = self.label_counter;
        self.label_counter += 1;
        label
    }
    pub fn emit(&mut self, compile_unit: HirCompileUnit) -> Vec<IRInstr> {
        self.generate_compile_unit(compile_unit)
    }

    fn ir_function(&self, function_id: FunctionId) -> Function {
        let sig = self.sema.get_function_sig(function_id);
        Function {
            name: sig.name.clone(),
            parameter_types: sig.parameter_types.iter().map(|ty| ty.clone().into()).collect(),
            return_type: sig.return_type.clone().into(),
        }
    }

    fn generate_compile_unit(&mut self, compile_unit: HirCompileUnit) -> Vec<IRInstr> {
        let mut instrs = vec![];
        use HirGlobalDeclStmt::*;
        for decl in compile_unit.global_decls {
            match decl {
                VarDecl(var_decl) => {
                    instrs.extend(self.generate_var_decl(var_decl, true));
                }
                FuncDecl(func_decl) => {
                    let extra_global_start = self.extra_global_instrs.len();
                    let func_instrs = self.generate_func_decl(func_decl);
                    instrs.extend(self.extra_global_instrs.drain(extra_global_start..));
                    instrs.extend(func_instrs);
                }
            }
        }
        instrs
    }

    fn generate_var_decl(&mut self, var_decl: HirVarDeclStmt, is_global: bool) -> Vec<IRInstr> {
        let mut instrs = vec![];
        for value in var_decl.values {
            let symbol_kind = self.sema.get_symbol_kind(value.symbol);
            let has_static_storage = is_global || symbol_kind == SymbolKind::StaticLocal || value.is_static_local;
            let var_type = if has_static_storage { VariableType::Global } else { VariableType::Local };
            let var = self.var_manager.declare_symbol(value.symbol, var_type, self.sema.get_symbol_type(value.symbol).into());
            if has_static_storage {
                let init = value.value.as_ref().and_then(|expr| Self::const_init_value(expr));
                let decl = IRInstr::DeclareGlobal(var, init);
                if is_global {
                    instrs.push(decl);
                } else {
                    self.extra_global_instrs.push(decl);
                }
            } else if let Some(init) = value.value {
                self.current_exit_label.push(None);
                let (init_instrs, init_var) = match self.generate_expr(init) {
                    Some(res) => res,
                    None => {
                        self.current_exit_label.pop();
                        continue;
                    }
                };
                self.current_exit_label.pop();
                let target = self.var_manager.get_symbol(value.symbol).unwrap();
                instrs.extend(init_instrs);
                instrs.push(IRInstr::Move(target, MoveRValue::Var(init_var.unwrap())));
            }
        }

        instrs
    }

    fn const_init_value(expr: &HirExpr) -> Option<i32> {
        match &expr.value {
            HirExprValue::IntLit(value) => Some(*value as i32),
            HirExprValue::UnaryOp { op, operand } => {
                let value = Self::const_init_value(operand)?;
                match op {
                    AstUnaryOp::Add => Some(value),
                    AstUnaryOp::Sub => value.checked_neg(),
                    AstUnaryOp::Not => Some((value == 0) as i32),
                }
            }
            HirExprValue::BinaryOp { lhs, op, rhs } => {
                let lhs = Self::const_init_value(lhs)?;
                let rhs = Self::const_init_value(rhs)?;
                match op {
                    AstBinaryOp::Add => lhs.checked_add(rhs),
                    AstBinaryOp::Sub => lhs.checked_sub(rhs),
                    AstBinaryOp::Mul => lhs.checked_mul(rhs),
                    AstBinaryOp::Div => lhs.checked_div(rhs),
                    AstBinaryOp::Mod => lhs.checked_rem(rhs),
                    AstBinaryOp::Equal => Some((lhs == rhs) as i32),
                    AstBinaryOp::NotEqual => Some((lhs != rhs) as i32),
                    AstBinaryOp::Less => Some((lhs < rhs) as i32),
                    AstBinaryOp::LessEqual => Some((lhs <= rhs) as i32),
                    AstBinaryOp::Greater => Some((lhs > rhs) as i32),
                    AstBinaryOp::GreaterEqual => Some((lhs >= rhs) as i32),
                    AstBinaryOp::And => Some((lhs != 0 && rhs != 0) as i32),
                    AstBinaryOp::Or => Some((lhs != 0 || rhs != 0) as i32),
                }
            }
            _ => None,
        }
    }

    fn generate_func_decl(&mut self, func_decl: HirFuncDeclStmt) -> Vec<IRInstr> {
        let parameters: Vec<Variable> = func_decl.params.iter()
            .map(|param| self.var_manager.declare_symbol(param.symbol, VariableType::Local, param.param_type.clone().into()))
            .collect();
        let temp_parameters = parameters.iter().enumerate()
            .map(|(i, param)| self.var_manager.declare_param_temp(param.data_type.clone(), i))
            .collect::<Vec<_>>();
        let mut body_instrs = vec![];
        self.func_exit = Some((self.request_label(),  {
            let ret_type = func_decl.sig.return_type.into();
            if ret_type != IRType::Void {
                Some(self.var_manager.declare_unamed_local(ret_type))
            } else {
                None
            }
        }));
        let block_instrs = self.generate_block_stmt(func_decl.body);
        for var in self.var_manager.get_cur_func_variables() {
            if matches!(var.var_type, VariableType::ParamTemp(_)) {
                continue;
            }
            body_instrs.push(IRInstr::Declare(var));
        }
        body_instrs.push(IRInstr::Entry);
        parameters.iter().zip(temp_parameters.iter()).for_each(|(param, temp_param)| {
            body_instrs.push(IRInstr::Move(param.clone(), MoveRValue::Var(temp_param.clone())));
        });
        body_instrs.extend(block_instrs);
        let func_exit = self.func_exit.take().unwrap();
        body_instrs.push(IRInstr::Label(func_exit.0));
        body_instrs.push(IRInstr::Exit(func_exit.1));
        self.var_manager.clear_local_counter();
        let mut func = self.ir_function(func_decl.sig.id);
        func.parameter_types = temp_parameters.iter().map(|v| v.data_type.clone()).collect();
        vec![IRInstr::DefineFunc(func, temp_parameters, body_instrs)]
    }
    fn generate_block_stmt(&mut self, block_stmt: HirBlockStmt) -> Vec<IRInstr> {
        let mut instrs = vec![];
        for stmt in block_stmt.statements {
            instrs.extend(self.generate_statement(stmt));
        }
        instrs
    }

    fn generate_statement(&mut self, stmt: HirStatement) -> Vec<IRInstr> {
        use HirStatement::*;
        let instrs = match stmt {
            Return(return_stmt) => self.generate_return_stmt(return_stmt),
            If(if_stmt) => self.generate_if_stmt(if_stmt),
            While(while_stmt) => self.generate_while_stmt(while_stmt),
            For(for_stmt) => self.generate_for_stmt(for_stmt),
            Break(break_stmt) => self.generate_break_stmt(break_stmt),
            Continue(continue_stmt) => self.generate_continue_stmt(continue_stmt),
            Block(block_stmt) => {
                self.generate_block_stmt(block_stmt)
            },
            Expr(expr) => {
                self.current_exit_label.push(None);
                let (instrs, _) = match self.generate_expr(expr) {
                    Some(res) => res,
                    None => {
                        self.current_exit_label.pop();
                        return vec![];
                    }
                };
                self.current_exit_label.pop();
                instrs
            },
            VarDecl(var_decl) => self.generate_var_decl(var_decl, false),
        };
        instrs
    }

    fn generate_return_stmt(&mut self, return_stmt: HirReturnStmt) -> Vec<IRInstr> {
        let mut instrs = vec![];
        let func_exit = self.func_exit.clone().unwrap();
        match return_stmt.value {
            Some(expr) => {
                self.current_exit_label.push(None);
                let (value_instrs, value_var) = match self.generate_expr(expr) {
                    Some(res) => res,
                    None => {
                        self.current_exit_label.pop();
                        return vec![];
                    }
                };
                self.current_exit_label.pop();
                instrs.extend(value_instrs);
                instrs.push(IRInstr::Move(func_exit.1.unwrap(), MoveRValue::Var(value_var.unwrap())));
            }
            None => {},
        }
        instrs.push(IRInstr::Goto(func_exit.0));
        instrs
    }
    fn generate_while_stmt(&mut self, while_stmt: HirWhileStmt) -> Vec<IRInstr> {
        let mut instrs = vec![];
        let cond_label = self.request_label();
        let body_label = self.request_label();
        let exit_label = self.request_label();
        instrs.push(IRInstr::Label(cond_label));
        self.current_exit_label.push(Some((body_label, exit_label)));
        let (cond_instrs, cond_var) = match self.generate_expr(while_stmt.condition) {
            Some(res) => res,
            None => {
                self.current_exit_label.pop();
                return vec![];
            }
        };
        self.current_exit_label.pop();
        instrs.extend(cond_instrs);
        instrs.push(IRInstr::Label(body_label));
        self.while_exit_label.push((cond_label, exit_label));
        instrs.extend(self.generate_block_stmt(while_stmt.body));
        self.while_exit_label.pop();
        instrs.push(IRInstr::Goto(cond_label));
        instrs.push(IRInstr::Label(exit_label));
        instrs
    }
    fn generate_for_stmt(&mut self, for_stmt: HirForStmt) -> Vec<IRInstr> {
        let mut instrs = vec![];
        let cond_label = self.request_label();
        let body_label = self.request_label();
        let update_label = self.request_label();
        let exit_label = self.request_label();

        if let Some(init) = for_stmt.init {
            match init {
                HirForInit::Expr(init) => {
                    self.current_exit_label.push(None);
                    let (init_instrs, _) = match self.generate_expr(init) {
                        Some(res) => res,
                        None => {
                            self.current_exit_label.pop();
                            return vec![];
                        }
                    };
                    self.current_exit_label.pop();
                    instrs.extend(init_instrs);
                }
                HirForInit::VarDecl(var_decl) => {
                    instrs.extend(self.generate_var_decl(var_decl, false));
                }
            }
        }

        instrs.push(IRInstr::Label(cond_label));
        if let Some(condition) = for_stmt.condition {
            self.current_exit_label.push(Some((body_label, exit_label)));
            let (cond_instrs, _) = match self.generate_expr(condition) {
                Some(res) => res,
                None => {
                    self.current_exit_label.pop();
                    return vec![];
                }
            };
            self.current_exit_label.pop();
            instrs.extend(cond_instrs);
        }

        instrs.push(IRInstr::Label(body_label));
        self.while_exit_label.push((update_label, exit_label));
        instrs.extend(self.generate_block_stmt(for_stmt.body));
        self.while_exit_label.pop();

        instrs.push(IRInstr::Label(update_label));
        if let Some(update) = for_stmt.update {
            self.current_exit_label.push(None);
            let (update_instrs, _) = match self.generate_expr(update) {
                Some(res) => res,
                None => {
                    self.current_exit_label.pop();
                    return vec![];
                }
            };
            self.current_exit_label.pop();
            instrs.extend(update_instrs);
        }
        instrs.push(IRInstr::Goto(cond_label));
        instrs.push(IRInstr::Label(exit_label));
        instrs
    }
    fn generate_if_stmt(&mut self, if_stmt: HirIfStmt) -> Vec<IRInstr> {
        let mut instrs = vec![];
        let then_label = self.request_label();
        let exit_label = self.request_label();
        let mut labels = vec![then_label];
        for _ in &if_stmt.ifelse_branch {
            let else_if_label = self.request_label();
            let else_if_body_label = self.request_label();

            labels.push(else_if_label);
            labels.push(else_if_body_label);
        }
        if if_stmt.else_branch.is_some() {
            let else_label = self.request_label();
            labels.push(else_label);
        }
        labels.push(exit_label);
        // now generate if expr, true exit to labels[0], false exit to labels[1]
        self.current_exit_label.push(Some((labels[0], labels[1])));
        let (cond_instrs, cond_var) = match self.generate_expr(if_stmt.condition) {
            Some(res) => res,
            None => {
                self.current_exit_label.pop();
                return vec![];
            }
        };
        self.current_exit_label.pop();
        instrs.extend(cond_instrs);
        instrs.push(IRInstr::Label(labels[0]));
        instrs.extend(self.generate_block_stmt(if_stmt.then_branch));
        instrs.push(IRInstr::Goto(exit_label));
        for (i, else_if_branch) in if_stmt.ifelse_branch.into_iter().enumerate() {
            let HirIfElseBranch { condition: else_if_cond, then_branch: else_if_block } = else_if_branch;
            instrs.push(IRInstr::Label(labels[i * 2 + 1]));
            self.current_exit_label.push(Some((labels[i * 2 + 2], labels[i * 2 + 3])));
            let (else_if_cond_instrs, else_if_cond_var) = match self.generate_expr(else_if_cond) {
                Some(res) => res,
                None => {
                    self.current_exit_label.pop();
                    continue;
                }
            };
            self.current_exit_label.pop();
            instrs.extend(else_if_cond_instrs);
            instrs.push(IRInstr::Label(labels[i * 2 + 2]));
            instrs.extend(self.generate_block_stmt(else_if_block));
            instrs.push(IRInstr::Goto(exit_label));
        }
        if let Some(else_block) = if_stmt.else_branch {
            instrs.push(IRInstr::Label(labels[labels.len() - 2]));
            instrs.extend(self.generate_block_stmt(else_block));
            instrs.push(IRInstr::Goto(exit_label));
        }
        instrs.push(IRInstr::Label(exit_label));
        instrs
    }
    fn generate_continue_stmt(&mut self, _stmt: HirContinueStmt) -> Vec<IRInstr> {
        if let Some((continue_label, _)) = self.while_exit_label.last() {
            vec![IRInstr::Goto(*continue_label)]
        } else {
            vec![]
        }
    }
    fn generate_break_stmt(&mut self, _stmt: HirBreakStmt) -> Vec<IRInstr> {
        if let Some((_, break_label)) = self.while_exit_label.last() {
            vec![IRInstr::Goto(*break_label)]
        } else {
            vec![]
        }
    }
    fn generate_expr(&mut self, expr: HirExpr) -> Option<(Vec<IRInstr>, Option<Variable>)> {
        // there may be some expr that doesn't produce value, like void func call
        let (mut instrs, var) = match expr.value {
            HirExprValue::IntLit(i) => {
                // TODO: convert check
                let var = self.var_manager.declare_temp(IRType::I32);
                (vec![IRInstr::Move(var.clone(), MoveRValue::ConstInt(i as i32))], Some(var))
            },
            HirExprValue::Var(symbol) => {
                let var = self.var_manager.get_symbol(symbol).unwrap();
                if var.data_type.is_array() {
                    if var.data_type.is_array_param() {
                        (vec![], Some(var))
                    } else {
                        let ptr_ty = var.data_type.decay_to_ptr().unwrap();
                        let dest = self.var_manager.declare_temp(ptr_ty);
                        (vec![IRInstr::Move(dest.clone(), MoveRValue::Var(var))], Some(dest))
                    }
                } else {
                    (vec![], Some(var))
                }
            },
            HirExprValue::Assign { lvalue, rvalue } => {
                let lvalue_ty: IRType = lvalue.ty.clone().into();
                self.current_exit_label.push(None);
                let (mut instrs, rvalue_var) = self.generate_expr(*rvalue)?;
                self.current_exit_label.pop();
                let (lvalue_instrs, target, is_addr) = self.generate_lvalue(*lvalue)?;
                instrs.extend(lvalue_instrs);
                if is_addr {
                    instrs.push(IRInstr::Store(target.clone(), rvalue_var));
                } else {
                    instrs.push(IRInstr::Move(target.clone(), MoveRValue::Var(rvalue_var)));
                }
                let temp_var = self.var_manager.declare_temp(lvalue_ty);
                if is_addr {
                    instrs.push(IRInstr::Load(temp_var.clone(), target));
                } else {
                    instrs.push(IRInstr::Move(temp_var.clone(), MoveRValue::Var(target)));
                }
                (instrs, Some(temp_var))
            },
            HirExprValue::CompoundAssign { lvalue, op, rvalue } => {
                let lvalue_ty: IRType = lvalue.ty.clone().into();
                let (mut instrs, target, is_addr) = self.generate_lvalue(*lvalue)?;
                let mut old_var = self.var_manager.declare_temp(lvalue_ty.clone());
                if is_addr {
                    instrs.push(IRInstr::Load(old_var.clone(), target.clone()));
                } else {
                    instrs.push(IRInstr::Move(old_var.clone(), MoveRValue::Var(target.clone())));
                }

                self.current_exit_label.push(None);
                let (rvalue_instrs, rvalue_var) = self.generate_expr(*rvalue)?;
                self.current_exit_label.pop();
                instrs.extend(rvalue_instrs);
                let mut rvalue_var = rvalue_var.unwrap();

                let convert_to = IRType::get_elevate_result(&old_var.data_type, &rvalue_var.data_type).unwrap();
                if convert_to != old_var.data_type {
                    old_var = self.coerce_value(&mut instrs, old_var, &convert_to);
                }
                if convert_to != rvalue_var.data_type {
                    rvalue_var = self.coerce_value(&mut instrs, rvalue_var, &convert_to);
                }
                let computed = self.var_manager.declare_temp(convert_to);
                instrs.push(IRInstr::Binary(computed.clone(), old_var, op.into(), rvalue_var));
                let stored = self.coerce_value(&mut instrs, computed, &lvalue_ty);
                if is_addr {
                    instrs.push(IRInstr::Store(target.clone(), stored));
                } else {
                    instrs.push(IRInstr::Move(target.clone(), MoveRValue::Var(stored)));
                }
                let temp_var = self.var_manager.declare_temp(lvalue_ty);
                if is_addr {
                    instrs.push(IRInstr::Load(temp_var.clone(), target));
                } else {
                    instrs.push(IRInstr::Move(temp_var.clone(), MoveRValue::Var(target)));
                }
                (instrs, Some(temp_var))
            },
            HirExprValue::ArrayAccess { array, index } => {
                let result_ty: IRType = expr.ty.clone().into();
                let (mut instrs, addr) = self.generate_array_element_addr(*array, *index)?;
                if result_ty.is_array() {
                    (instrs, Some(addr))
                } else {
                    let dest = self.var_manager.declare_temp(result_ty);
                    instrs.push(IRInstr::Load(dest.clone(), addr));
                    (instrs, Some(dest))
                }
            },
            HirExprValue::UnaryOp { op, operand } => {
                let mut parent_is_logical = true;
                let exit_passdown = if matches!(op, AstUnaryOp::Not) {
                    Some(self.current_exit_label.last().cloned().flatten().map_or_else(|| {
                        parent_is_logical = false;
                        (self.request_label(), self.request_label())
                    }, |(true_exit, false_exit)| (false_exit, true_exit)))
                } else {
                    None
                };
                // NOT:
                //   $x = cmp eq left_var, 0
                //   bc, $x, true_exit, false_exit
                //   false_exit: #only when parent isn't logical
                //   true_exit:
                //   $dest = $x

                // +--------+-------------------------+-------------------------+----------------------------+
                // | parent | (true_exit, false_exit) | (true_exit, false_exit) | None                       |
                // | self   | not                     | add/sub                 | not(true_exit, false_exit) |
                // | child  | (false_exit, true_exit) | None                    | (true_exit, false_exit)    |
                // +--------+-------------------------+-------------------------+----------------------------+


                self.current_exit_label.push(exit_passdown);
                let (mut instrs, operand_var) = self.generate_expr(*operand)?;
                self.current_exit_label.pop();
                let operand_var = operand_var.unwrap();
                let dest_var = match op {
                    AstUnaryOp::Add => {
                        let dest_var = self.var_manager.declare_temp(operand_var.data_type.clone());
                        instrs.push(IRInstr::Move(dest_var.clone(), MoveRValue::Var(operand_var)));
                        dest_var
                    },
                    AstUnaryOp::Sub => {
                        let dest_var = self.var_manager.declare_temp(operand_var.data_type.clone());
                        instrs.push(IRInstr::Unary(dest_var.clone(), UnaryOp::Neg, operand_var));
                        dest_var
                    },
                    AstUnaryOp::Not => {
                        let dest_var = self.var_manager.declare_unamed_local(operand_var.data_type.clone());
                        // child will do the cmp
                        if !parent_is_logical {
                            let exit = exit_passdown.unwrap(); // (false_exit, true_exit) (consider `not`)
                            let final_exit = self.request_label();
                            instrs.push(IRInstr::Label(exit.1));
                            instrs.push(IRInstr::Move(dest_var.clone(), MoveRValue::ConstInt(1)));
                            instrs.push(IRInstr::Goto(final_exit));
                            instrs.push(IRInstr::Label(exit.0));
                            instrs.push(IRInstr::Move(dest_var.clone(), MoveRValue::ConstInt(0)));
                            instrs.push(IRInstr::Goto(final_exit));
                            instrs.push(IRInstr::Label(final_exit));

                        }
                        // return directly since it's logical expr
                        return Some((instrs, Some(dest_var)));
                    }
                };
                (instrs, Some(dest_var))
            },
            HirExprValue::IncDec { op, operand, is_prefix } => {
                let result_ty: IRType = expr.ty.clone().into();
                let (mut instrs, target, is_addr) = self.generate_lvalue(*operand)?;
                let old_var = self.var_manager.declare_temp(result_ty.clone());
                if is_addr {
                    instrs.push(IRInstr::Load(old_var.clone(), target.clone()));
                } else {
                    instrs.push(IRInstr::Move(old_var.clone(), MoveRValue::Var(target.clone())));
                }

                let one = self.var_manager.declare_temp(result_ty.clone());
                instrs.push(IRInstr::Move(one.clone(), MoveRValue::ConstInt(1)));
                let new_var = self.var_manager.declare_temp(result_ty.clone());
                let ir_op = match op {
                    crate::ast::types::IncDecOp::Inc => IRBinaryOp::Add,
                    crate::ast::types::IncDecOp::Dec => IRBinaryOp::Sub,
                };
                instrs.push(IRInstr::Binary(new_var.clone(), old_var.clone(), ir_op, one));
                if is_addr {
                    instrs.push(IRInstr::Store(target, new_var.clone()));
                } else {
                    instrs.push(IRInstr::Move(target, MoveRValue::Var(new_var.clone())));
                }
                if is_prefix {
                    (instrs, Some(new_var))
                } else {
                    (instrs, Some(old_var))
                }
            },
            HirExprValue::BinaryOp { lhs, op, rhs } => {
                // +--------+-------------------------+-------------------------+-------------------------+
                // | parent | (true_exit, false_exit) | (true_exit, false_exit) | (true_exit, false_exit) |
                // | self   | and(next)               | or(next)                | others                  |
                // | lhs    | (next, false_exit)      | (true_exit, next)       | None                    |
                // | rhs    | (true_exit, false_exit) | (next, false_exit)      | None                    |
                // +--------+-------------------------+-------------------------+-------------------------+


                // +--------+----------------------------------+-------------------------------+--------+
                // | parent | None                             | None                          | None   |
                // | self   | and(true_exit, next, false_exit) | or(true_exit,next,false_exit) | others |
                // | lhs    | (next, false_exit)               | (true_exit, next)             | None   |
                // | rhs    | (true_exit, false_exit)          | (next, false_exit)            | None   |
                // +--------+----------------------------------+-------------------------------+--------+


                let mut parent_exit = None;
                let exit = if op.is_logical() {
                    Some(self.current_exit_label.last().cloned().flatten().map_or(
                        (self.request_label(), self.request_label(), self.request_label()),
                        |(true_exit, false_exit)| {
                            parent_exit = Some((true_exit, false_exit));
                            (true_exit, self.request_label(), false_exit)
                        }
                    ))
                } else {
                    None
                }; // (true_exit, next_label, false_exit)
                let lhs_exit_passdown = exit.map(|(true_exit, next_label, false_exit)| match op {
                    AstBinaryOp::And => (next_label, false_exit),
                    AstBinaryOp::Or => (true_exit, next_label),
                    _ => unreachable!(),
                });
                let rhs_exit_passdown = exit.map(|(true_exit, _, false_exit)| match op {
                    AstBinaryOp::And => (true_exit, false_exit),
                    AstBinaryOp::Or => (true_exit, false_exit),
                    _ => unreachable!(),
                });
                self.current_exit_label.push(lhs_exit_passdown);
                let (mut instrs, left_var) = self.generate_expr(*lhs)?;
                self.current_exit_label.pop();
                let mut left_var = left_var.unwrap();
                self.current_exit_label.push(rhs_exit_passdown);
                let (right_instrs, right_var) = self.generate_expr(*rhs)?;
                self.current_exit_label.pop();
                let mut right_var = right_var.unwrap();
                // check implicit convert
                let convert_to;
                if op.is_logical() {
                    // we dont really care since we use cmp
                    convert_to = left_var.data_type.clone();
                } else {
                    convert_to = IRType::get_elevate_result(&left_var.data_type, &right_var.data_type).unwrap();
                }
                // do implicit convert if needed
                // TODO: further check
                if !op.is_logical() && convert_to != left_var.data_type {
                    let temp_var = self.var_manager.declare_temp(convert_to.clone());
                    instrs.push(IRInstr::Move(temp_var.clone(), MoveRValue::Var(left_var)));
                    left_var = temp_var;
                }
                let result_type;
                match op {
                    AstBinaryOp::Add | AstBinaryOp::Sub | AstBinaryOp::Mul | AstBinaryOp::Div | AstBinaryOp::Mod => {
                        result_type = left_var.data_type.clone();
                    },
                    AstBinaryOp::Equal | AstBinaryOp::NotEqual | AstBinaryOp::Less | AstBinaryOp::Greater | AstBinaryOp::LessEqual | AstBinaryOp::GreaterEqual 
                    | AstBinaryOp::And | AstBinaryOp::Or => {
                        result_type = IRType::I1;
                    }
                }
                let dest_var = self.var_manager.declare_temp(result_type);
                match op {
                    AstBinaryOp::And | AstBinaryOp::Or => {
                        instrs.push(IRInstr::Label(exit.unwrap().1));
                    },
                    _ => {}
                }
                // for logical operator, considering short-circuit
                // AND: 
                //   $x = cmp ne left_var, 0
                //   bc, $x, next, false_exit
                //   next:
                //   $x = cmp eq right_var, 0
                //   bc, $x, true_exit, false_exit
                //   false_exit: #only when parent isn't logical
                //   true_exit:
                // OR:
                //   $x = cmp ne left_var, 0
                //   bc, $x, true_exit, next
                //   next:
                //   $x = cmp ne right_var, 0
                //   bc, $x, true_exit, false_exit
                //   false_exit: #only when parent isn't logical
                //   true_exit:
                instrs.extend(right_instrs);
                if !op.is_logical() && convert_to != right_var.data_type {
                    let temp_var = self.var_manager.declare_temp(convert_to);
                    instrs.push(IRInstr::Move(temp_var.clone(), MoveRValue::Var(right_var)));
                    right_var = temp_var;
                }
                if !op.is_logical() {
                    if let Some((true_exit, false_exit)) = parent_exit {
                        let final_exit = self.request_label();
                        instrs.push(IRInstr::Label(true_exit));
                        instrs.push(IRInstr::Move(dest_var.clone(), MoveRValue::ConstInt(1)));
                        instrs.push(IRInstr::Goto(final_exit));
                        instrs.push(IRInstr::Label(false_exit));
                        instrs.push(IRInstr::Move(dest_var.clone(), MoveRValue::ConstInt(0)));
                        instrs.push(IRInstr::Label(final_exit));


                    }
                }
                if op.is_logical() {
                    return Some((instrs, Some(dest_var)));
                } else {
                    if op.is_cmp() {
                        instrs.push(IRInstr::Cmp(dest_var.clone(), VariableOrIntLit::Var(left_var), op.into(), VariableOrIntLit::Var(right_var)));
                    } else {
                        instrs.push(IRInstr::Binary(dest_var.clone(), left_var, op.into(), right_var));
                    }
                }
                // if !op.is_logical() {
                //     if let Some((true_exit, false_exit))  = self.current_exit_label.last().cloned().flatten() {
                //         // parent is logical
                //         instrs.push(IRInstr::Cmp(dest_var, VariableOrIntLit::Var(dest_var), CmpOp::Ne, VariableOrIntLit::IntLit(0)));
                //         instrs.push(IRInstr::CondGoto(dest_var, true_exit, false_exit));
                //     } else {
                //         // parent isn't logical
                //         if op.is_cmp() {
                //             instrs.push(IRInstr::Cmp(dest_var, VariableOrIntLit::Var(left_var), op.into(), VariableOrIntLit::Var(right_var)));
                //         } else {
                //             instrs.push(IRInstr::Binary(dest_var, left_var, op.into(), right_var));
                //         }
                //     }
                // }
                (instrs, Some(dest_var))
            },
            HirExprValue::FuncCall(func_id, args) => {
                let mut instrs = vec![];
                let mut arg_vars = vec![];
                let func_def = self.ir_function(func_id);

                for arg in args.into_iter() {
                    self.current_exit_label.push(None);
                    let (arg_instrs, arg_var) = self.generate_expr(arg)?;
                    self.current_exit_label.pop();
                    instrs.extend(arg_instrs);
                    arg_vars.push(arg_var.unwrap());
                }
                let ret_variable = if matches!(func_def.return_type, IRType::Void) {
                    None
                } else {
                    Some(self.var_manager.declare_temp(func_def.return_type.clone()))
                };
                instrs.push(IRInstr::FuncCall(func_def.clone(), arg_vars, ret_variable.clone()));
                (instrs, ret_variable)
            }
        };
        if let Some((true_exit, false_exit)) = self.current_exit_label.last().cloned().flatten() {
            let cmp_var = self.var_manager.declare_temp(IRType::I1);
            instrs.push(IRInstr::Cmp(cmp_var.clone(), VariableOrIntLit::Var(var.clone().unwrap()), CmpOp::Ne, VariableOrIntLit::IntLit(0)));
            instrs.push(IRInstr::CondGoto(cmp_var, true_exit, false_exit));
            Some((instrs, var))
        } else {
            Some((instrs, var))
        }
    }

    fn generate_lvalue(&mut self, expr: HirExpr) -> Option<(Vec<IRInstr>, Variable, bool)> {
        match expr.value {
            HirExprValue::Var(symbol) => {
                let var = self.var_manager.get_symbol(symbol).unwrap();
                Some((vec![], var, false))
            }
            HirExprValue::ArrayAccess { array, index } => {
                let (instrs, addr) = self.generate_array_element_addr(*array, *index)?;
                Some((instrs, addr, true))
            }
            _ => None,
        }
    }

    fn generate_array_element_addr(&mut self, array: HirExpr, index: HirExpr) -> Option<(Vec<IRInstr>, Variable)> {
        let elem_ty = array.ty.indexed_type().unwrap();
        let elem_size = elem_ty.element_size_in_bytes();
        let (mut instrs, base) = match array.value {
            HirExprValue::Var(symbol) => {
                let var = self.var_manager.get_symbol(symbol).unwrap();
                if var.data_type.is_array() {
                    if var.data_type.is_array_param() {
                        (vec![], var)
                    } else {
                        let ptr = self.var_manager.declare_temp(var.data_type.decay_to_ptr().unwrap());
                        (vec![IRInstr::Move(ptr.clone(), MoveRValue::Var(var))], ptr)
                    }
                } else {
                    (vec![], var)
                }
            }
            HirExprValue::ArrayAccess { array, index } => self.generate_array_element_addr(*array, *index)?,
            _ => self.generate_expr(array).map(|(instrs, var)| (instrs, var.unwrap()))?,
        };
        self.current_exit_label.push(None);
        let (index_instrs, index_var) = self.generate_expr(index)?;
        self.current_exit_label.pop();
        instrs.extend(index_instrs);
        let addr_ty = IRType::Ptr(Box::new(elem_ty.into()));
        let dest = self.var_manager.declare_temp(addr_ty);
        let elem_size_var = self.var_manager.declare_temp(IRType::I32);
        let offset = self.var_manager.declare_temp(IRType::I32);
        instrs.push(IRInstr::Move(elem_size_var.clone(), MoveRValue::ConstInt(elem_size as i32)));
        instrs.push(IRInstr::Binary(offset.clone(), index_var.unwrap(), AstBinaryOp::Mul.into(), elem_size_var));
        instrs.push(IRInstr::Binary(dest.clone(), base, AstBinaryOp::Add.into(), offset));
        Some((instrs, dest))
    }
}

struct VariableManager {
    variable_map: BTreeMap<SymbolId, Variable>,
    global_counter: usize,
    local_counter: usize,
    local_var_type: Vec<Variable>,
}

impl VariableManager {
    pub fn new() -> Self {
        Self {
            variable_map: BTreeMap::new(),
            global_counter: 0,
            local_counter: 0,
            local_var_type: vec![],
        }
    }
    fn declare_symbol(&mut self, symbol: SymbolId, var_type: VariableType, var_data_type: IRType) -> Variable {
        let variable = match var_type {
            VariableType::Global => {
                let var = Variable { index: self.global_counter, var_type, data_type: var_data_type };
                self.global_counter += 1;
                var
            }
            VariableType::Local => {
                let var = Variable { index: self.local_counter, var_type, data_type: var_data_type };
                self.local_counter += 1;
                var
            }
            _ => unreachable!(),
        };
        self.variable_map.insert(symbol, variable.clone());
        if matches!(var_type, VariableType::Local) {
            self.local_var_type.push(variable.clone());
        }
        variable
    }
    pub fn declare_temp(&mut self, var_data_type: IRType) -> Variable {
        let var = Variable { index: self.local_counter, var_type: VariableType::Temp, data_type: var_data_type };
        self.local_counter += 1;
        self.local_var_type.push(var.clone());
        var
    }
    pub fn declare_unamed_local(&mut self, var_data_type: IRType) -> Variable {
        let var = Variable { index: self.local_counter, var_type: VariableType::Local, data_type: var_data_type };
        self.local_counter += 1;
        self.local_var_type.push(var.clone());
        var
    }
    pub fn declare_param_temp(&mut self, var_data_type: IRType, param_index: usize) -> Variable {
        let var = Variable { index: self.local_counter, var_type: VariableType::ParamTemp(param_index), data_type: var_data_type };
        self.local_counter += 1;
        self.local_var_type.push(var.clone());
        var
    }
    pub fn clear_local_counter(&mut self) {
        self.local_counter = 0;
        self.local_var_type.clear();
        self.variable_map.retain(|_, var| matches!(var.var_type, VariableType::Global));
    }
    pub fn get_cur_func_variables(&self) -> Vec<Variable> {
        self.local_var_type.iter().cloned().collect()
    }
    pub fn get_symbol(&self, symbol: SymbolId) -> Option<Variable> {
        self.variable_map.get(&symbol).cloned()
    }

}

#[cfg(test)]
mod tests {
    use std::io::BufRead;
    use std::path::Path;
    use std::fs::File;
    use crate::ast::graph::AstGraphExt;
    use std::io::Write;
    use crate::ast::parser::Parser;
    use crate::lexer::lexer::Lexer;
    use crate::sema::analyzer::Analyzer;
    use crate::utils::case_list::CaseList;
    use crate::utils::num_sequence::NumberSequence;

    pub use super::*;
    fn test_case(case_str: &str) {
        let case_sequence = NumberSequence::from_str(case_str).unwrap();
        let case_list = CaseList::from_dir(&Path::new("./testcases")).unwrap();
        let mut error_case_cnt = 0;
        for case_no in case_sequence {
            let case_path = case_list.get_case_path(case_no).unwrap();
            println!("{}", case_path.display());
            let file = File::open(&case_path).unwrap();
            let mut buf_reader = std::io::BufReader::new(file);
            let mut lexer = Lexer::new();
            let mut full_text = String::new();
            loop {
                let mut line = String::new();
                let bytes_read = buf_reader.read_line(&mut line).unwrap();
                if bytes_read == 0 {
                    break;
                }
                full_text.push_str(&line);
                lexer.parse_next_str(&line);
            }
            let (tokens, diagnostics) = lexer.finish();
            let mut is_error = false;
            if !diagnostics.is_empty() {
                diagnostics.print(&format!("{}", case_path.display()), &full_text);
                is_error = true;
            }
            let mut parser = Parser::new(tokens, diagnostics);
            let compile_unit = parser.parse();
            let case_name = case_list.get_case_name(case_no).unwrap().strip_suffix(".c").unwrap();
            if !parser.diagnostics.is_empty() {
                parser.diagnostics.print(&format!("{}", case_path.display()), &full_text);
                is_error = true;
            }
            let mut analyzer = Analyzer::new();
            let hir = analyzer.analyze(compile_unit);
            if !analyzer.get_diagnostics().is_empty() {
                analyzer.get_diagnostics().print(&format!("{}", case_path.display()), &full_text);
                is_error = true;
            }
            let mut generator = Generator::new(&analyzer);
            let ir = generator.emit(hir);
            let mut ir_file = File::create(format!("output/{}.ir", case_name)).unwrap();
            for instr in ir {
                writeln!(ir_file, "{}", instr).unwrap();
            }
            if is_error {
                error_case_cnt += 1;
            }
        }
        if error_case_cnt > 0 {
            panic!("Found {} cases with errors", error_case_cnt);
        }

    }
    #[test]
    fn test_expr() {
        test_case("0-3,14-25");
        // test_case("0-3,14-25");
    }
    #[test]
    fn test_if_while() {
        test_case("26-32,34-41,46-51,57");
    }
    #[test]
    fn test_func() {
        test_case("12-13,58-60");
    }
    #[test]
    fn test_array() {
        test_case("4-7,11,42,71,74-78,82,84,86,90,95,96,100,101,103");
    }
}
