//! TO-field evaluation for `tw`, `twi`, `td`, `tdi`.
//!
//! The TO field (5 bits) encodes which comparison outcomes trigger a trap:
//!
//! | bit | condition |
//! |-----|-----------|
//! | 0   | a <  b (signed)   |
//! | 1   | a >  b (signed)   |
//! | 2   | a == b            |
//! | 3   | a <  b (unsigned) |
//! | 4   | a >  b (unsigned) |
//!
//! The bit numbering matches PowerISA ("MSB is bit 0"): TO[0] corresponds to
//! the high bit of the 5-bit field, i.e. (to >> 4) & 1.
//!
//! `tw` / `twi` compare the low 32 bits of the operands (sign-extended back to
//! 64 for the signed comparison); `td` / `tdi` compare the full 64 bits.

#[derive(Clone, Copy, Debug)]
pub enum TrapWidth {
    Word,        // tw, twi: 32-bit
    Doubleword,  // td, tdi: 64-bit
}

const TO_SLT: u32 = 1 << 4; // a < b  signed
const TO_SGT: u32 = 1 << 3; // a > b  signed
const TO_EQ:  u32 = 1 << 2; // a == b
const TO_ULT: u32 = 1 << 1; // a < b  unsigned
const TO_UGT: u32 = 1 << 0; // a > b  unsigned

/// Returns true when the trap should fire.
pub fn evaluate(to: u32, a: u64, b: u64, width: TrapWidth) -> bool {
    let (sa, sb, ua, ub): (i64, i64, u64, u64) = match width {
        TrapWidth::Word => (
            a as i32 as i64,
            b as i32 as i64,
            a as u32 as u64,
            b as u32 as u64,
        ),
        TrapWidth::Doubleword => (a as i64, b as i64, a, b),
    };

    if (to & TO_SLT) != 0 && sa <  sb { return true; }
    if (to & TO_SGT) != 0 && sa >  sb { return true; }
    if (to & TO_EQ)  != 0 && ua == ub { return true; }
    if (to & TO_ULT) != 0 && ua <  ub { return true; }
    if (to & TO_UGT) != 0 && ua >  ub { return true; }
    false
}

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

    #[test]
    fn to_zero_never_traps() {
        assert!(!evaluate(0, 0, 0, TrapWidth::Doubleword));
        assert!(!evaluate(0, 5, 3, TrapWidth::Doubleword));
        assert!(!evaluate(0, !0, 0, TrapWidth::Doubleword));
    }

    #[test]
    fn to_31_always_traps_when_any_condition_holds() {
        // 31 = 0b11111 = all conditions enabled
        assert!(evaluate(31, 1, 2, TrapWidth::Doubleword)); // slt+ult
        assert!(evaluate(31, 2, 1, TrapWidth::Doubleword)); // sgt+ugt
        assert!(evaluate(31, 7, 7, TrapWidth::Doubleword)); // eq
    }

    #[test]
    fn to_eq_only() {
        // TO[2] = 0b00100 = 4
        assert!(evaluate(4, 5, 5, TrapWidth::Doubleword));
        assert!(!evaluate(4, 5, 6, TrapWidth::Doubleword));
    }

    #[test]
    fn to_signed_vs_unsigned_on_negative() {
        // a=-1 (as u64 = all-ones). TO[0]=slt enabled = 0b10000 = 16
        // Signed: -1 < 0  → true
        let neg1 = (-1i64) as u64;
        assert!(evaluate(16, neg1, 0, TrapWidth::Doubleword));
        // TO[3]=ult enabled = 0b00010 = 2 → unsigned: all-ones < 0 is false
        assert!(!evaluate(2, neg1, 0, TrapWidth::Doubleword));
    }

    #[test]
    fn word_width_ignores_high_32_bits() {
        // a's low 32 = 1, high 32 = different; b = 1. With TO=eq, should trap.
        let a = 0xDEAD_BEEF_0000_0001u64;
        assert!(evaluate(4, a, 1, TrapWidth::Word));
        // In doubleword, different.
        assert!(!evaluate(4, a, 1, TrapWidth::Doubleword));
    }
}
