use crate::assembler::Assembler;
use crate::utils::DataType;

pub enum RoundingMode {
    Round,
    Floor,
    Ceiling,
    Trunc,
}

pub struct Amd {
    pub a: Assembler,
    pub dtype: DataType,
}

#[allow(dead_code)]
impl Amd {
    pub fn new(dtype: DataType) -> Amd {
        Amd {
            a: Assembler::new(),
            dtype,
        }
    }

    pub const RAX: u8 = 0;
    pub const RCX: u8 = 1;
    pub const RDX: u8 = 2;
    pub const RBX: u8 = 3;
    pub const RSP: u8 = 4;
    pub const RBP: u8 = 5;
    pub const RSI: u8 = 6;
    pub const RDI: u8 = 7;
    pub const R8: u8 = 8;
    pub const R9: u8 = 9;
    pub const R10: u8 = 10;
    pub const R11: u8 = 11;
    pub const R12: u8 = 12;
    pub const R13: u8 = 13;
    pub const R14: u8 = 14;
    pub const R15: u8 = 15;

    pub fn bytes(&self) -> Vec<u8> {
        self.a.bytes()
    }

    pub fn append_byte(&mut self, b: u8) {
        self.a.append_byte(b)
    }

    pub fn append_bytes(&mut self, bs: &[u8]) {
        self.a.append_bytes(bs)
    }

    pub fn append_word(&mut self, u: u32) {
        self.a.append_word(u)
    }

    pub fn append_quad(&mut self, u: u64) {
        self.a.append_quad(u)
    }

    pub fn jump(&mut self, label: &str) {
        self.a.jump(label, 0, |offset, _| (offset - 4) as u32);
    }

    pub fn modrm_reg(&mut self, reg: u8, rm: u8) {
        self.append_byte(0xc0 + ((reg & 7) << 3) + (rm & 7))
    }

    pub fn modrm_sib(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.append_byte(0x04 + ((reg & 7) << 3)); // R/M = 0b100, MOD = 0b00
        let scale = match scale {
            1 => 0,
            2 => 1 << 6,
            4 => 2 << 6,
            8 => 3 << 6,
            _ => {
                panic!("scale in SIB should be 1, 2, 4, or, 8.")
            }
        };
        self.append_byte((scale | (index & 7) << 3) | (base & 7));
    }

    pub fn rex(&mut self, reg: u8, rm: u8) {
        let b = 0x48 + ((rm & 8) >> 3) + ((reg & 8) >> 1);
        self.append_byte(b);
    }

    pub fn rex_index(&mut self, reg: u8, rm: u8, index: u8) {
        let b = 0x48 + ((rm & 8) >> 3) + ((index & 8) >> 2) + ((reg & 8) >> 1);
        self.append_byte(b);
    }

    pub fn modrm_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        let small = (-128..128).contains(&offset);

        if small {
            self.append_byte(0x40 + ((reg & 7) << 3) + (rm & 7))
        } else {
            self.append_byte(0x80 + ((reg & 7) << 3) + (rm & 7))
        }

        if rm == Self::RSP {
            self.append_byte(0x24); // SIB byte for RSP
        }

        if small {
            self.append_byte(offset as u8);
        } else {
            self.append_word(offset as u32);
        }
    }

    pub fn vex2pd(&mut self, reg: u8, vreg: u8) {
        // This is the two-byte VEX prefix (VEX2) for packed-double (pd)
        // and 256-bit ymm registers
        let r = (!reg & 8) << 4;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc5);
        self.append_byte(r | vvvv | 4 | pp);
    }

    pub fn vex2sd(&mut self, reg: u8, vreg: u8) {
        // This is the two-byte VEX prefix (VEX2) for scalar-double (sd)
        // and 256-bit ymm registers
        let r = (!reg & 8) << 4;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 2, // ss
            DataType::F64 => 3, // sd
        };

        self.append_byte(0xc5);
        self.append_byte(r | vvvv | pp);
    }

    pub fn vex3pd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8, encoding: u8) {
        // This is the three-byte VEX prefix (VEX3) for packed-double (pd)
        // and 256-bit ymm registers
        // fnault encoding is 1
        let r = (!reg & 8) << 4;
        let x = (!index & 8) << 3;
        let b = (!rm & 8) << 2;
        let w = 0;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc4);
        self.append_byte(r | x | b | encoding);
        self.append_byte(w | vvvv | 4 | pp);
    }

    pub fn vex3pd_w1(&mut self, reg: u8, vreg: u8, rm: u8, index: u8, encoding: u8) {
        // This is the three-byte VEX prefix (VEX3) for packed-double (pd)
        // and 256-bit ymm registers
        // fnault encoding is 1
        let r = (!reg & 8) << 4;
        let x = (!index & 8) << 3;
        let b = (!rm & 8) << 2;
        let w = 0x80;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc4);
        self.append_byte(r | x | b | encoding);
        self.append_byte(w | vvvv | 4 | pp);
    }

    pub fn vex3sd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8, encoding: u8) {
        // This is the three-byte VEX prefix (VEX3) for scalar-double (sd)
        // and 256-bit ymm registers
        // default encoding is 1
        let r = (!reg & 8) << 4;
        let x = (!index & 8) << 3;
        let b = (!rm & 8) << 2;
        let w = 0;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 2, // ss
            DataType::F64 => 3, // sd
        };

        self.append_byte(0xc4);
        self.append_byte(r | x | b | encoding);
        self.append_byte(w | vvvv | pp);
    }

    pub fn vex_sd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8) {
        if rm < 8 && index < 8 {
            self.vex2sd(reg, vreg);
        } else {
            self.vex3sd(reg, vreg, rm, index, 1);
        }
    }

    pub fn vex_pd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8) {
        if rm < 8 && index < 8 {
            self.vex2pd(reg, vreg);
        } else {
            self.vex3pd(reg, vreg, rm, index, 1);
        }
    }

    pub fn vex2dd(&mut self, reg: u8, vreg: u8) {
        // This is the two-byte VEX prefix (VEX2) for packed-double (pd)
        // and 128-bit xmm registers
        let r = (!reg & 8) << 4;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc5);
        self.append_byte(r | vvvv | pp);
    }

    pub fn vex3dd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8, encoding: u8) {
        // This is the three-byte VEX prefix (VEX3) for packed-double (pd)
        // and 128-bit xmm registers
        // default encoding is 1
        let r = (!reg & 8) << 4;
        let x = (!index & 8) << 3;
        let b = (!rm & 8) << 2;
        let w = 0;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc4);
        self.append_byte(r | x | b | encoding);
        self.append_byte(w | vvvv | pp);
    }

    pub fn vex3dd_w1(&mut self, reg: u8, vreg: u8, rm: u8, index: u8, encoding: u8) {
        // This is the three-byte VEX prefix (VEX3) for packed-double (pd)
        // and 128-bit xmm registers
        // default encoding is 1
        let r = (!reg & 8) << 4;
        let x = (!index & 8) << 3;
        let b = (!rm & 8) << 2;
        let w = 0x80;
        let vvvv = (!vreg & 0x0f) << 3;

        let pp = match self.dtype {
            DataType::F32 => 0, // ps
            DataType::F64 => 1, // pd
        };

        self.append_byte(0xc4);
        self.append_byte(r | x | b | encoding);
        self.append_byte(w | vvvv | pp);
    }

    pub fn vex_dd(&mut self, reg: u8, vreg: u8, rm: u8, index: u8) {
        if rm < 8 && index < 8 {
            self.vex2dd(reg, vreg);
        } else {
            self.vex3dd(reg, vreg, rm, index, 1);
        }
    }

    pub fn sse_sd(&mut self, reg: u8, rm: u8) {
        match self.dtype {
            DataType::F32 => self.append_byte(0xf3), // ss
            DataType::F64 => self.append_byte(0xf2), // sd
        };

        self.rex(reg, rm);
        self.append_byte(0x0f);
    }

    pub fn sse_sd_index(&mut self, reg: u8, rm: u8, index: u8) {
        match self.dtype {
            DataType::F32 => self.append_byte(0xf3), // ss
            DataType::F64 => self.append_byte(0xf2), // sd
        };

        self.rex_index(reg, rm, index);
        self.append_byte(0x0f);
    }

    pub fn sse_pd(&mut self, reg: u8, rm: u8) {
        match self.dtype {
            DataType::F32 => {}                      // ps
            DataType::F64 => self.append_byte(0x66), // pd
        };

        self.rex(reg, rm);
        self.append_byte(0x0f);
    }

    // AVX rules!
    pub fn vmovapd(&mut self, reg: u8, rm: u8) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x28);
        self.modrm_reg(reg, rm);
    }

    /******************* scalar double ******************/
    pub fn vmovsd_xmm_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.vex_sd(reg, 0, rm, 0);
        self.append_byte(0x10);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovsd_xmm_indexed(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.vex_sd(reg, 0, base, index);
        self.append_byte(0x10);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vmovsd_xmm_label(&mut self, reg: u8, label: &str) {
        self.vex_sd(reg, 0, 0, 0);
        self.append_byte(0x10);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn vmovsd_mem_xmm(&mut self, rm: u8, offset: i32, reg: u8) {
        self.vex_sd(reg, 0, rm, 0);
        self.append_byte(0x11);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovsd_indexed_xmm(&mut self, base: u8, index: u8, scale: u8, reg: u8) {
        self.vex_sd(reg, 0, base, index);
        self.append_byte(0x11);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vaddsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0x58);
        self.modrm_reg(reg, rm);
    }

    pub fn vsubsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0x5c);
        self.modrm_reg(reg, rm);
    }

    pub fn vmulsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0x59);
        self.modrm_reg(reg, rm);
    }

    pub fn vdivsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0x5e);
        self.modrm_reg(reg, rm);
    }

    pub fn vsqrtsd(&mut self, reg: u8, rm: u8) {
        self.vex_sd(reg, 0, rm, 0);
        self.append_byte(0x51);
        self.modrm_reg(reg, rm);
    }

    pub fn vroundsd(&mut self, reg: u8, rm: u8, mode: RoundingMode) {
        self.vex3pd(reg, reg, rm, 0, 3);
        self.append_byte(0x0b);
        self.modrm_reg(reg, rm);
        self.append_byte(match mode {
            RoundingMode::Round => 0,
            RoundingMode::Floor => 1,
            RoundingMode::Ceiling => 2,
            RoundingMode::Trunc => 3,
        });
    }

    pub fn vcmpeqsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(0)
    }

    pub fn vcmpltsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(1);
    }

    pub fn vcmplesd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(2);
    }

    pub fn vcmpunordsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(3);
    }

    pub fn vcmpneqsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(4);
    }

    pub fn vcmpnltsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(5);
    }

    pub fn vcmpnlesd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(6);
    }

    pub fn vcmpordsd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_sd(reg, vreg, rm, 0);
        self.append_byte(0xC2);
        self.modrm_reg(reg, rm);
        self.append_byte(7);
    }

    pub fn vucomisd(&mut self, reg: u8, rm: u8) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x2e);
        self.modrm_reg(reg, rm);
    }

    /******************* packed double ******************/
    pub fn vbroadcastsd(&mut self, reg: u8, rm: u8, offset: i32) {
        self.vex3pd(reg, 0, rm, 0, 2);
        self.append_byte(0x19);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vbroadcastsd_label(&mut self, reg: u8, label: &str) {
        self.vex3pd(reg, 0, 0, 0, 2);
        self.append_byte(0x19);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn vmovpd_ymm_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x10);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovpd_ymm_indexed(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.vex_pd(reg, 0, base, index);
        self.append_byte(0x10);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vmovpd_ymm_label(&mut self, reg: u8, label: &str) {
        self.vex_pd(reg, 0, 0, 0);
        self.append_byte(0x10);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn vmovpd_mem_ymm(&mut self, rm: u8, offset: i32, reg: u8) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x11);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovpd_indexed_ymm(&mut self, base: u8, index: u8, scale: u8, reg: u8) {
        self.vex_pd(reg, 0, base, index);
        self.append_byte(0x11);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vaddpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x58);
        self.modrm_reg(reg, rm);
    }

    pub fn vsubpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x5c);
        self.modrm_reg(reg, rm);
    }

    pub fn vmulpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x59);
        self.modrm_reg(reg, rm);
    }

    pub fn vdivpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x5e);
        self.modrm_reg(reg, rm);
    }

    pub fn vsqrtpd(&mut self, reg: u8, rm: u8) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x51);
        self.modrm_reg(reg, rm);
    }

    pub fn vroundpd(&mut self, reg: u8, rm: u8, mode: RoundingMode) {
        self.vex3pd(reg, 0, rm, 0, 3);
        self.append_byte(0x09);
        self.modrm_reg(reg, rm);
        self.append_byte(match mode {
            RoundingMode::Round => 0,
            RoundingMode::Floor => 1,
            RoundingMode::Ceiling => 2,
            RoundingMode::Trunc => 3,
        });
    }

    pub fn vandpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x54);
        self.modrm_reg(reg, rm);
    }

    pub fn vandnpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x55);
        self.modrm_reg(reg, rm);
    }

    pub fn vorpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x56);
        self.modrm_reg(reg, rm);
    }

    pub fn vxorpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x57);
        self.modrm_reg(reg, rm);
    }

    pub fn vcmpeqpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(0)
    }

    pub fn vcmpltpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(1);
    }

    pub fn vcmplepd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(2);
    }

    pub fn vcmpunordpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(3);
    }

    pub fn vcmpneqpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(4);
    }

    pub fn vcmpnltpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(5);
    }

    pub fn vcmpnlepd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(6);
    }

    pub fn vcmpordpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(7);
    }

    /******************* packed double xmm *******************/

    pub fn vbroadcastdd(&mut self, reg: u8, rm: u8, offset: i32) {
        self.vex3dd(reg, 0, rm, 0, 2);
        self.append_byte(0x19);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vbroadcastdd_label(&mut self, reg: u8, label: &str) {
        self.vex3dd(reg, 0, 0, 0, 2);
        self.append_byte(0x19);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn vmovdd_xmm_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.vex_dd(reg, 0, rm, 0);
        self.append_byte(0x10);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovdd_xmm_indexed(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.vex_dd(reg, 0, base, index);
        self.append_byte(0x10);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vmovdd_xmm_label(&mut self, reg: u8, label: &str) {
        self.vex_dd(reg, 0, 0, 0);
        self.append_byte(0x10);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn vmovdd_mem_xmm(&mut self, rm: u8, offset: i32, reg: u8) {
        self.vex_dd(reg, 0, rm, 0);
        self.append_byte(0x11);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn vmovdd_indexed_xmm(&mut self, base: u8, index: u8, scale: u8, reg: u8) {
        self.vex_dd(reg, 0, base, index);
        self.append_byte(0x11);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn vadddd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x58);
        self.modrm_reg(reg, rm);
    }

    pub fn vhadddd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x7c);
        self.modrm_reg(reg, rm);
    }

    pub fn vsubdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x5c);
        self.modrm_reg(reg, rm);
    }

    pub fn vmuldd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x59);
        self.modrm_reg(reg, rm);
    }

    pub fn vdivdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x5e);
        self.modrm_reg(reg, rm);
    }

    pub fn vsqrtdd(&mut self, reg: u8, rm: u8) {
        self.vex_dd(reg, 0, rm, 0);
        self.append_byte(0x51);
        self.modrm_reg(reg, rm);
    }

    pub fn vrounddd(&mut self, reg: u8, rm: u8, mode: RoundingMode) {
        self.vex3dd(reg, 0, rm, 0, 3);
        self.append_byte(0x09);
        self.modrm_reg(reg, rm);
        self.append_byte(match mode {
            RoundingMode::Round => 0,
            RoundingMode::Floor => 1,
            RoundingMode::Ceiling => 2,
            RoundingMode::Trunc => 3,
        });
    }

    pub fn vanddd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x54);
        self.modrm_reg(reg, rm);
    }

    pub fn vandndd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x55);
        self.modrm_reg(reg, rm);
    }

    pub fn vordd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x56);
        self.modrm_reg(reg, rm);
    }

    pub fn vxordd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x57);
        self.modrm_reg(reg, rm);
    }

    pub fn vcmpeqdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(0)
    }

    pub fn vcmpltdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(1);
    }

    pub fn vcmpledd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(2);
    }

    pub fn vcmpunorddd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(3);
    }

    pub fn vcmpneqdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(4);
    }

    pub fn vcmpnltdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(5);
    }

    pub fn vcmpnledd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(6);
    }

    pub fn vcmporddd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(7);
    }

    /*
     * let xmm1 = y1:x1 and xmm2 = y2:x2,
     *
     * vshufdd(0, 1, 2, 0) = vshufpd xmm0, xmm1, xmm2, 0 => xmm0 = x2:x1 = vunpcklpd
     * vshufdd(0, 1, 2, 1) = vshufpd xmm0, xmm1, xmm2, 1 => xmm0 = x2:y1
     * vshufdd(0, 1, 2, 2) = vshufpd xmm0, xmm1, xmm2, 2 => xmm0 = y2:x1
     * vshufdd(0, 1, 2, 3) = vshufpd xmm0, xmm1, xmm2, 1 => xmm0 = y2:y1 = vunpckhpd
     *
     * Specifically,
     *
     * vshufdd(0, 1, 1, 0) = vshufpd xmm0, xmm1, xmm1, 0 => xmm0 = x1:x1 = dup low
     * vshufdd(0, 1, 1, 1) = vshufpd xmm0, xmm1, xmm1, 1 => xmm0 = x1:y1 = flip
     * vshufdd(0, 1, 1, 2) = vshufpd xmm0, xmm1, xmm1, 2 => xmm0 = y1:x1 = ident
     * vshufdd(0, 1, 1, 3) = vshufpd xmm0, xmm1, xmm1, 3 => xmm0 = y1:y1 = dup high
     *
     */

    pub fn vshufdd(&mut self, reg: u8, vreg: u8, rm: u8, imm8: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xc6);
        self.modrm_reg(reg, rm);
        self.append_byte(imm8);
    }

    pub fn vunpckhdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x15);
        self.modrm_reg(reg, rm);
    }

    pub fn vunpckldd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0x14);
        self.modrm_reg(reg, rm);
    }

    pub fn vaddsubdd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_dd(reg, vreg, rm, 0);
        self.append_byte(0xd0);
        self.modrm_reg(reg, rm);
    }

    pub fn vshufpd(&mut self, reg: u8, vreg: u8, rm: u8, imm8: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xc6);
        self.modrm_reg(reg, rm);
        self.append_byte(imm8);
    }

    pub fn vunpckhpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x15);
        self.modrm_reg(reg, rm);
    }

    pub fn vunpcklpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0x14);
        self.modrm_reg(reg, rm);
    }

    pub fn vaddsubpd(&mut self, reg: u8, vreg: u8, rm: u8) {
        self.vex_pd(reg, vreg, rm, 0);
        self.append_byte(0xd0);
        self.modrm_reg(reg, rm);
    }

    // imm8 == 0 => reg = vreg[128:256]:rm[0:128]
    // imm8 == 1 => reg = rm[128:256]:vreg[0:128]
    pub fn vinsertf128(&mut self, reg: u8, vreg: u8, rm: u8, imm8: u8) {
        self.vex3pd(reg, vreg, rm, 0, 3);
        self.append_byte(0x18);
        self.modrm_reg(reg, rm);
        self.append_byte(imm8);
    }

    pub fn vinsertf128_mem(&mut self, reg: u8, vreg: u8, rm: u8, offset: i32, imm8: u8) {
        self.vex3pd(reg, vreg, rm, 0, 3);
        self.append_byte(0x18);
        self.modrm_mem(reg, rm, offset);
        self.append_byte(imm8);
    }

    // imm8 == 0 => rm = reg[0:128]
    // imm8 == 1 => rm = reg[128:256]
    pub fn vextractf128(&mut self, rm: u8, reg: u8, imm8: u8) {
        self.vex3pd(reg, 0, rm, 0, 3);
        self.append_byte(0x19);
        self.modrm_reg(reg, rm);
        self.append_byte(imm8);
    }

    /******************* SSE scalar double ******************/
    pub fn movapd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x28);
        self.modrm_reg(reg, rm);
    }

    pub fn movsd_xmm_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.sse_sd(reg, rm);
        self.append_byte(0x10);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn movsd_xmm_indexed(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.sse_sd_index(reg, base, index);
        self.append_byte(0x10);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn movsd_xmm_label(&mut self, reg: u8, label: &str) {
        self.sse_sd(reg, 0);
        self.append_byte(0x10);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn movsd_mem_xmm(&mut self, rm: u8, offset: i32, reg: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x11);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn movsd_indexed_xmm(&mut self, base: u8, index: u8, scale: u8, reg: u8) {
        self.sse_sd_index(reg, base, index);
        self.append_byte(0x11);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn addsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x58);
        self.modrm_reg(reg, rm);
    }

    pub fn subsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x5c);
        self.modrm_reg(reg, rm);
    }

    pub fn mulsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x59);
        self.modrm_reg(reg, rm);
    }

    pub fn divsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x5e);
        self.modrm_reg(reg, rm);
    }

    pub fn sqrtsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0x51);
        self.modrm_reg(reg, rm);
    }

    pub fn roundsd(&mut self, reg: u8, rm: u8, mode: RoundingMode) {
        self.sse_pd(reg, rm);
        self.append_bytes(&[0x3a, 0x0b]);
        self.modrm_reg(reg, rm);
        self.append_byte(match mode {
            RoundingMode::Round => 0,
            RoundingMode::Floor => 1,
            RoundingMode::Ceiling => 2,
            RoundingMode::Trunc => 3,
        });
    }

    pub fn cmpeqsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(0)
    }

    pub fn cmpltsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(1);
    }

    pub fn cmplesd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(2);
    }

    pub fn cmpunordsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(3);
    }

    pub fn cmpneqsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(4);
    }

    pub fn cmpnltsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(5);
    }

    pub fn cmpnlesd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xc2);
        self.modrm_reg(reg, rm);
        self.append_byte(6);
    }

    pub fn cmpordsd(&mut self, reg: u8, rm: u8) {
        self.sse_sd(reg, rm);
        self.append_byte(0xC2);
        self.modrm_reg(reg, rm);
        self.append_byte(7);
    }

    pub fn ucomisd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x2e);
        self.modrm_reg(reg, rm);
    }

    pub fn andpd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x54);
        self.modrm_reg(reg, rm);
    }

    pub fn andnpd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x55);
        self.modrm_reg(reg, rm);
    }

    pub fn orpd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x56);
        self.modrm_reg(reg, rm);
    }

    pub fn xorpd(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x57);
        self.modrm_reg(reg, rm);
    }

    pub fn vmovmskpd(&mut self, reg: u8, rm: u8) {
        self.vex_pd(reg, 0, rm, 0);
        self.append_byte(0x50);
        self.modrm_reg(reg, rm);
    }

    /*******************************************/
    pub fn vzeroupper(&mut self) {
        self.append_bytes(&[0xC5, 0xF8, 0x77]);
    }

    // general registers
    pub fn mov(&mut self, reg: u8, rm: u8) {
        self.rex(reg, rm);
        self.append_byte(0x8b);
        self.modrm_reg(reg, rm);
    }

    pub fn mov_reg_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.rex(reg, rm);
        self.append_byte(0x8b);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn lea_mem(&mut self, reg: u8, rm: u8, offset: i32) {
        self.rex(reg, rm);
        self.append_byte(0x8d);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn mov_reg_label(&mut self, reg: u8, label: &str) {
        self.rex(reg, 0);
        self.append_byte(0x8b);
        // modr/m byte with MOD=00 and R/M=101 (RIP-relative address)
        self.append_byte(5 | ((reg & 7) << 3));
        self.jump(label);
    }

    pub fn mov_mem_reg(&mut self, rm: u8, offset: i32, reg: u8) {
        self.rex(reg, rm);
        self.append_byte(0x89);
        self.modrm_mem(reg, rm, offset);
    }

    pub fn lea_indexed(&mut self, reg: u8, base: u8, index: u8, scale: u8) {
        self.rex(reg, 0);
        self.append_byte(0x8d);
        self.modrm_sib(reg, base, index, scale);
    }

    pub fn movabs(&mut self, rm: u8, imm64: u64) {
        self.rex(0, rm);
        self.append_byte(0xb8 + (rm & 7));
        self.append_word(imm64 as u32);
        self.append_word((imm64 >> 32) as u32);
    }

    pub fn call(&mut self, reg: u8) {
        if reg < 8 {
            self.append_bytes(&[0xff, 0xd0 | reg]);
        } else {
            self.append_bytes(&[0x41, 0xff, 0xd0 | (reg & 7)]);
        }
    }

    pub fn call_indirect(&mut self, label: &str) {
        self.append_bytes(&[0xff, 0x15]);
        self.jump(label);
    }

    pub fn call_relative(&mut self, label: &str) {
        self.append_byte(0xe8);
        self.jump(label);
    }

    pub fn push(&mut self, reg: u8) {
        if reg < 8 {
            self.append_byte(0x50 | reg);
        } else {
            self.append_bytes(&[0x41, 0x50 | (reg & 7)]);
        }
    }

    pub fn pop(&mut self, reg: u8) {
        if reg < 8 {
            self.append_byte(0x58 | reg);
        } else {
            self.append_bytes(&[0x41, 0x58 | (reg & 7)]);
        }
    }

    pub fn ret(&mut self) {
        self.append_byte(0xc3);
    }

    pub fn add_rsp(&mut self, imm: u32) {
        self.append_bytes(&[0x48, 0x81, 0xc4]);
        self.append_word(imm);
    }

    pub fn sub_rsp(&mut self, imm: u32) {
        self.append_bytes(&[0x48, 0x81, 0xec]);
        self.append_word(imm);
    }

    pub fn or(&mut self, reg: u8, rm: u8) {
        self.rex(reg, rm);
        self.append_byte(0x0b);
        self.modrm_reg(reg, rm);
    }

    pub fn xor(&mut self, reg: u8, rm: u8) {
        self.rex(reg, rm);
        self.append_byte(0x33);
        self.modrm_reg(reg, rm);
    }

    pub fn add(&mut self, reg: u8, rm: u8) {
        self.rex(reg, rm);
        self.append_byte(0x03);
        self.modrm_reg(reg, rm);
    }

    pub fn add_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(0, rm);
        self.append_word(imm);
    }

    pub fn mov_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0xc7);
        self.modrm_reg(0, rm);
        self.append_word(imm);
    }

    pub fn or_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(1, rm);
        self.append_word(imm);
    }

    pub fn and_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(4, rm);
        self.append_word(imm);
    }

    pub fn sub_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(5, rm);
        self.append_word(imm);
    }

    pub fn xor_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(6, rm);
        self.append_word(imm);
    }

    pub fn cmp_imm(&mut self, rm: u8, imm: u32) {
        self.rex(0, rm);
        self.append_byte(0x81);
        self.modrm_reg(7, rm);
        self.append_word(imm);
    }

    pub fn inc(&mut self, rm: u8) {
        self.rex(0, rm);
        self.append_byte(0xff);
        self.modrm_reg(0, rm);
    }

    pub fn dec(&mut self, rm: u8) {
        self.rex(0, rm);
        self.append_byte(0xff);
        self.modrm_reg(1, rm);
    }

    pub fn jmp(&mut self, label: &str) {
        self.append_byte(0xe9);
        self.jump(label);
    }

    pub fn jz(&mut self, label: &str) {
        self.append_bytes(&[0x0f, 0x84]);
        self.jump(label);
    }

    pub fn jnz(&mut self, label: &str) {
        self.append_bytes(&[0x0f, 0x85]);
        self.jump(label);
    }

    pub fn jpe(&mut self, label: &str) {
        // jump if parity even is true if vucomisd returns
        // an unordered result
        self.append_bytes(&[0x0f, 0x8a]);
        self.jump(label);
    }

    pub fn jpo(&mut self, label: &str) {
        // jump if parity even is true if vucomisd returns
        // an unordered result
        self.append_bytes(&[0x0f, 0x8b]);
        self.jump(label);
    }

    pub fn js(&mut self, label: &str) {
        // jump if sign = 1
        self.append_bytes(&[0x0f, 0x88]);
        self.jump(label);
    }

    pub fn movq_reg_xmm(&mut self, rm: u8, reg: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x7e);
        self.modrm_reg(reg, rm);
    }

    pub fn movq_xmm_reg(&mut self, reg: u8, rm: u8) {
        self.sse_pd(reg, rm);
        self.append_byte(0x6e);
        self.modrm_reg(reg, rm);
    }

    pub fn vmovq_reg_xmm(&mut self, rm: u8, reg: u8) {
        self.vex3dd_w1(reg, 0, rm, 0, 1);
        self.append_byte(0x7e);
        self.modrm_reg(reg, rm);
    }

    pub fn vmovq_xmm_reg(&mut self, reg: u8, rm: u8) {
        self.vex3dd_w1(reg, 0, rm, 0, 1);
        self.append_byte(0x6e);
        self.modrm_reg(reg, rm);
    }

    pub fn nop(&mut self) {
        self.append_byte(0x90);
    }

    pub fn prefetcht0_ip(&mut self, offset: u32) {
        self.append_bytes(&[0x0f, 0x18, 0x0d]);
        self.append_word(offset);
    }

    pub fn prefetcht1_ip(&mut self, offset: u32) {
        self.append_bytes(&[0x0f, 0x18, 0x15]);
        self.append_word(offset);
    }

    pub fn prefetcht2_ip(&mut self, offset: u32) {
        self.append_bytes(&[0x0f, 0x18, 0x1d]);
        self.append_word(offset);
    }

    pub fn prefetchnta_ip(&mut self, offset: u32) {
        self.append_bytes(&[0x0f, 0x18, 0x05]);
        self.append_word(offset);
    }
}
