use super::{
    basic_block::BasicBlock,
    statement::{call, IRStatement, Ret},
};
use crate::{
    ast::{self, expression::VariableRef, statement::Statement},
    ir::{quantity::Quantity, RegisterName},
    utility::data_type::{Integer, Type},
};
use std::{collections::HashMap, vec};
mod assign;
mod declare;
pub mod expression;
mod if_statement;
mod return_statement;
mod while_statement;
pub use expression::rvalue_from_ast;
pub struct SymbolTable {
    pub variable_types_stack: Vec<HashMap<VariableRef, (Type, usize)>>,
    pub next_variable_id: HashMap<VariableRef, usize>,
    pub register_type: HashMap<RegisterName, Type>,
}
impl SymbolTable {
    pub fn start_frame(&mut self) {
        self.variable_types_stack.push(HashMap::new());
    }
    pub fn end_frame(&mut self) {
        self.variable_types_stack.pop();
    }
    fn variable_id(&self, variable: &VariableRef) -> usize {
        for frame in self.variable_types_stack.iter().rev() {
            if let Some(entry) = frame.get(variable) {
                return entry.1;
            }
        }
        unreachable!()
    }
    pub fn current_variable_register(&self, variable: &VariableRef) -> RegisterName {
        RegisterName(format!("{}_{}", variable.0, self.variable_id(variable)))
    }
    pub fn current_variable_address_register(&self, variable: &VariableRef) -> RegisterName {
        RegisterName(format!(
            "{}_{}_addr",
            variable.0,
            self.variable_id(variable)
        ))
    }
    pub fn create_register_for(
        &mut self,
        variable: &VariableRef,
        data_type: &Type,
    ) -> RegisterName {
        let id = *self.next_variable_id.entry(variable.clone()).or_insert(0);
        self.next_variable_id.insert(variable.clone(), id + 1);
        let address_register_name = RegisterName(format!("{}_{}_addr", variable.0, id));
        self.variable_types_stack
            .last_mut()
            .unwrap()
            .insert(variable.clone(), (data_type.clone(), id));
        self.register_type
            .insert(address_register_name.clone(), data_type.clone());
        address_register_name
    }
    pub fn type_of_variable(&self, variable: &VariableRef) -> Type {
        self.variable_types_stack
            .iter()
            .rev()
            .find_map(|it| it.get(variable))
            .unwrap()
            .0
            .clone()
    }
}
pub struct IRGeneratingContext<'a> {
    pub parent_context: &'a mut crate::ir::IRGeneratingContext,
    pub done_basic_blocks: Vec<BasicBlock>,
    pub current_basic_block: BasicBlock,
    pub symbol_table: SymbolTable,
}
impl<'a> IRGeneratingContext<'a> {
    pub fn new(parent_context: &'a mut crate::ir::IRGeneratingContext) -> Self {
        Self {
            parent_context,
            done_basic_blocks: Vec::new(),
            current_basic_block: BasicBlock::default(),
            symbol_table: SymbolTable {
                variable_types_stack: vec![HashMap::new()],
                register_type: HashMap::new(),
                next_variable_id: HashMap::new(),
            },
        }
    }
    pub fn end_current_basic_block_with(&mut self, terminator: impl Into<IRStatement>) {
        self.current_basic_block.content.push(terminator.into());
        self.done_basic_blocks
            .push(std::mem::take(&mut self.current_basic_block));
    }
    pub fn done(mut self) -> Vec<BasicBlock> {
        if !self.current_basic_block.empty() {
            if !matches!(
                self.current_basic_block.content.last(),
                Some(IRStatement::Jump(_))
            ) && !matches!(
                self.current_basic_block.content.last(),
                Some(IRStatement::Ret(_))
            ) && !matches!(
                self.current_basic_block.content.last(),
                Some(IRStatement::Branch(_))
            ) {
                self.current_basic_block
                    .content
                    .push(Ret { value: None }.into());
            }
            self.done_basic_blocks.push(self.current_basic_block);
        }
        self.done_basic_blocks
            .into_iter()
            .filter(|it| !it.empty())
            .collect()
    }
    pub fn type_of_variable(&self, variable: &VariableRef) -> Type {
        self.symbol_table.type_of_variable(variable)
    }
    pub fn type_of_field(&self, field_access: &ast::expression::FieldAccess) -> Type {
        let ast::expression::FieldAccess { from: _, name } = field_access;
        let parent_type = match field_access.from.as_ref() {
            ast::expression::LValue::VariableRef(variable) => self.type_of_variable(variable),
            ast::expression::LValue::FieldAccess(field_access) => self.type_of_field(field_access),
        };
        match parent_type {
            Type::StructRef(s) => {
                let struct_definition = self.parent_context.type_definitions.get(&s).unwrap();
                let field_index = struct_definition.field_names.get(name).unwrap();
                struct_definition.field_types[*field_index].clone()
            }
            _ => panic!("Cannot access field from non-struct type"),
        }
    }
    pub fn type_of_quantity(&self, variable: &Quantity) -> Type {
        match variable {
            Quantity::RegisterName(name) => self.symbol_table.register_type[name].clone(),
            Quantity::GlobalVariableName(name) => self.parent_context.global_definitions[&name.0]
                .data_type
                .clone(),
            Quantity::NumberLiteral(_) => {
                Type::Integer(Integer {
                    signed: true,
                    width: 32,
                })
            }
        }
    }
    pub fn next_register_with_type(&mut self, data_type: &Type) -> RegisterName {
        let register = self.parent_context.next_register();
        self.symbol_table
            .register_type
            .insert(register.clone(), data_type.clone());
        register
    }
}
pub fn compound_from_ast(ast: &ast::statement::compound::Compound, ctx: &mut IRGeneratingContext) {
    ctx.symbol_table.start_frame();
    for statement in &ast.0 {
        match statement {
            Statement::Declare(declare) => declare::from_ast(declare, ctx),
            Statement::Assign(assign) => assign::from_ast(assign, ctx),
            Statement::Return(return_statement) => {
                return_statement::from_ast(return_statement, ctx);
                break;
            }
            Statement::If(if_statement) => if_statement::from_ast(if_statement, ctx),
            Statement::While(while_statement) => while_statement::from_ast(while_statement, ctx),
            Statement::FunctionCall(function_call) => {
                call::from_ast(&function_call.0, ctx);
            }
        }
    }
    ctx.symbol_table.end_frame();
}