use crate::xor_bytes::{XOR_LEN, XORBytes};

#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub struct XORIndex(usize);

impl XORIndex {
    /// Phase-aligned `XORIndex` for a buffer that conceptually starts
    /// at `offset` in the blk file.
    #[inline]
    pub fn at_offset(offset: usize) -> Self {
        Self(offset & (XOR_LEN - 1))
    }

    #[inline]
    pub(crate) fn phase(self) -> usize {
        self.0
    }

    #[inline]
    pub(crate) fn set_phase(&mut self, phase: usize) {
        self.0 = phase & (XOR_LEN - 1);
    }

    #[inline]
    pub fn add_assign(&mut self, i: usize) {
        self.0 = (self.0 + i) & (XOR_LEN - 1);
    }

    /// XOR-decode `bytes` in place, advancing the phase. Aligned 8-byte
    /// chunks XOR against the full mask in one go (auto-vectorised by
    /// LLVM); only the head/tail straddling alignment are scalar.
    pub fn bytes<'a>(&mut self, bytes: &'a mut [u8], xor_bytes: XORBytes) -> &'a mut [u8] {
        if xor_bytes.is_identity() {
            return bytes;
        }
        let xb = *xor_bytes;
        let mut phase = self.0;
        let len = bytes.len();
        let mut i = 0;

        while phase != 0 && i < len {
            bytes[i] ^= xb[phase];
            phase = (phase + 1) & (XOR_LEN - 1);
            i += 1;
        }

        let body_len = (len - i) & !(XOR_LEN - 1);
        for chunk in bytes[i..i + body_len].chunks_exact_mut(XOR_LEN) {
            for (b, m) in chunk.iter_mut().zip(xb) {
                *b ^= m;
            }
        }
        i += body_len;

        while i < len {
            bytes[i] ^= xb[phase];
            phase = (phase + 1) & (XOR_LEN - 1);
            i += 1;
        }

        self.0 = phase;
        bytes
    }

    /// XOR-decode `buffer` as if it lived at `offset` in the blk file.
    #[inline]
    pub fn decode_at(buffer: &mut [u8], offset: usize, xor_bytes: XORBytes) {
        Self::at_offset(offset).bytes(buffer, xor_bytes);
    }
}
