use crate::device;

pub fn rd(opcode: u32) -> u32 {
    (opcode >> 11) & 0x1F
}

pub fn rs(opcode: u32) -> u32 {
    (opcode >> 21) & 0x1F
}

pub fn rt(opcode: u32) -> u32 {
    (opcode >> 16) & 0x1F
}

fn sa(opcode: u32) -> u32 {
    (opcode >> 6) & 0x1F
}

pub fn imm(opcode: u32) -> u16 {
    opcode as u16
}

pub fn se32(value: i32) -> u64 {
    value as i64 as u64
}

pub fn se16(value: i16) -> u64 {
    value as i64 as u64
}

fn se8(value: i8) -> u64 {
    value as i64 as u64
}

fn bshift<T: Into<u64>>(address: T) -> u64 {
    ((address.into() & 3) ^ 3) << 3
}

fn hshift<T: Into<u64>>(address: T) -> u64 {
    ((address.into() & 2) ^ 2) << 3
}

fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
    (1 << x.into()) - 1
}

fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
    !bits_below_mask(x)
}

pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
    if imm(opcode) as i16 == -1
        && device.memory.fast_read[(device.cpu.pc_phys >> 16) as usize](
            device,
            device.cpu.pc_phys + 4,
            device::memory::AccessSize::Word,
        ) == 0
    {
        device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
    }
}

fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
    if device.cpu.pc == target
        && device.memory.fast_read[(device.cpu.pc_phys >> 16) as usize](
            device,
            device.cpu.pc_phys + 4,
            device::memory::AccessSize::Word,
        ) == 0
    {
        device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
    }
}

pub fn j(device: &mut device::Device, opcode: u32) {
    if device::cpu::in_delay_slot_taken(device) {
        return;
    }
    let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
    check_absolute_idle_loop(device, target);
    device.cpu.branch_state.state = device::cpu::State::Take;
    device.cpu.branch_state.pc = target
}

pub fn jal(device: &mut device::Device, opcode: u32) {
    if device::cpu::in_delay_slot_taken(device) {
        device.cpu.gpr[31] = device.cpu.branch_state.pc + 4
    } else {
        device.cpu.gpr[31] = device.cpu.pc + 8
    }
    if !device::cpu::in_delay_slot_taken(device) {
        let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
        check_absolute_idle_loop(device, target);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = target
    } else if !device::cpu::in_delay_slot(device) {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn beq(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] == device.cpu.gpr[rt(opcode) as usize] {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn bne(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] != device.cpu.gpr[rt(opcode) as usize] {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn blez(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] as i64 <= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn bgtz(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] as i64 > 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn addi(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        se32(device.cpu.gpr[rs(opcode) as usize] as i32).wrapping_add(se16(imm(opcode) as i16))
}

pub fn addiu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        se32(device.cpu.gpr[rs(opcode) as usize] as i32).wrapping_add(se16(imm(opcode) as i16))
}

pub fn slti(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        ((device.cpu.gpr[rs(opcode) as usize] as i64) < (imm(opcode) as i16 as i64)) as u64
}

pub fn sltiu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        (device.cpu.gpr[rs(opcode) as usize] < se16(imm(opcode) as i16)) as u64
}

pub fn andi(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] = device.cpu.gpr[rs(opcode) as usize] & imm(opcode) as u64
}

pub fn ori(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] = device.cpu.gpr[rs(opcode) as usize] | imm(opcode) as u64
}

pub fn xori(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] = device.cpu.gpr[rs(opcode) as usize] ^ imm(opcode) as u64
}

pub fn lui(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] = se32(((imm(opcode) as u32) << 16) as i32)
}

pub fn beql(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] == device.cpu.gpr[rt(opcode) as usize] {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn bnel(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] != device.cpu.gpr[rt(opcode) as usize] {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn blezl(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] as i64 <= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn bgtzl(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] as i64 > 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn daddi(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16))
}

pub fn daddiu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rt(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16))
}

pub fn ldl(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 7;
    let shift = 8 * n;

    let mask = bits_below_mask(8 * n);

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Read);
    if err {
        return;
    }
    phys_address &= !7;

    let mut w = [0; 2];
    w[0] = device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Dword,
        cached,
    );
    w[1] = device::memory::data_read(
        device,
        phys_address + 4,
        device::memory::AccessSize::Dword,
        cached,
    );
    device.cpu.gpr[rt(opcode) as usize] = (device.cpu.gpr[rt(opcode) as usize] & mask)
        | (((((w[0]) as u64) << 32) | (w[1]) as u64) << shift)
}

pub fn ldr(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 7;
    let shift = 8 * (7 - n);

    let mask = if n == 7 {
        0
    } else {
        bits_above_mask(8 * (n + 1))
    };

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Read);
    if err {
        return;
    }
    phys_address &= !7;

    let mut w = [0; 2];
    w[0] = device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Dword,
        cached,
    );
    w[1] = device::memory::data_read(
        device,
        phys_address + 4,
        device::memory::AccessSize::Dword,
        cached,
    );
    device.cpu.gpr[rt(opcode) as usize] = (device.cpu.gpr[rt(opcode) as usize] & mask)
        | (((((w[0]) as u64) << 32) | (w[1]) as u64) >> shift)
}

pub fn lb(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let shift = bshift(phys_address);
    device.cpu.gpr[rt(opcode) as usize] = se8((device::memory::data_read(
        device,
        phys_address & !0x3,
        device::memory::AccessSize::Word,
        cached,
    ) >> shift) as u8 as i8)
}

pub fn lh(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let shift = hshift(phys_address);
    device.cpu.gpr[rt(opcode) as usize] = se16(
        (device::memory::data_read(
            device,
            phys_address & !0x3,
            device::memory::AccessSize::Word,
            cached,
        ) >> shift) as u16 as i16,
    )
}

pub fn lwl(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 3;
    let shift = 8 * n;

    let mask = bits_below_mask(8 * n) as u32;

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Read);
    if err {
        return;
    }
    phys_address &= !3;

    device.cpu.gpr[rt(opcode) as usize] = se32(
        ((device.cpu.gpr[rt(opcode) as usize] as u32) & mask
            | (device::memory::data_read(
                device,
                phys_address,
                device::memory::AccessSize::Word,
                cached,
            ) << shift)) as i32,
    )
}

pub fn lw(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    device.cpu.gpr[rt(opcode) as usize] = se32(device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Word,
        cached,
    ) as i32)
}

pub fn lbu(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let shift = bshift(phys_address);
    device.cpu.gpr[rt(opcode) as usize] = (device::memory::data_read(
        device,
        phys_address & !0x3,
        device::memory::AccessSize::Word,
        cached,
    ) >> shift) as u8 as u64
}

pub fn lhu(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let shift = hshift(phys_address);
    device.cpu.gpr[rt(opcode) as usize] = (device::memory::data_read(
        device,
        phys_address & !0x3,
        device::memory::AccessSize::Word,
        cached,
    ) >> shift) as u16 as u64
}

pub fn lwr(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 3;
    let shift = 8 * (3 - n);

    let mask = if n == 3 {
        0
    } else {
        bits_above_mask(8 * (n + 1))
    };

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Read);
    if err {
        return;
    }
    phys_address &= !3;

    device.cpu.gpr[rt(opcode) as usize] = se32(
        ((device.cpu.gpr[rt(opcode) as usize] as u32) & (mask as u32)
            | (device::memory::data_read(
                device,
                phys_address,
                device::memory::AccessSize::Word,
                cached,
            ) >> shift)) as i32,
    )
}

pub fn lwu(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    device.cpu.gpr[rt(opcode) as usize] = device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Word,
        cached,
    ) as u64
}

pub fn sb(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Write,
    );
    if err {
        return;
    }

    let shift = bshift(phys_address);

    device::memory::data_write(
        device,
        phys_address & !0x3,
        (device.cpu.gpr[rt(opcode) as usize] as u32) << shift,
        0xFF << shift,
        cached,
    )
}

pub fn sh(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Write,
    );
    if err {
        return;
    }

    let shift = hshift(phys_address);

    device::memory::data_write(
        device,
        phys_address & !0x3,
        (device.cpu.gpr[rt(opcode) as usize] as u32) << shift,
        0xFFFF << shift,
        cached,
    )
}

pub fn swl(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 3;
    let shift = 8 * n;

    let mask = if n == 0 {
        u32::MAX
    } else {
        bits_below_mask(8 * (4 - n)) as u32
    };

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Write);
    if err {
        return;
    }
    phys_address &= !3;

    device::memory::data_write(
        device,
        phys_address,
        (device.cpu.gpr[rt(opcode) as usize] >> shift) as u32,
        mask,
        cached,
    )
}

pub fn sw(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Write,
    );
    if err {
        return;
    }

    device::memory::data_write(
        device,
        phys_address,
        device.cpu.gpr[rt(opcode) as usize] as u32,
        0xFFFFFFFF,
        cached,
    )
}

pub fn sdl(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 7;
    let shift = 8 * n;

    let mask = if n == 0 {
        u64::MAX
    } else {
        bits_below_mask(8 * (8 - n))
    };

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Write);
    if err {
        return;
    }
    phys_address &= !7;

    let value = device.cpu.gpr[rt(opcode) as usize] >> shift;
    device::memory::data_write(
        device,
        phys_address,
        (value >> 32) as u32,
        (mask >> 32) as u32,
        cached,
    );
    device::memory::data_write(device, phys_address + 4, value as u32, mask as u32, cached)
}

pub fn sdr(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 7;
    let shift = 8 * (7 - n);

    let mask = bits_above_mask(8 * (7 - n));

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Write);
    if err {
        return;
    }
    phys_address &= !7;

    let value = device.cpu.gpr[rt(opcode) as usize] << shift;
    device::memory::data_write(
        device,
        phys_address,
        (value >> 32) as u32,
        (mask >> 32) as u32,
        cached,
    );
    device::memory::data_write(device, phys_address + 4, value as u32, mask as u32, cached)
}

pub fn swr(device: &mut device::Device, opcode: u32) {
    let addr = device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16));
    let n = addr & 3;
    let shift = 8 * (3 - n);

    let mask = bits_above_mask(8 * (3 - n)) as u32;

    let (mut phys_address, cached, err) =
        device::memory::translate_address(device, addr, device::memory::AccessType::Write);
    if err {
        return;
    }
    phys_address &= !3;

    device::memory::data_write(
        device,
        phys_address,
        (device.cpu.gpr[rt(opcode) as usize] << shift) as u32,
        mask,
        cached,
    )
}

pub fn cache(device: &mut device::Device, opcode: u32) {
    let (phys_address, _, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let dcache_line = ((phys_address >> 4) & 0x1FF) as usize;
    let icache_line = ((phys_address >> 5) & 0x1FF) as usize;
    match rt(opcode) {
        0x00 => {
            //icache index invalidate
            device.memory.icache[icache_line].valid = false
        }
        0x04 => {
            //icache load tag
            let mut valid = 0;
            if device.memory.icache[icache_line].valid {
                valid = 1
            }
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                valid << 7,
                0x80,
            );
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                0,
                0x40,
            );
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                (device.memory.icache[icache_line].tag >> 4) as u64,
                0xFFFFF00,
            )
        }
        0x08 => {
            //icache store tag
            device.memory.icache[icache_line].valid =
                (device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
            device.memory.icache[icache_line].tag =
                ((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
        }
        0x10 => {
            //icache hit invalidate
            if device::cache::icache_hit(device, icache_line, phys_address) {
                device.memory.icache[icache_line].valid = false
            }
        }
        0x14 => {
            //icache fill
            device::cache::icache_fill(device, icache_line, phys_address)
        }
        0x18 => {
            //icache hit write back
            if device::cache::icache_hit(device, icache_line, phys_address) {
                device::cache::icache_writeback(device, icache_line)
            }
        }
        0x01 => {
            //dcache index write back invalidate
            if device.memory.dcache[dcache_line].dirty && device.memory.dcache[dcache_line].valid {
                device::cache::dcache_writeback(device, dcache_line)
            }
            device.memory.dcache[dcache_line].valid = false
        }
        0x05 => {
            //dcache index load tag
            let mut valid = 0;
            let mut dirty = 0;
            if device.memory.dcache[dcache_line].valid {
                valid = 1
            }
            if device.memory.dcache[dcache_line].dirty {
                dirty = 1
            }
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                valid << 7,
                0x80,
            );
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                dirty << 6,
                0x40,
            );
            device::memory::masked_write_64(
                &mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
                (device.memory.dcache[dcache_line].tag >> 4) as u64,
                0xFFFFF00,
            )
        }
        0x09 => {
            //dcache index store tag
            device.memory.dcache[dcache_line].valid =
                (device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
            device.memory.dcache[dcache_line].dirty =
                (device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x40) >> 6 != 0;
            device.memory.dcache[dcache_line].tag =
                ((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
        }
        0x0D => {
            //dcache create dirty exclusive
            if !device::cache::dcache_hit(device, dcache_line, phys_address)
                && device.memory.dcache[dcache_line].dirty
            {
                device::cache::dcache_writeback(device, dcache_line)
            }
            device.memory.dcache[dcache_line].tag = (phys_address & !0xFFF) as u32;
            device.memory.dcache[dcache_line].valid = true;
            device.memory.dcache[dcache_line].dirty = true
        }
        0x11 => {
            //dcache hit invalidate
            if device::cache::dcache_hit(device, dcache_line, phys_address) {
                device.memory.dcache[dcache_line].valid = false;
                device.memory.dcache[dcache_line].dirty = false
            }
        }
        0x15 => {
            //dcache hit write back invalidate
            if device::cache::dcache_hit(device, dcache_line, phys_address) {
                if device.memory.dcache[dcache_line].dirty {
                    device::cache::dcache_writeback(device, dcache_line)
                }
                device.memory.dcache[dcache_line].valid = false
            }
        }
        0x19 => {
            //dcache hit write back
            if device::cache::dcache_hit(device, dcache_line, phys_address)
                && device.memory.dcache[dcache_line].dirty
            {
                device::cache::dcache_writeback(device, dcache_line)
            }
        }
        _ => {
            panic!("unknown cache code {:x}", rt(opcode))
        }
    }
}

pub fn ll(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    device.cpu.gpr[rt(opcode) as usize] = se32(device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Word,
        cached,
    ) as i32);
    device.cpu.llbit = true;
    device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
}

pub fn lld(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let mut w = [0; 2];
    w[0] = device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Dword,
        cached,
    );
    w[1] = device::memory::data_read(
        device,
        phys_address + 4,
        device::memory::AccessSize::Dword,
        cached,
    );
    device.cpu.gpr[rt(opcode) as usize] = ((w[0] as u64) << 32) | (w[1]) as u64;

    device.cpu.llbit = true;
    device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
}

pub fn ld(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Read,
    );
    if err {
        return;
    }

    let mut w = [0; 2];
    w[0] = device::memory::data_read(
        device,
        phys_address,
        device::memory::AccessSize::Dword,
        cached,
    );
    w[1] = device::memory::data_read(
        device,
        phys_address + 4,
        device::memory::AccessSize::Dword,
        cached,
    );
    device.cpu.gpr[rt(opcode) as usize] = ((w[0] as u64) << 32) | (w[1]) as u64
}

pub fn sc(device: &mut device::Device, opcode: u32) {
    if device.cpu.llbit {
        let (phys_address, cached, err) = device::memory::translate_address(
            device,
            device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
            device::memory::AccessType::Write,
        );
        if err {
            return;
        }

        device.cpu.llbit = false;
        device::memory::data_write(
            device,
            phys_address,
            device.cpu.gpr[rt(opcode) as usize] as u32,
            0xFFFFFFFF,
            cached,
        );
        device.cpu.gpr[rt(opcode) as usize] = 1
    } else {
        device.cpu.gpr[rt(opcode) as usize] = 0
    }
}

pub fn scd(device: &mut device::Device, opcode: u32) {
    if device.cpu.llbit {
        let (phys_address, cached, err) = device::memory::translate_address(
            device,
            device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
            device::memory::AccessType::Write,
        );
        if err {
            return;
        }

        device.cpu.llbit = false;
        device::memory::data_write(
            device,
            phys_address,
            (device.cpu.gpr[rt(opcode) as usize] >> 32) as u32,
            0xFFFFFFFF,
            cached,
        );
        device::memory::data_write(
            device,
            phys_address + 4,
            device.cpu.gpr[rt(opcode) as usize] as u32,
            0xFFFFFFFF,
            cached,
        );
        device.cpu.gpr[rt(opcode) as usize] = 1
    } else {
        device.cpu.gpr[rt(opcode) as usize] = 0
    }
}

pub fn sd(device: &mut device::Device, opcode: u32) {
    let (phys_address, cached, err) = device::memory::translate_address(
        device,
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(se16(imm(opcode) as i16)),
        device::memory::AccessType::Write,
    );
    if err {
        return;
    }

    device::memory::data_write(
        device,
        phys_address,
        (device.cpu.gpr[rt(opcode) as usize] >> 32) as u32,
        0xFFFFFFFF,
        cached,
    );
    device::memory::data_write(
        device,
        phys_address + 4,
        device.cpu.gpr[rt(opcode) as usize] as u32,
        0xFFFFFFFF,
        cached,
    )
}

pub fn sll(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        se32(((device.cpu.gpr[rt(opcode) as usize] as u32) << sa(opcode)) as i32)
}

pub fn srl(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        se32(((device.cpu.gpr[rt(opcode) as usize] as u32) >> sa(opcode)) as i32)
}

pub fn sra(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        se32(((device.cpu.gpr[rt(opcode) as usize] as i64) >> sa(opcode)) as i32)
}

pub fn sllv(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        ((device.cpu.gpr[rt(opcode) as usize] as u32)
            << (device.cpu.gpr[rs(opcode) as usize] as u32 & 31)) as i32,
    )
}

pub fn srlv(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        ((device.cpu.gpr[rt(opcode) as usize] as u32)
            >> (device.cpu.gpr[rs(opcode) as usize] as u32 & 31)) as i32,
    )
}

pub fn srav(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        ((device.cpu.gpr[rt(opcode) as usize] as i64) >> (device.cpu.gpr[rs(opcode) as usize] & 31))
            as i32,
    )
}

pub fn jr(device: &mut device::Device, opcode: u32) {
    if !device::cpu::in_delay_slot_taken(device) {
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
    } else if !device::cpu::in_delay_slot(device) {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn jalr(device: &mut device::Device, opcode: u32) {
    let in_delay_slot_taken = device::cpu::in_delay_slot_taken(device);

    if !in_delay_slot_taken {
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
    } else if !device::cpu::in_delay_slot(device) {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }

    if in_delay_slot_taken {
        device.cpu.gpr[rd(opcode) as usize] = device.cpu.branch_state.pc + 4
    } else {
        device.cpu.gpr[rd(opcode) as usize] = device.cpu.pc + 8
    }
}

pub fn sync(_device: &mut device::Device, _opcode: u32) {}

pub fn mfhi(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.hi
}

pub fn mthi(device: &mut device::Device, opcode: u32) {
    device.cpu.hi = device.cpu.gpr[rs(opcode) as usize]
}

pub fn mflo(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.lo
}

pub fn mtlo(device: &mut device::Device, opcode: u32) {
    device.cpu.lo = device.cpu.gpr[rs(opcode) as usize]
}

pub fn dsllv(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rt(opcode) as usize] << (device.cpu.gpr[rs(opcode) as usize] & 63)
}

pub fn dsrlv(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rt(opcode) as usize] >> (device.cpu.gpr[rs(opcode) as usize] & 63)
}

pub fn dsrav(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = ((device.cpu.gpr[rt(opcode) as usize] as i64)
        >> (device.cpu.gpr[rs(opcode) as usize] & 63))
        as u64
}

pub fn mult(device: &mut device::Device, opcode: u32) {
    let result = ((device.cpu.gpr[rs(opcode) as usize]) as i32 as i64)
        .wrapping_mul(device.cpu.gpr[rt(opcode) as usize] as i32 as i64);

    device.cpu.lo = se32((result) as i32);
    device.cpu.hi = se32((result >> 32) as i32);

    device::cop0::add_cycles(device, 4);
}

pub fn multu(device: &mut device::Device, opcode: u32) {
    let result = (device.cpu.gpr[rs(opcode) as usize] as u32 as u64)
        .wrapping_mul(device.cpu.gpr[rt(opcode) as usize] as u32 as u64);

    device.cpu.lo = se32((result) as u32 as i32);
    device.cpu.hi = se32((result >> 32) as u32 as i32);

    device::cop0::add_cycles(device, 4);
}

pub fn div(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rt(opcode) as usize]) as i32 != 0 {
        device.cpu.lo = se32(
            (device.cpu.gpr[rs(opcode) as usize] as i32)
                .wrapping_div(device.cpu.gpr[rt(opcode) as usize] as i32),
        );
        device.cpu.hi = se32(
            (device.cpu.gpr[rs(opcode) as usize] as i32)
                .wrapping_rem(device.cpu.gpr[rt(opcode) as usize] as i32),
        );
    } else {
        if (device.cpu.gpr[rs(opcode) as usize] as i32) < 0 {
            device.cpu.lo = 1
        } else {
            device.cpu.lo = u64::MAX
        }
        device.cpu.hi = se32(device.cpu.gpr[rs(opcode) as usize] as i32)
    }

    device::cop0::add_cycles(device, 36)
}

pub fn divu(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rt(opcode) as usize]) as u32 != 0 {
        device.cpu.lo = se32(
            ((device.cpu.gpr[rs(opcode) as usize] as u32)
                / (device.cpu.gpr[rt(opcode) as usize] as u32)) as i32,
        );
        device.cpu.hi = se32(
            ((device.cpu.gpr[rs(opcode) as usize] as u32)
                % (device.cpu.gpr[rt(opcode) as usize] as u32)) as i32,
        );
    } else {
        device.cpu.lo = u64::MAX;
        device.cpu.hi = se32(device.cpu.gpr[rs(opcode) as usize] as u32 as i32)
    }

    device::cop0::add_cycles(device, 36)
}

pub fn dmult(device: &mut device::Device, opcode: u32) {
    let result = ((device.cpu.gpr[rs(opcode) as usize] as i64 as i128)
        * (device.cpu.gpr[rt(opcode) as usize] as i64 as i128)) as u128;
    device.cpu.lo = result as u64;
    device.cpu.hi = (result >> 64) as u64;
    device::cop0::add_cycles(device, 7)
}

pub fn dmultu(device: &mut device::Device, opcode: u32) {
    let result = (device.cpu.gpr[rs(opcode) as usize] as u128)
        * (device.cpu.gpr[rt(opcode) as usize] as u128);
    device.cpu.lo = result as u64;
    device.cpu.hi = (result >> 64) as u64;
    device::cop0::add_cycles(device, 7)
}

pub fn ddiv(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rt(opcode) as usize] as i64 != 0 {
        device.cpu.lo = (((device.cpu.gpr[rs(opcode) as usize] as i64) as i128)
            / (device.cpu.gpr[rt(opcode) as usize] as i64) as i128) as u64;
        device.cpu.hi = (((device.cpu.gpr[rs(opcode) as usize] as i64) as i128)
            % (device.cpu.gpr[rt(opcode) as usize] as i64) as i128) as u64;
    } else {
        if ((device.cpu.gpr[rs(opcode) as usize]) as i64) < 0 {
            device.cpu.lo = 1
        } else {
            device.cpu.lo = u64::MAX
        }
        device.cpu.hi = device.cpu.gpr[rs(opcode) as usize]
    }
    device::cop0::add_cycles(device, 68);
}

pub fn ddivu(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rt(opcode) as usize] != 0 {
        device.cpu.lo = device.cpu.gpr[rs(opcode) as usize] / device.cpu.gpr[rt(opcode) as usize];
        device.cpu.hi = device.cpu.gpr[rs(opcode) as usize] % device.cpu.gpr[rt(opcode) as usize]
    } else {
        device.cpu.lo = u64::MAX;
        device.cpu.hi = device.cpu.gpr[rs(opcode) as usize]
    }
    device::cop0::add_cycles(device, 68);
}

pub fn add(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        (device.cpu.gpr[rs(opcode) as usize] as u32)
            .wrapping_add(device.cpu.gpr[rt(opcode) as usize] as u32) as i32,
    )
}

pub fn addu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        (device.cpu.gpr[rs(opcode) as usize] as u32)
            .wrapping_add(device.cpu.gpr[rt(opcode) as usize] as u32) as i32,
    )
}

pub fn sub(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        (device.cpu.gpr[rs(opcode) as usize] as u32)
            .wrapping_sub(device.cpu.gpr[rt(opcode) as usize] as u32) as i32,
    )
}

pub fn subu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = se32(
        (device.cpu.gpr[rs(opcode) as usize] as u32)
            .wrapping_sub(device.cpu.gpr[rt(opcode) as usize] as u32) as i32,
    )
}

pub fn and(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize] & device.cpu.gpr[rt(opcode) as usize]
}

pub fn or(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize] | device.cpu.gpr[rt(opcode) as usize]
}

pub fn xor(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize] ^ device.cpu.gpr[rt(opcode) as usize]
}

pub fn nor(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        !(device.cpu.gpr[rs(opcode) as usize] | device.cpu.gpr[rt(opcode) as usize])
}

pub fn slt(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = ((device.cpu.gpr[rs(opcode) as usize] as i64)
        < (device.cpu.gpr[rt(opcode) as usize] as i64))
        as u64
}

pub fn sltu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        (device.cpu.gpr[rs(opcode) as usize] < device.cpu.gpr[rt(opcode) as usize]) as u64
}

pub fn dadd(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(device.cpu.gpr[rt(opcode) as usize])
}

pub fn daddu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_add(device.cpu.gpr[rt(opcode) as usize])
}

pub fn dsub(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_sub(device.cpu.gpr[rt(opcode) as usize])
}

pub fn dsubu(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        device.cpu.gpr[rs(opcode) as usize].wrapping_sub(device.cpu.gpr[rt(opcode) as usize])
}

pub fn tge(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) >= (device.cpu.gpr[rt(opcode) as usize] as i64)
    {
        device::exceptions::trap_exception(device)
    }
}

pub fn tgeu(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] >= device.cpu.gpr[rt(opcode) as usize] {
        device::exceptions::trap_exception(device)
    }
}

pub fn tlt(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) < (device.cpu.gpr[rt(opcode) as usize] as i64) {
        device::exceptions::trap_exception(device)
    }
}

pub fn tltu(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] < device.cpu.gpr[rt(opcode) as usize] {
        device::exceptions::trap_exception(device)
    }
}

pub fn teq(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] == device.cpu.gpr[rt(opcode) as usize] {
        device::exceptions::trap_exception(device)
    }
}

pub fn tne(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] != device.cpu.gpr[rt(opcode) as usize] {
        device::exceptions::trap_exception(device)
    }
}

pub fn dsll(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.gpr[rt(opcode) as usize] << sa(opcode)
}

pub fn dsrl(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.gpr[rt(opcode) as usize] >> sa(opcode)
}

pub fn dsra(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        ((device.cpu.gpr[rt(opcode) as usize] as i64) >> sa(opcode)) as u64
}

pub fn dsll32(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.gpr[rt(opcode) as usize] << (32 + sa(opcode))
}

pub fn dsrl32(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] = device.cpu.gpr[rt(opcode) as usize] >> (32 + sa(opcode))
}

pub fn dsra32(device: &mut device::Device, opcode: u32) {
    device.cpu.gpr[rd(opcode) as usize] =
        ((device.cpu.gpr[rt(opcode) as usize] as i64) >> (32 + sa(opcode))) as u64
}

pub fn bltz(device: &mut device::Device, opcode: u32) {
    if ((device.cpu.gpr[rs(opcode) as usize]) as i64) < 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn bgez(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) >= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
}

pub fn bltzl(device: &mut device::Device, opcode: u32) {
    if ((device.cpu.gpr[rs(opcode) as usize]) as i64) < 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn bgezl(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) >= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
}

pub fn tgei(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) >= (imm(opcode) as i16 as i64) {
        device::exceptions::trap_exception(device)
    }
}

pub fn tgeiu(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] >= se16(imm(opcode) as i16) {
        device::exceptions::trap_exception(device)
    }
}

pub fn tlti(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) < (imm(opcode) as i16 as i64) {
        device::exceptions::trap_exception(device)
    }
}

pub fn tltiu(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] < se16(imm(opcode) as i16) {
        device::exceptions::trap_exception(device)
    }
}

pub fn teqi(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) == (imm(opcode) as i16 as i64) {
        device::exceptions::trap_exception(device)
    }
}

pub fn tnei(device: &mut device::Device, opcode: u32) {
    if (device.cpu.gpr[rs(opcode) as usize] as i64) != (imm(opcode) as i16 as i64) {
        device::exceptions::trap_exception(device)
    }
}

pub fn bltzal(device: &mut device::Device, opcode: u32) {
    if ((device.cpu.gpr[rs(opcode) as usize]) as i64) < 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }
    device.cpu.gpr[31] = device.cpu.pc + 8
}

pub fn bgezal(device: &mut device::Device, opcode: u32) {
    let in_delay_slot = device::cpu::in_delay_slot(device);

    if device.cpu.gpr[rs(opcode) as usize] as i64 >= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::NotTaken;
    }

    if in_delay_slot {
        device.cpu.gpr[31] = device.cpu.branch_state.pc + 4
    } else {
        device.cpu.gpr[31] = device.cpu.pc + 8
    }
}

pub fn bltzall(device: &mut device::Device, opcode: u32) {
    if ((device.cpu.gpr[rs(opcode) as usize]) as i64) < 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
    device.cpu.gpr[31] = device.cpu.pc + 8
}

pub fn bgezall(device: &mut device::Device, opcode: u32) {
    if device.cpu.gpr[rs(opcode) as usize] as i64 >= 0 {
        check_relative_idle_loop(device, opcode);
        device.cpu.branch_state.state = device::cpu::State::Take;
        device.cpu.branch_state.pc = device.cpu.pc.wrapping_add(se16(imm(opcode) as i16) << 2) + 4;
    } else {
        device.cpu.branch_state.state = device::cpu::State::Discard;
    }
    device.cpu.gpr[31] = device.cpu.pc + 8
}
