// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::tests::common::run_test;
use crate::tests::common::RFLAGS_LOGIC_MASK;
use iced_x86::code_asm::*;
use x86emu::Cpu;
use x86emu::Gp;

const COUNT_MASK32: u64 = 0x1f;
const COUNT_MASK64: u64 = 0x3f;

fn shift_memory_by_regvalue(
    variations: &[(u64, u64, u64, u64)],
    shift_op: impl Fn(&mut CodeAssembler, AsmMemoryOperand, AsmRegister8) -> Result<(), IcedError>,
    ptr_op: impl Fn(AsmMemoryOperand) -> AsmMemoryOperand,
    count_mask: u64,
) {
    for &(left, right, result, rflags) in variations {
        let count = (right & count_mask) as u32;
        let flags = if count == 0 {
            x86defs::RFlags::new()
        } else if count != 1 {
            let mut flags_no_of = RFLAGS_LOGIC_MASK;
            flags_no_of.set_overflow(false);
            flags_no_of
        } else {
            RFLAGS_LOGIC_MASK
        };

        let mut cpu = run_test(
            flags,
            |asm| shift_op(asm, ptr_op(rcx + 0x10), cl),
            |cpu| {
                cpu.set_gp(Gp::RCX.into(), right);
                cpu.valid_gva = cpu.gp(Gp::RCX.into()).wrapping_add(0x10);
                cpu.mem_val = left;
            },
        );

        assert_eq!(cpu.mem_val, result);
        assert_eq!(cpu.rflags() & flags, rflags.into());
    }
}

#[test]
fn shl_byte_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x40, 0x0),
        (0x64, 0x1, 0xc8, 0x880),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xfffffffffffffffe, 0x81),
        (0xffffffffffffffff, 0xf, 0xffffffffffffff00, 0x44),
        (0xffffffffffffffff, 0xff, 0xffffffffffffff00, 0x44),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x80000000, 0x44),
        (0x80000000, 0xf, 0x80000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x8000000000000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x7fffffffffffff00, 0x44),
        (0x8000000000000000, 0xff, 0x8000000000000000, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::shl, byte_ptr, COUNT_MASK32);
}

#[test]
fn shl_qword_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x64000000000, 0x4),
        (0x64, 0x1, 0xc8, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xfffffffffffffffe, 0x81),
        (0xffffffffffffffff, 0xf, 0xffffffffffff8000, 0x85),
        (0xffffffffffffffff, 0xff, 0x8000000000000000, 0x85),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x0, 0x44),
        (0x80000000, 0xf, 0x400000000000, 0x4),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x0, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x8000000000000000, 0x85),
        (0x8000000000000000, 0xff, 0x0, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::shl, qword_ptr, COUNT_MASK64);
}

#[test]
fn shr_byte_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x6, 0x4),
        (0x64, 0x1, 0x32, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xffffffffffffff7f, 0x801),
        (0xffffffffffffffff, 0xf, 0xffffffffffffff00, 0x44),
        (0xffffffffffffffff, 0xff, 0xffffffffffffff00, 0x44),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x80000000, 0x44),
        (0x80000000, 0xf, 0x80000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x8000000000000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x7fffffffffffff00, 0x44),
        (0x8000000000000000, 0xff, 0x8000000000000000, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::shr, byte_ptr, COUNT_MASK32);
}

#[test]
fn shr_qword_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x0, 0x44),
        (0x64, 0x1, 0x32, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0x7fffffffffffffff, 0x805),
        (0xffffffffffffffff, 0xf, 0x1ffffffffffff, 0x5),
        (0xffffffffffffffff, 0xff, 0x1, 0x1),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x0, 0x44),
        (0x80000000, 0xf, 0x10000, 0x4),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x1, 0x0),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x0, 0x45),
        (0x8000000000000000, 0xff, 0x1, 0x0),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::shr, qword_ptr, COUNT_MASK64);
}

#[test]
fn sar_byte_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x6, 0x4),
        (0x64, 0x1, 0x32, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xffffffffffffffff, 0x85),
        (0xffffffffffffffff, 0xf, 0xffffffffffffffff, 0x85),
        (0xffffffffffffffff, 0xff, 0xffffffffffffffff, 0x85),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x80000000, 0x44),
        (0x80000000, 0xf, 0x80000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x8000000000000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x7fffffffffffffff, 0x85),
        (0x8000000000000000, 0xff, 0x8000000000000000, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::sar, byte_ptr, COUNT_MASK32);
}

#[test]
fn sar_qword_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x0, 0x44),
        (0x64, 0x1, 0x32, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xffffffffffffffff, 0x85),
        (0xffffffffffffffff, 0xf, 0xffffffffffffffff, 0x85),
        (0xffffffffffffffff, 0xff, 0xffffffffffffffff, 0x85),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x0, 0x44),
        (0x80000000, 0xf, 0x10000, 0x4),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0xffffffffffffffff, 0x84),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x0, 0x45),
        (0x8000000000000000, 0xff, 0xffffffffffffffff, 0x84),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::sar, qword_ptr, COUNT_MASK64);
}

#[test]
fn sal_byte_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x40, 0x0),
        (0x64, 0x1, 0xc8, 0x880),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xfffffffffffffffe, 0x81),
        (0xffffffffffffffff, 0xf, 0xffffffffffffff00, 0x44),
        (0xffffffffffffffff, 0xff, 0xffffffffffffff00, 0x44),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x80000000, 0x44),
        (0x80000000, 0xf, 0x80000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x8000000000000000, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x7fffffffffffff00, 0x44),
        (0x8000000000000000, 0xff, 0x8000000000000000, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::sal, byte_ptr, COUNT_MASK32);
}

#[test]
fn sal_qword_memory_by_regvalue() {
    let variations = [
        (0x0, 0x0, 0x0, 0x0),
        (0x64, 0x64, 0x64000000000, 0x4),
        (0x64, 0x1, 0xc8, 0x0),
        (0x0, 0x1, 0x0, 0x44),
        (0x1, 0x0, 0x1, 0x0),
        (0xffffffffffffffff, 0x0, 0xffffffffffffffff, 0x0),
        (0xffffffffffffffff, 0x1, 0xfffffffffffffffe, 0x81),
        (0xffffffffffffffff, 0xf, 0xffffffffffff8000, 0x85),
        (0xffffffffffffffff, 0xff, 0x8000000000000000, 0x85),
        (0x7fffffffffffffff, 0x0, 0x7fffffffffffffff, 0x0),
        (0x7fffffff, 0x0, 0x7fffffff, 0x0),
        (0x80000000, 0x7f, 0x0, 0x44),
        (0x80000000, 0xf, 0x400000000000, 0x4),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x8000000000000000, 0x7f, 0x0, 0x44),
        (0x7fffffff, 0x80, 0x7fffffff, 0x0),
        (0x7fffffffffffffff, 0xff, 0x8000000000000000, 0x85),
        (0x8000000000000000, 0xff, 0x0, 0x44),
    ];

    shift_memory_by_regvalue(&variations, CodeAssembler::sal, qword_ptr, COUNT_MASK64);
}
