use core::fmt;

use alloc::{format, string::ToString, vec::Vec};
use serde::{Deserialize, Serialize};

use crate::{Block, Function, Inst, InstRange, MachineEnv, Operand, PRegSet, RegClass, VReg};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum InstOpcode {
    Op,
    Ret,
    Branch,
}

impl fmt::Display for InstOpcode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InstOpcode::Op => f.write_str("op"),
            InstOpcode::Ret => f.write_str("ret"),
            InstOpcode::Branch => f.write_str("branch"),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct InstData {
    op: InstOpcode,
    operands: Vec<Operand>,
    clobbers: PRegSet,
    is_safepoint: bool,
}

/// A wrapper around a `Function` and `MachineEnv` that can be serialized and
/// deserialized.
///
/// The serialized form of this structure is not stable: it is intended to be
/// deserialized with the exact same version of regalloc2 as the one that it
/// was created with.
#[derive(Serialize, Deserialize)]
pub struct SerializableFunction {
    machine_env: MachineEnv,
    entry_block: Block,
    insts: Vec<InstData>,
    blocks: Vec<InstRange>,
    block_preds: Vec<Vec<Block>>,
    block_succs: Vec<Vec<Block>>,
    block_params_in: Vec<Vec<VReg>>,
    block_params_out: Vec<Vec<Vec<VReg>>>,
    num_vregs: usize,
    reftype_vregs: Vec<VReg>,
    debug_value_labels: Vec<(VReg, Inst, Inst, u32)>,
    spillslot_size: Vec<usize>,
    multi_spillslot_named_by_last_slot: bool,
    allow_multiple_vreg_defs: bool,
}

impl SerializableFunction {
    /// Creates a new `SerializableFunction` from an arbitray `Function` and
    /// `MachineEnv`.
    pub fn new(func: &impl Function, machine_env: MachineEnv) -> Self {
        Self {
            machine_env,
            entry_block: func.entry_block(),
            insts: (0..func.num_insts())
                .map(|i| {
                    let inst = Inst::new(i);
                    let op = if func.is_ret(inst) {
                        InstOpcode::Ret
                    } else if func.is_branch(inst) {
                        InstOpcode::Branch
                    } else {
                        InstOpcode::Op
                    };
                    InstData {
                        op,
                        operands: func.inst_operands(inst).to_vec(),
                        clobbers: func.inst_clobbers(inst),
                        is_safepoint: func.requires_refs_on_stack(inst),
                    }
                })
                .collect(),
            blocks: (0..func.num_blocks())
                .map(|i| {
                    let block = Block::new(i);
                    func.block_insns(block)
                })
                .collect(),
            block_preds: (0..func.num_blocks())
                .map(|i| {
                    let block = Block::new(i);
                    func.block_preds(block).to_vec()
                })
                .collect(),
            block_succs: (0..func.num_blocks())
                .map(|i| {
                    let block = Block::new(i);
                    func.block_succs(block).to_vec()
                })
                .collect(),
            block_params_in: (0..func.num_blocks())
                .map(|i| {
                    let block = Block::new(i);
                    func.block_params(block).to_vec()
                })
                .collect(),
            block_params_out: (0..func.num_blocks())
                .map(|i| {
                    let block = Block::new(i);
                    let inst = func.block_insns(block).last();
                    (0..func.block_succs(block).len())
                        .map(|succ_idx| func.branch_blockparams(block, inst, succ_idx).to_vec())
                        .collect()
                })
                .collect(),
            num_vregs: func.num_vregs(),
            reftype_vregs: func.reftype_vregs().to_vec(),
            debug_value_labels: func.debug_value_labels().to_vec(),
            spillslot_size: [
                func.spillslot_size(RegClass::Int),
                func.spillslot_size(RegClass::Float),
                func.spillslot_size(RegClass::Vector),
            ]
            .to_vec(),
            multi_spillslot_named_by_last_slot: func.multi_spillslot_named_by_last_slot(),
            allow_multiple_vreg_defs: func.allow_multiple_vreg_defs(),
        }
    }

    /// Returns the `MachineEnv` associated with this function.
    pub fn machine_env(&self) -> &MachineEnv {
        &self.machine_env
    }
}

impl Function for SerializableFunction {
    fn num_insts(&self) -> usize {
        self.insts.len()
    }

    fn num_blocks(&self) -> usize {
        self.blocks.len()
    }

    fn entry_block(&self) -> Block {
        self.entry_block
    }

    fn block_insns(&self, block: Block) -> InstRange {
        self.blocks[block.index()]
    }

    fn block_succs(&self, block: Block) -> &[Block] {
        &self.block_succs[block.index()][..]
    }

    fn block_preds(&self, block: Block) -> &[Block] {
        &self.block_preds[block.index()][..]
    }

    fn block_params(&self, block: Block) -> &[VReg] {
        &self.block_params_in[block.index()][..]
    }

    fn is_ret(&self, insn: Inst) -> bool {
        self.insts[insn.index()].op == InstOpcode::Ret
    }

    fn is_branch(&self, insn: Inst) -> bool {
        self.insts[insn.index()].op == InstOpcode::Branch
    }

    fn branch_blockparams(&self, block: Block, _: Inst, succ: usize) -> &[VReg] {
        &self.block_params_out[block.index()][succ][..]
    }

    fn requires_refs_on_stack(&self, insn: Inst) -> bool {
        self.insts[insn.index()].is_safepoint
    }

    fn inst_operands(&self, insn: Inst) -> &[Operand] {
        &self.insts[insn.index()].operands[..]
    }

    fn inst_clobbers(&self, insn: Inst) -> PRegSet {
        self.insts[insn.index()].clobbers
    }

    fn num_vregs(&self) -> usize {
        self.num_vregs
    }

    fn reftype_vregs(&self) -> &[VReg] {
        &self.reftype_vregs[..]
    }

    fn debug_value_labels(&self) -> &[(VReg, Inst, Inst, u32)] {
        &self.debug_value_labels[..]
    }

    fn spillslot_size(&self, regclass: RegClass) -> usize {
        self.spillslot_size[regclass as usize]
    }

    fn multi_spillslot_named_by_last_slot(&self) -> bool {
        self.multi_spillslot_named_by_last_slot
    }

    fn allow_multiple_vreg_defs(&self) -> bool {
        self.allow_multiple_vreg_defs
    }
}

impl fmt::Debug for SerializableFunction {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{\n")?;
        write!(f, "  machine_env: {:#?}\n", self.machine_env())?;
        write!(
            f,
            "  spillslot_size(Int): {}\n",
            self.spillslot_size(RegClass::Int)
        )?;
        write!(
            f,
            "  spillslot_size(Float): {}\n",
            self.spillslot_size(RegClass::Float)
        )?;
        write!(
            f,
            "  spillslot_size(Vector): {}\n",
            self.spillslot_size(RegClass::Vector)
        )?;
        write!(
            f,
            "  multi_spillslot_named_by_last_slot: {}\n",
            self.multi_spillslot_named_by_last_slot()
        )?;
        write!(
            f,
            "  allow_multiple_vreg_defs: {}\n",
            self.allow_multiple_vreg_defs()
        )?;
        for vreg in self.reftype_vregs() {
            write!(f, "  REF: {}\n", vreg)?;
        }
        for (i, blockrange) in self.blocks.iter().enumerate() {
            let succs = self.block_succs[i]
                .iter()
                .map(|b| b.index())
                .collect::<Vec<_>>();
            let preds = self.block_preds[i]
                .iter()
                .map(|b| b.index())
                .collect::<Vec<_>>();
            let params_in = self.block_params_in[i]
                .iter()
                .map(|v| format!("v{}", v.vreg()))
                .collect::<Vec<_>>()
                .join(", ");
            let params_out = self.block_params_out[i]
                .iter()
                .enumerate()
                .map(|(succ_idx, vec)| {
                    let succ = self.block_succs[i][succ_idx];
                    let params = vec
                        .iter()
                        .map(|v| format!("v{}", v.vreg()))
                        .collect::<Vec<_>>()
                        .join(", ");
                    format!("block{}({})", succ.index(), params)
                })
                .collect::<Vec<_>>()
                .join(", ");
            write!(
                f,
                "  block{i}({params_in}): # succs:{succs:?} preds:{preds:?}\n",
            )?;
            for inst in blockrange.iter() {
                if self.requires_refs_on_stack(inst) {
                    write!(f, "    -- SAFEPOINT --\n")?;
                }
                let ops: Vec<_> = self
                    .inst_operands(inst)
                    .iter()
                    .map(|op| op.to_string())
                    .collect();
                let ops = ops.join(", ");
                let clobbers = if self.inst_clobbers(inst) == PRegSet::empty() {
                    format!("")
                } else {
                    let clobbers: Vec<_> = self
                        .inst_clobbers(inst)
                        .into_iter()
                        .map(|preg| format!("Clobber: {preg}"))
                        .collect();
                    format!(", {}", clobbers.join(", "))
                };
                write!(
                    f,
                    "    inst{}: {} {ops}{clobbers}\n",
                    inst.index(),
                    self.insts[inst.index()].op,
                )?;
                if let InstOpcode::Branch = self.insts[inst.index()].op {
                    write!(f, "    params: {}\n", params_out)?;
                }
            }
        }
        write!(f, "}}\n")?;
        Ok(())
    }
}
