mod basic;
mod calls;
mod control_flow;
mod phi;
mod weak;

use crate::mir::MirInstruction as I;

pub(crate) fn emit_phi_instructions(
    func: &crate::mir::MirFunction,
    block: &crate::mir::BasicBlock,
) -> Vec<serde_json::Value> {
    let mut insts = Vec::new();
    for inst in &block.instructions {
        if let I::Phi { .. } = inst {
            if let Some(value) = phi::emit_phi(inst, &func.metadata.value_types) {
                insts.push(value);
            }
        }
    }
    insts
}

pub(crate) fn emit_non_phi_instructions(
    func: &crate::mir::MirFunction,
    block: &crate::mir::BasicBlock,
    insts: &mut Vec<serde_json::Value>,
) {
    for inst in &block.instructions {
        if let I::Phi { .. } = inst {
            continue;
        }
        if let Some(value) = emit_instruction(func, inst) {
            insts.push(value);
        }
    }
}

pub(crate) fn emit_terminator(
    terminator: &Option<crate::mir::MirInstruction>,
) -> Option<serde_json::Value> {
    terminator.as_ref().and_then(control_flow::emit_terminator)
}

fn emit_instruction(
    func: &crate::mir::MirFunction,
    inst: &crate::mir::MirInstruction,
) -> Option<serde_json::Value> {
    match inst {
        I::Copy { dst, src } => Some(basic::emit_copy(dst, src)),
        I::UnaryOp { dst, op, operand } => Some(basic::emit_unary_op(dst, op, operand)),
        I::Const { dst, value } => Some(basic::emit_const(dst, value)),
        I::TypeOp { dst, op, value, ty } => {
            Some(basic::emit_type_op(dst, op, value, ty))
        }
        I::BinOp { dst, op, lhs, rhs } => Some(basic::emit_bin_op(
            dst,
            op,
            lhs,
            rhs,
            &func.metadata.value_types,
        )),
        I::Compare { dst, op, lhs, rhs } => Some(basic::emit_compare(
            dst,
            op,
            lhs,
            rhs,
            &func.metadata.value_types,
        )),
        I::Call {
            dst,
            func,
            callee,
            args,
            effects,
            ..
        } => calls::emit_call(dst, func, callee.as_ref(), args, effects),
        I::ExternCall {
            dst,
            iface_name,
            method_name,
            args,
            ..
        } => Some(calls::emit_extern_call(
            dst,
            iface_name,
            method_name,
            args,
        )),
        I::BoxCall {
            dst,
            box_val,
            method,
            method_id,
            args,
            ..
        } => Some(calls::emit_box_call(
            dst,
            box_val,
            method,
            method_id.as_ref(),
            args,
        )),
        I::NewBox {
            dst,
            box_type,
            args,
        } => Some(calls::emit_new_box(dst, box_type, args)),
        I::Branch {
            condition,
            then_bb,
            else_bb,
            ..
        } => Some(control_flow::emit_branch(condition, then_bb, else_bb)),
        I::Jump { target, .. } => Some(control_flow::emit_jump(target)),
        I::Return { value } => Some(control_flow::emit_return(value.as_ref())),
        I::WeakRef { dst, op, value } => Some(weak::emit_weak_ref(dst, op, value)),
        I::WeakNew { dst, box_val } => Some(weak::emit_weak_new(dst, box_val)),
        I::WeakLoad { dst, weak_ref } => Some(weak::emit_weak_load(dst, weak_ref)),
        I::KeepAlive { values } => Some(weak::emit_keep_alive(values)),
        I::ReleaseStrong { values } => Some(weak::emit_release_strong(values)),
        _ => None,
    }
}
