use super::*;

const MASK_TO_ALLOWED_STATUS: &[bool] = &[true, true, true, false, true, false, false, false];

const MASK_TO_BIT_NUMBER: &[u8] = &[0, 1, 2, 2, 3, 3, 3, 3];

#[inline(always)]
const fn test_86_ms_byte(b: u8) -> bool {
    b == 0x00 || b == 0xFF
}

impl BCJFilter {
    pub(crate) fn new_x86(start_pos: usize, encoder: bool) -> Self {
        Self {
            is_encoder: encoder,
            pos: start_pos + 5,
            prev_mask: 0,
            filter: Self::x86_code,
        }
    }

    fn x86_code(&mut self, buf: &mut [u8]) -> usize {
        let len = buf.len();
        if len < 5 {
            return 0;
        }
        let end = len - 5;
        let mut prev_pos = -1;
        let mut prev_mask = self.prev_mask;
        let mut i = 0;
        while i <= end {
            let b = buf[i];
            if b != 0xE9 && b != 0xE8 {
                i += 1;
                continue;
            }
            prev_pos = i as isize - prev_pos;
            if (prev_pos & !3) != 0 {
                prev_mask = 0;
            } else {
                prev_mask = (prev_mask << (prev_pos - 1)) & 7;
                if prev_mask != 0
                    && (!MASK_TO_ALLOWED_STATUS[prev_mask as usize]
                        || test_86_ms_byte(
                            buf[i + 4 - MASK_TO_BIT_NUMBER[prev_mask as usize] as usize],
                        ))
                {
                    prev_pos = i as isize;
                    prev_mask = (prev_mask << 1) | 1;
                    i += 1;
                    continue;
                }
            }

            prev_pos = i as isize;
            if test_86_ms_byte(buf[i + 4]) {
                let mut src = buf[i + 1] as i32
                    | ((buf[i + 2] as i32) << 8)
                    | ((buf[i + 3] as i32) << 16)
                    | ((buf[i + 4] as i32) << 24);
                let mut dest: i32;
                loop {
                    if self.is_encoder {
                        dest = src + (self.pos + i) as i32;
                    } else {
                        dest = src - (self.pos + i) as i32;
                    }

                    if prev_mask == 0 {
                        break;
                    }

                    let index = MASK_TO_BIT_NUMBER[prev_mask as usize] * 8;
                    if !test_86_ms_byte(((dest >> (24 - index)) & 0xFF) as u8) {
                        break;
                    }

                    src = dest ^ ((1 << (32 - index)) - 1);
                }

                buf[i + 1] = dest as u8;
                buf[i + 2] = (dest >> 8) as u8;
                buf[i + 3] = (dest >> 16) as u8;
                buf[i + 4] = (!(((dest >> 24) & 1) - 1)) as u8;
                i += 4;
            } else {
                prev_mask = (prev_mask << 1) | 1;
            }
            i += 1;
        }

        prev_pos = i as isize - prev_pos;
        prev_mask = if (prev_pos & !3) != 0 {
            0
        } else {
            prev_mask << (prev_pos - 1)
        };

        self.prev_mask = prev_mask;
        self.pos += i;
        i
    }
}
