use xenia_types::Vec128;

/// Condition register field (one of CR0-CR7).
#[derive(Debug, Clone, Copy, Default)]
pub struct CrField {
    pub lt: bool,
    pub gt: bool,
    pub eq: bool,
    pub so: bool,
}

impl CrField {
    pub fn as_u8(&self) -> u8 {
        ((self.lt as u8) << 3) | ((self.gt as u8) << 2) | ((self.eq as u8) << 1) | (self.so as u8)
    }

    pub fn from_u8(val: u8) -> Self {
        Self {
            lt: val & 8 != 0,
            gt: val & 4 != 0,
            eq: val & 2 != 0,
            so: val & 1 != 0,
        }
    }
}

/// SPR (Special Purpose Register) numbers used by mfspr/mtspr.
pub mod spr {
    pub const XER: u32 = 1;
    pub const LR: u32 = 8;
    pub const CTR: u32 = 9;
    pub const DSISR: u32 = 18;
    pub const DAR: u32 = 19;
    /// Decrementer (hypervisor-visible, 32-bit down-counter).
    pub const DEC: u32 = 22;
    pub const TBL: u32 = 268;  // Read (user)
    pub const TBU: u32 = 269;  // Read (user)
    /// Time-base write (supervisor). Separate SPR number from TBL (268) for
    /// access-control reasons.
    pub const TBL_WRITE: u32 = 284;
    pub const TBU_WRITE: u32 = 285;
    pub const SPRG0: u32 = 272;
    pub const SPRG1: u32 = 273;
    pub const SPRG2: u32 = 274;
    pub const SPRG3: u32 = 275;
    pub const VRSAVE: u32 = 256;
    pub const PVR: u32 = 287;
    pub const HID0: u32 = 1008;
    pub const HID1: u32 = 1009;
    pub const PIR: u32 = 1023;
}

/// LR halt sentinel. When `bclr` returns to this address, the interpreter
/// loop halts cleanly (matches the "entry returned" convention).
pub const LR_HALT_SENTINEL: u64 = 0xBCBC_BCBC;

/// VSCR NJ (Non-Java mode) bit. Stored in word 3 at bit 16 (mask 0x0001_0000).
/// Set at startup; when clear, denormals are flushed to zero following IEEE-754.
pub const VSCR_NJ_MASK: u32 = 0x0001_0000;
/// VSCR SAT (saturation sticky) bit. Stored in word 3 at bit 31 (mask 0x0000_0001).
pub const VSCR_SAT_MASK: u32 = 0x0000_0001;

/// PowerPC processor context. Holds all register state for one guest thread.
/// Mirrors PPCContext from ppc_context.h, minus JIT-specific fields.
// `Clone` supports the recompiler's differential harness, which snapshots a
// context to run the JIT speculatively against the interpreter. Cloning shares
// the `reservation_table` Arc; the harness clears it on the speculative copy so
// a speculative lwarx/stwcx cannot perturb the authoritative reservation state.
#[derive(Clone)]
#[repr(C, align(64))]
pub struct PpcContext {
    // General purpose registers (R0-R31)
    pub gpr: [u64; 32],
    // Count register
    pub ctr: u64,
    // Link register
    pub lr: u64,
    // Machine state register
    pub msr: u64,
    // Floating-point registers (F0-F31)
    pub fpr: [f64; 32],
    // VMX128 vector registers (V0-V127, Xbox 360 extended set)
    pub vr: [Vec128; 128],

    // Condition register fields (CR0-CR7)
    pub cr: [CrField; 8],
    // Floating-point status and control register
    pub fpscr: u32,
    // XER register (split for easy individual updates)
    pub xer_ca: u8,
    pub xer_ov: u8,
    pub xer_so: u8,
    /// XER[25:31] string-byte count (`TBC`). Read/written by `mtspr XER`,
    /// consumed by `lswx`/`stswx`. Per PPCBUG-123/124/161: was previously
    /// unmodelled, making `lswx`/`stswx` a permanent no-op.
    pub xer_tbc: u8,
    // Altivec VSCR. Only bits 16 (NJ) and 31 (SAT) of word 3 are meaningful.
    pub vscr: Vec128,
    // VRSAVE (SPR 256). Bitmask of which VRs need saving across context switches.
    pub vrsave: u32,

    // Program counter
    pub pc: u32,
    // Reservation for lwarx/ldarx/stwcx/stdcx. Xenon's reservation granule is
    // one L2 cache line (128 bytes) — `reserved_line` is stored as the base
    // address of that line (`ea & !0x7F`). `has_reservation` gates the
    // validity; stwcx./stdcx. check that both match before committing.
    // `reserved_val` is retained for possible future use by a coherency
    // observer; the store-conditional logic itself does not compare it.
    pub reserved_line: u32,
    pub reserved_val: u64,
    pub has_reservation: bool,
    /// PPCBUG-151 — width of the active reservation: 4 = `lwarx` (word),
    /// 8 = `ldarx` (doubleword), 0 = no reservation. `stwcx.` requires
    /// width==4; `stdcx.` requires width==8. Cross-width pairs fail
    /// deterministically with CR0.EQ=0. Cleared alongside `has_reservation`
    /// on every `stwcx.`/`stdcx.` exit (success or failure).
    pub reservation_width: u8,
    /// M3.7 — generation stamp returned by [`crate::ReservationTable::reserve`]
    /// at the most recent `lwarx`/`ldarx`. Paired with `reserved_line`;
    /// `stwcx.`/`stdcx.` pass this back to `try_commit`. Meaningful only
    /// when `reservation_table` is `Some` and the table is enabled.
    pub reserved_generation: u32,
    /// M3.7 — optional handle to the inter-thread reservation table.
    /// When `Some(table)` *and* `table.is_enabled()`, the interpreter's
    /// `lwarx`/`stwcx.`/`ldarx`/`stdcx.` arms route through the table;
    /// otherwise they use the legacy per-`PpcContext` fields above. The
    /// scheduler populates this when it spawns a thread under a kernel
    /// that has `reservations` set.
    pub reservation_table: Option<std::sync::Arc<crate::ReservationTable>>,
    /// M3.7 — emulated HW slot ID this thread is bound to. Used as the
    /// reservation table's `hw_id` discriminator so two threads on
    /// different slots can't accidentally commit each other's
    /// reservations. Populated by the scheduler at spawn / migration.
    pub hw_id: u8,

    // Thread ID (for kernel use)
    pub thread_id: u32,

    // Cycle counter for timing
    pub cycle_count: u64,

    // Time base (incremented each instruction for debugging)
    pub timebase: u64,

    // Decrementer (SPR 22): 32-bit down-counter that fires an external
    // interrupt at underflow on real hw. Xenia-rs doesn't dispatch DEC
    // interrupts to the guest; this value is maintained so that mfspr DEC
    // returns something coherent.
    pub dec: u32,
}

impl PpcContext {
    pub fn new() -> Self {
        Self {
            gpr: [0; 32],
            ctr: 0,
            // Canary sets LR to the halt sentinel at thread start so `blr`
            // from the top-level entry falls out of the interpreter loop.
            lr: LR_HALT_SENTINEL,
            msr: 0,
            fpr: [0.0; 32],
            vr: [Vec128::ZERO; 128],
            cr: [CrField::default(); 8],
            fpscr: 0,
            xer_ca: 0,
            xer_ov: 0,
            xer_so: 0,
            xer_tbc: 0,
            // VSCR starts with NJ bit set (denormals flushed) — matches canary
            // thread_state.cc initialization.
            vscr: Vec128::from_u32x4(0, 0, 0, VSCR_NJ_MASK),
            vrsave: 0xFFFF_FFFF,
            pc: 0,
            reserved_line: 0,
            reserved_val: 0,
            has_reservation: false,
            reservation_width: 0,
            reserved_generation: 0,
            reservation_table: None,
            hw_id: 0,
            thread_id: 0,
            cycle_count: 0,
            timebase: 0,
            dec: 0,
        }
    }

    /// Get the full 32-bit condition register.
    pub fn cr(&self) -> u32 {
        let mut val = 0u32;
        for (i, field) in self.cr.iter().enumerate() {
            val |= (field.as_u8() as u32) << (28 - i * 4);
        }
        val
    }

    /// Set the full 32-bit condition register.
    pub fn set_cr(&mut self, val: u32) {
        for i in 0..8 {
            self.cr[i] = CrField::from_u8(((val >> (28 - i * 4)) & 0xF) as u8);
        }
    }

    /// Get a single CR bit by absolute bit number (0-31).
    pub fn get_cr_bit(&self, bit: u32) -> bool {
        let field = (bit / 4) as usize;
        let sub = bit % 4;
        match sub {
            0 => self.cr[field].lt,
            1 => self.cr[field].gt,
            2 => self.cr[field].eq,
            3 => self.cr[field].so,
            _ => unreachable!(),
        }
    }

    /// Set a single CR bit by absolute bit number (0-31).
    pub fn set_cr_bit(&mut self, bit: u32, val: bool) {
        let field = (bit / 4) as usize;
        let sub = bit % 4;
        match sub {
            0 => self.cr[field].lt = val,
            1 => self.cr[field].gt = val,
            2 => self.cr[field].eq = val,
            3 => self.cr[field].so = val,
            _ => unreachable!(),
        }
    }

    /// Update a condition register field based on a comparison result (signed).
    pub fn update_cr_signed(&mut self, field: usize, val: i64) {
        self.cr[field] = CrField {
            lt: val < 0,
            gt: val > 0,
            eq: val == 0,
            so: self.xer_so != 0,
        };
    }

    /// Update a condition register field based on a comparison result (unsigned).
    pub fn update_cr_unsigned(&mut self, field: usize, a: u64, b: u64) {
        self.cr[field] = CrField {
            lt: a < b,
            gt: a > b,
            eq: a == b,
            so: self.xer_so != 0,
        };
    }

    /// Get the full XER register value.
    pub fn xer(&self) -> u32 {
        ((self.xer_so as u32) << 31)
            | ((self.xer_ov as u32) << 30)
            | ((self.xer_ca as u32) << 29)
            | (self.xer_tbc as u32)  // PPCBUG-123/566: bits 0-6 (TBC).
    }

    /// Set XER from a full 32-bit value.
    pub fn set_xer(&mut self, val: u32) {
        self.xer_so = ((val >> 31) & 1) as u8;
        self.xer_ov = ((val >> 30) & 1) as u8;
        self.xer_ca = ((val >> 29) & 1) as u8;
        self.xer_tbc = (val & 0x7F) as u8;  // PPCBUG-124.
    }

    /// Read the VSCR SAT (sticky saturation) bit.
    pub fn vscr_sat(&self) -> bool {
        (self.vscr.u32x4(3) & VSCR_SAT_MASK) != 0
    }

    /// Set or clear VSCR SAT. Preserves the NJ bit (and any other word-3 bits).
    pub fn set_vscr_sat(&mut self, v: bool) {
        let mut w = self.vscr.u32x4(3);
        if v {
            w |= VSCR_SAT_MASK;
        } else {
            w &= !VSCR_SAT_MASK;
        }
        self.vscr.set_u32x4(3, w);
    }

    /// Read the VSCR NJ (non-Java mode / flush-denormals) bit.
    pub fn vscr_nj(&self) -> bool {
        (self.vscr.u32x4(3) & VSCR_NJ_MASK) != 0
    }
}

impl Default for PpcContext {
    fn default() -> Self {
        Self::new()
    }
}
