/// All PPC opcodes supported by the Xbox 360, including VMX128 extensions.
/// Directly mirrors the C++ PPCOpcode enum from ppc_opcode.h.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
#[allow(non_camel_case_types)]
pub enum PpcOpcode {
    // ALU
    addcx, addex, addi, addic, addicx, addis, addmex, addx, addzex,
    andcx, andisx, andix, andx,
    // Branch
    bcctrx, bclrx, bcx, bx,
    // Compare
    cmp, cmpi, cmpl, cmpli,
    // Count leading zeros
    cntlzdx, cntlzwx,
    // Condition register
    crand, crandc, creqv, crnand, crnor, cror, crorc, crxor,
    // Data cache
    dcbf, dcbi, dcbst, dcbt, dcbtst, dcbz, dcbz128,
    // Division
    divdux, divdx, divwux, divwx,
    // Sync/barrier
    eieio,
    // Logical
    eqvx, extsbx, extshx, extswx,
    // FPU
    fabsx, faddsx, faddx, fcfidx, fcmpo, fcmpu, fctidx, fctidzx, fctiwx, fctiwzx,
    fdivsx, fdivx, fmaddsx, fmaddx, fmrx, fmsubsx, fmsubx, fmulsx, fmulx,
    fnabsx, fnegx, fnmaddsx, fnmaddx, fnmsubsx, fnmsubx, fresx, frspx, frsqrtex,
    fselx, fsqrtsx, fsqrtx, fsubsx, fsubx,
    // Instruction cache
    icbi, isync,
    // Load byte
    lbz, lbzu, lbzux, lbzx,
    // Load doubleword
    ld, ldarx, ldbrx, ldu, ldux, ldx,
    // Load float
    lfd, lfdu, lfdux, lfdx, lfs, lfsu, lfsux, lfsx,
    // Load halfword
    lha, lhau, lhaux, lhax, lhbrx, lhz, lhzu, lhzux, lhzx,
    // Load multiple/string
    lmw, lswi, lswx,
    // Load vector
    lvebx, lvehx, lvewx, lvewx128, lvlx, lvlx128, lvlxl, lvlxl128,
    lvrx, lvrx128, lvrxl, lvrxl128,
    lvsl, lvsl128, lvsr, lvsr128,
    lvx, lvx128, lvxl, lvxl128,
    // Load word
    lwa, lwarx, lwaux, lwax, lwbrx, lwz, lwzu, lwzux, lwzx,
    // Move CR
    mcrf, mcrfs, mcrxr,
    // Move from special
    mfcr, mffsx, mfmsr, mfspr, mftb, mfvscr,
    // Move to special
    mtcrf, mtfsb0x, mtfsb1x, mtfsfix, mtfsfx, mtmsr, mtmsrd, mtspr, mtvscr,
    // Multiply
    mulhdux, mulhdx, mulhwux, mulhwx, mulldx, mulli, mullwx,
    // Logical
    nandx, negx, norx, orcx, ori, oris, orx,
    // Rotate
    rldclx, rldcrx, rldiclx, rldicrx, rldicx, rldimix, rlwimix, rlwinmx, rlwnmx,
    // System call
    sc,
    // Shift
    sldx, slwx, sradix, sradx, srawix, srawx, srdx, srwx,
    // Store byte
    stb, stbu, stbux, stbx,
    // Store doubleword
    std, stdbrx, stdcx, stdu, stdux, stdx,
    // Store float
    stfd, stfdu, stfdux, stfdx, stfiwx, stfs, stfsu, stfsux, stfsx,
    // Store halfword
    sth, sthbrx, sthu, sthux, sthx,
    // Store multiple/string
    stmw, stswi, stswx,
    // Store vector
    stvebx, stvehx, stvewx, stvewx128, stvlx, stvlx128, stvlxl, stvlxl128,
    stvrx, stvrx128, stvrxl, stvrxl128,
    stvx, stvx128, stvxl, stvxl128,
    // Store word
    stw, stwbrx, stwcx, stwu, stwux, stwx,
    // Subtract
    subfcx, subfex, subficx, subfmex, subfx, subfzex,
    // Sync
    sync,
    // Trap
    td, tdi, tw, twi,
    // VMX integer
    vaddcuw, vaddfp, vaddfp128, vaddsbs, vaddshs, vaddsws,
    vaddubm, vaddubs, vadduhm, vadduhs, vadduwm, vadduws,
    vand, vand128, vandc, vandc128,
    vavgsb, vavgsh, vavgsw, vavgub, vavguh, vavguw,
    vcfpsxws128, vcfpuxws128, vcfsx, vcfux,
    vcmpbfp, vcmpbfp128, vcmpeqfp, vcmpeqfp128,
    vcmpequb, vcmpequh, vcmpequw, vcmpequw128,
    vcmpgefp, vcmpgefp128, vcmpgtfp, vcmpgtfp128,
    vcmpgtsb, vcmpgtsh, vcmpgtsw, vcmpgtub, vcmpgtuh, vcmpgtuw,
    vcsxwfp128, vctsxs, vctuxs, vcuxwfp128,
    vexptefp, vexptefp128, vlogefp, vlogefp128,
    vmaddcfp128, vmaddfp, vmaddfp128,
    vmaxfp, vmaxfp128, vmaxsb, vmaxsh, vmaxsw, vmaxub, vmaxuh, vmaxuw,
    vmhaddshs, vmhraddshs,
    vminfp, vminfp128, vminsb, vminsh, vminsw, vminub, vminuh, vminuw,
    vmladduhm,
    vmrghb, vmrghh, vmrghw, vmrghw128, vmrglb, vmrglh, vmrglw, vmrglw128,
    vmsum3fp128, vmsum4fp128,
    vmsummbm, vmsumshm, vmsumshs, vmsumubm, vmsumuhm, vmsumuhs,
    vmulesb, vmulesh, vmuleub, vmuleuh, vmulfp128,
    vmulosb, vmulosh, vmuloub, vmulouh,
    vnmsubfp, vnmsubfp128, vnor, vnor128,
    vor, vor128,
    vperm, vperm128, vpermwi128, vpkd3d128,
    vpkpx, vpkshss, vpkshss128, vpkshus, vpkshus128,
    vpkswss, vpkswss128, vpkswus, vpkswus128,
    vpkuhum, vpkuhum128, vpkuhus, vpkuhus128,
    vpkuwum, vpkuwum128, vpkuwus, vpkuwus128,
    vrefp, vrefp128,
    vrfim, vrfim128, vrfin, vrfin128, vrfip, vrfip128, vrfiz, vrfiz128,
    vrlb, vrlh, vrlimi128, vrlw, vrlw128,
    vrsqrtefp, vrsqrtefp128,
    vsel, vsel128,
    vsl, vslb, vsldoi, vsldoi128, vslh, vslo, vslo128, vslw, vslw128,
    vspltb, vsplth, vspltisb, vspltish, vspltisw, vspltisw128, vspltw, vspltw128,
    vsr, vsrab, vsrah, vsraw, vsraw128, vsrb, vsrh, vsro, vsro128, vsrw, vsrw128,
    vsubcuw, vsubfp, vsubfp128, vsubsbs, vsubshs, vsubsws,
    vsububm, vsububs, vsubuhm, vsubuhs, vsubuwm, vsubuws,
    vsum2sws, vsum4sbs, vsum4shs, vsum4ubs, vsumsws,
    vupkd3d128, vupkhpx, vupkhsb, vupkhsb128, vupkhsh,
    vupklpx, vupklsb, vupklsb128, vupklsh,
    vxor, vxor128,
    // XOR immediate
    xori, xoris, xorx,
    // Invalid
    Invalid,
}

impl PpcOpcode {
    /// Returns true if this opcode is a branch instruction.
    pub fn is_branch(&self) -> bool {
        matches!(self, Self::bx | Self::bcx | Self::bclrx | Self::bcctrx)
    }

    /// Returns true if this opcode is a system call.
    pub fn is_syscall(&self) -> bool {
        matches!(self, Self::sc)
    }

    /// Returns true if this opcode unconditionally ends a basic block:
    /// any branch, system call, trap, or `Invalid` (decoder couldn't
    /// recognize the instruction — execution will hit the
    /// `Unimplemented` arm and we don't want to swallow the boundary
    /// inside a cached block).
    ///
    /// Notably *not* terminating: `mtmsr`/`mtmsrd`/`isync`/`mfmsr`.
    /// On real hardware these have synchronization semantics (a context
    /// synchronizing event for `isync`, MSR rewrite for the `mt*`s) but
    /// our interpreter has no asynchronous-exception model and no
    /// out-of-order execution — they execute as plain ALU/move ops and
    /// don't change control flow synchronously. Block-cache replay is
    /// still bit-for-bit identical to per-instruction dispatch for
    /// those.
    ///
    /// Used by the basic-block cache (`block_cache.rs`) to know when to
    /// stop accumulating instructions during a forward decode walk.
    pub fn terminates_block(&self) -> bool {
        matches!(
            self,
            Self::bx | Self::bcx | Self::bclrx | Self::bcctrx
                | Self::sc
                | Self::td | Self::tdi | Self::tw | Self::twi
                | Self::Invalid
        )
    }

    /// Returns true if this is a load instruction.
    pub fn is_load(&self) -> bool {
        matches!(self,
            Self::lbz | Self::lbzu | Self::lbzux | Self::lbzx |
            Self::lhz | Self::lhzu | Self::lhzux | Self::lhzx |
            Self::lha | Self::lhau | Self::lhaux | Self::lhax |
            Self::lwz | Self::lwzu | Self::lwzux | Self::lwzx |
            Self::lwa | Self::lwax | Self::lwaux |
            Self::ld | Self::ldu | Self::ldux | Self::ldx |
            Self::lfs | Self::lfsu | Self::lfsux | Self::lfsx |
            Self::lfd | Self::lfdu | Self::lfdux | Self::lfdx |
            Self::lhbrx | Self::lwbrx | Self::ldbrx |
            Self::lmw | Self::lswi | Self::lswx |
            Self::lwarx | Self::ldarx
        )
    }

    /// Returns true if this is a store instruction.
    pub fn is_store(&self) -> bool {
        matches!(self,
            Self::stb | Self::stbu | Self::stbux | Self::stbx |
            Self::sth | Self::sthu | Self::sthux | Self::sthx |
            Self::stw | Self::stwu | Self::stwux | Self::stwx |
            Self::std | Self::stdu | Self::stdux | Self::stdx |
            Self::stfs | Self::stfsu | Self::stfsux | Self::stfsx |
            Self::stfd | Self::stfdu | Self::stfdux | Self::stfdx |
            Self::sthbrx | Self::stwbrx | Self::stdbrx |
            Self::stmw | Self::stswi | Self::stswx |
            Self::stwcx | Self::stdcx | Self::stfiwx
        )
    }

    /// Returns true if this opcode is a cross-thread synchronization
    /// point at which the superblock runner MUST yield back to the
    /// round-robin scheduler so the lockstep interleaving stays
    /// fine-grained enough to preserve correct cross-thread ordering:
    ///
    ///   - reserved load/store (`lwarx`/`ldarx`/`stwcx.`/`stdcx.`): the
    ///     atomic primitive other threads race on. Running past one
    ///     without returning to the scheduler would let a single slot
    ///     win/lose a reservation across many blocks before any peer
    ///     observes it.
    ///   - memory barriers (`sync`/`eieio`/`isync`): the guest explicitly
    ///     demands a global ordering point here; honour it by ending the
    ///     superblock so the scheduler re-interleaves.
    ///
    /// Purely a function of the opcode (no guest data), so the yield
    /// decision is deterministic and the schedule reproduces byte-identically.
    /// Note: `sc` (syscall) and traps already `terminates_block`, and
    /// import-thunk / halt-sentinel PCs are handled by the per-block
    /// prologue re-check in the superblock loop — they are not listed here.
    #[inline]
    pub fn is_sync_sensitive(&self) -> bool {
        matches!(
            self,
            Self::lwarx | Self::ldarx | Self::stwcx | Self::stdcx
                | Self::sync | Self::eieio | Self::isync
        )
    }

    pub fn name(&self) -> &'static str {
        match self {
            Self::Invalid => "invalid",
            _ => {
                // Use debug formatting to get the variant name
                // This is a placeholder - in practice we'd have a lookup table
                "?"
            }
        }
    }
}

impl std::fmt::Display for PpcOpcode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::Debug::fmt(self, f)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn terminates_block_includes_all_branches() {
        assert!(PpcOpcode::bx.terminates_block());
        assert!(PpcOpcode::bcx.terminates_block());
        assert!(PpcOpcode::bclrx.terminates_block());
        assert!(PpcOpcode::bcctrx.terminates_block());
    }

    #[test]
    fn terminates_block_includes_sc_and_traps() {
        assert!(PpcOpcode::sc.terminates_block());
        assert!(PpcOpcode::td.terminates_block());
        assert!(PpcOpcode::tdi.terminates_block());
        assert!(PpcOpcode::tw.terminates_block());
        assert!(PpcOpcode::twi.terminates_block());
    }

    #[test]
    fn terminates_block_includes_invalid() {
        // Decoder failure must end the block — otherwise an unknown
        // opcode would be replayed inside a cached block without going
        // through the per-instruction Unimplemented path.
        assert!(PpcOpcode::Invalid.terminates_block());
    }

    #[test]
    fn terminates_block_excludes_straight_line_ops() {
        // Common ALU and load/store ops must NOT terminate a block.
        assert!(!PpcOpcode::addi.terminates_block());
        assert!(!PpcOpcode::addis.terminates_block());
        assert!(!PpcOpcode::addx.terminates_block());
        assert!(!PpcOpcode::cmpi.terminates_block());
        assert!(!PpcOpcode::cmp.terminates_block());
        assert!(!PpcOpcode::lwz.terminates_block());
        assert!(!PpcOpcode::stw.terminates_block());
        assert!(!PpcOpcode::lbzx.terminates_block());
        assert!(!PpcOpcode::ori.terminates_block());
        assert!(!PpcOpcode::oris.terminates_block());
        assert!(!PpcOpcode::rlwinmx.terminates_block());
    }

    #[test]
    fn terminates_block_excludes_msr_and_sync_ops() {
        // Documented decision: synchronizing ops execute as ALU within
        // a block since the interpreter has no async-exception model.
        assert!(!PpcOpcode::mtmsr.terminates_block());
        assert!(!PpcOpcode::mtmsrd.terminates_block());
        assert!(!PpcOpcode::isync.terminates_block());
        assert!(!PpcOpcode::sync.terminates_block());
        assert!(!PpcOpcode::mfmsr.terminates_block());
    }
}
