use std::collections::HashMap;
use super::register_assign::{self, RegisterAssign};
use crate::ir::{
    self,
    analyzer::{self, IsAnalyzer},
    quantity::Quantity,
    statement::{IRStatement, Phi},
    RegisterName,
};
pub mod basic_block;
pub mod statement;
pub struct FunctionCompileContext<'a> {
    pub parent_context: &'a mut super::Context,
    pub local_assign: HashMap<ir::RegisterName, RegisterAssign>,
    pub cleanup_label: Option<String>,
    pub phi_constant_assign: HashMap<String, Vec<(RegisterAssign, i64)>>,
}
fn collect_phi_constant_assign(
    function: &ir::FunctionDefinition,
    register_assign: &HashMap<RegisterName, RegisterAssign>,
) -> HashMap<String, Vec<(RegisterAssign, i64)>> {
    let mut result: HashMap<String, Vec<(RegisterAssign, i64)>> = HashMap::new();
    for statement in function.iter() {
        if let IRStatement::Phi(Phi { to, from, .. }) = statement {
            for from in from {
                if let Quantity::NumberLiteral(n) = from.value {
                    result
                        .entry(from.block.clone())
                        .or_default()
                        .push((register_assign[to].clone(), n));
                }
            }
        }
    }
    result
}
pub fn emit_code(function: &ir::FunctionDefinition, ctx: &mut super::Context) -> String {
    let binding = analyzer::Analyzer::new();
    let analyzer = binding.bind(function);
    let (register_assign, stack_space) = register_assign::assign_register(ctx, function, &analyzer);
    let phi_constant_assign = collect_phi_constant_assign(function, ®ister_assign);
    let mut result = format!(
        ".global {}\n{}:\n",
        function.header.name, function.header.name
    );
    let mut context = FunctionCompileContext {
        parent_context: ctx,
        local_assign: register_assign,
        cleanup_label: if stack_space != 0 {
            Some(format!("{}_end", function.header.name))
        } else {
            None
        },
        phi_constant_assign,
    };
    if stack_space != 0 {
        result.push_str(format!("    addi sp, sp, -{stack_space}\n").as_str());
    }
    for basic_block in function.content.iter() {
        result.push_str(basic_block::emit_code(basic_block, &mut context).as_str());
    }
    if let Some(cleanup_label) = context.cleanup_label {
        result.push_str(format!("{cleanup_label}:\n").as_str());
        if stack_space != 0 {
            result.push_str(format!("    addi sp, sp, {stack_space}\n").as_str());
        }
        result.push_str("    ret\n");
    }
    result
}
#[cfg(test)]
mod tests {
    #![allow(clippy::borrow_interior_mutable_const)]
    use crate::{
        ir::{
            function::{basic_block::BasicBlock, test_util::*},
            statement::phi::PhiSource,
        },
        utility::data_type::{self, Type},
    };
    use super::*;
    #[test]
    fn test_collect_phi_constant_assign() {
        let function = ir::FunctionDefinition {
            header: ir::FunctionHeader {
                name: "f".to_string(),
                parameters: Vec::new(),
                return_type: Type::None,
            },
            content: vec![
                BasicBlock {
                    name: Some("f_entry".to_string()),
                    content: vec![branch("bb1", "bb2")],
                },
                BasicBlock {
                    name: Some("bb1".to_string()),
                    content: vec![jump("bb3")],
                },
                BasicBlock {
                    name: Some("bb2".to_string()),
                    content: vec![jump("bb3")],
                },
                BasicBlock {
                    name: Some("bb3".to_string()),
                    content: vec![Phi {
                        to: RegisterName("reg0".to_string()),
                        data_type: data_type::I32.clone(),
                        from: vec![
                            PhiSource {
                                value: 1.into(),
                                block: "bb1".to_string(),
                            },
                            PhiSource {
                                value: 2.into(),
                                block: "bb2".to_string(),
                            },
                        ],
                    }
                    .into()],
                },
            ],
        };
        let mut register_assign = HashMap::new();
        register_assign.insert(
            RegisterName("reg0".to_string()),
            RegisterAssign::Register("t0".to_string()),
        );
        let result = collect_phi_constant_assign(&function, ®ister_assign);
        let bb1_result = result.get("bb1").unwrap();
        assert_eq!(bb1_result.len(), 1);
        assert_eq!(bb1_result[0].0, RegisterAssign::Register("t0".to_string()));
        assert_eq!(bb1_result[0].1, 1);
        let bb2_result = result.get("bb2").unwrap();
        assert_eq!(bb2_result.len(), 1);
        assert_eq!(bb2_result[0].0, RegisterAssign::Register("t0".to_string()));
        assert_eq!(bb2_result[0].1, 2);
    }
}