use std::arch::x86_64::_mm_xor_pd;
use std::fmt::{Debug, Display, Formatter};
use std::ops::{BitAnd, Shr};
use std::time::Instant;
use log::debug;
use rand::{random, Rng};
use crate::chip8::computer::{Chip8Computer};
use crate::chip8::cpu_states::Chip8CpuStates::WaitingForKey;
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
use crate::chip8::util::InstructionUtil;

/*
nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
n or nibble - A 4-bit value, the lowest 4 bits of the instruction
x - A 4-bit value, the lower 4 bits of the high byte of the instruction
y - A 4-bit value, the upper 4 bits of the low byte of the instruction
kk or byte - An 8-bit value, the lowest 8 bits of the instruction
 */

#[derive(Debug)]
pub enum Chip8CpuInstructions {
    /// 0nnn
    /// Exit to System Call at nnn
    SysAddr(i16),
    /// 00E0
    /// Clear the display.
    CLS,
    /// 00EE
    /// Return from a subroutine.
    ///
    /// The interpreter sets the program counter to the address at the top of the stack,
    /// then subtracts 1 from the stack pointer.
    RET,
    /// 1nnn
    /// Jump to location nnn.
    ///
    /// The interpreter sets the program counter to nnn.
    JpAddr(i16),
    /// 2nnn
    /// Call subroutine at nnn.
    ///
    /// The interpreter increments the stack pointer, then puts the current PC on the top
    /// of the stack. The PC is then set to nnn.
    CallAddr(i16),
    /// 0x3xkk
    /// Skip next instruction if Vx = kk.
    ///
    /// The interpreter compares register Vx to kk, and if they are equal, increments the
    /// program counter by 2.
    SeVxByte(u8, u8),
    /// 4xkk
    /// Skip next instruction if Vx != kk.
    ///
    /// The interpreter compares register Vx to kk, and if they are not equal, increments
    /// the program counter by 2.
    SneVxByte(u8, u8),
    /// 5xy0
    /// Skip next instruction if Vx = Vy.
    ///
    /// The interpreter compares register Vx to register Vy, and if they are equal, increments
    /// the program counter by 2.
    SeVxVy(u8, u8),
    /// 6xkk
    /// Set Vx = kk
    LdVxByte(u8, u8),
    /// 7xkk
    /// The interpreter puts the value kk into register Vx.
    AddVxByte(u8, u8),
    /// 8xy0
    /// Adds the value kk to the value of register Vx, then stores the result in Vx.
    LdVxVy(u8, u8),
    /// 8xy1
    /// Stores the value of register Vy in register Vx.
    OrVxVy(u8, u8),
    /// 8xy2
    /// Set Vx = Vx OR Vy.
    ///
    /// Performs a bitwise OR on the values of Vx and Vy, then stores the result in Vx.
    /// A bitwise OR compares the corrseponding bits from two values, and if either
    /// bit is 1, then the same bit in the result is also 1. Otherwise, it is 0.
    AndVxVy(u8, u8),
    /// 8xy3
    /// Set Vx = Vx AND Vy.
    ///
    /// Performs a bitwise AND on the values of Vx and Vy, then stores the result in Vx.
    /// A bitwise AND compares the corrseponding bits from two values, and if both bits
    /// are 1, then the same bit in the result is also 1. Otherwise, it is 0.
    XorVxVy(u8, u8),
    /// 8xy4
    /// Set Vx = Vx XOR Vy.
    ///
    /// Performs a bitwise exclusive OR on the values of Vx and Vy, then stores the
    /// result in Vx. An exclusive OR compares the corrseponding bits from two values,
    /// and if the bits are not both the same, then the corresponding bit in the result
    /// is set to 1. Otherwise, it is 0.
    AddVxVy(u8, u8),
    /// 8xy5
    /// Set Vx = Vx + Vy, set VF = carry.
    ///
    /// The values of Vx and Vy are added together. If the result is greater than 8 bits
    /// (i.e., > 255,) VF is set to 1, otherwise 0. Only the lowest 8 bits of the result
    /// are kept, and stored in Vx.
    SubVxVy(u8, u8),
    /// 8xy6
    /// Set Vx = Vx - Vy, set VF = NOT borrow.
    ///
    /// If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the
    /// results stored in Vx.
    ShrVxVy(u8, u8),
    /// 8xy7
    /// If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx
    /// is divided by 2.
    SubnVxVy(u8, u8),
    /// 8xye
    /// Set Vx = Vy - Vx, set VF = NOT borrow.
    //
    /// If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the
    /// results stored in Vx.
    ShlVxVy(u8, u8),
    /// 9xy0
    /// Skip next instruction if Vx != Vy.
    ///
    /// The values of Vx and Vy are compared, and if they are not equal, the program
    /// counter is increased by 2.
    SneVxVy(u8, u8),
    /// Annn
    /// Set I = nnn.
    ///
    /// The value of register I is set to nnn
    LdIAddr(u16),
    /// Bnnn
    /// Jump to location nnn + V0.
    ///
    /// The program counter is set to nnn plus the value of V0.
    JpV0Addr(u16),
    /// Cxkk
    /// Set Vx = random byte AND kk.
    ///
    /// The interpreter generates a random number from 0 to 255, which is then
    /// ANDed with the value kk. The results are stored in Vx. See instruction
    /// 8xy2 for more information on AND.
    RndVxByte(u8, u8),
    /// Dxyn
    /// Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.
    ///
    /// The interpreter reads n bytes from memory, starting at the address stored in I.
    /// These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
    /// Sprites are XORed onto the existing screen. If this causes any pixels to be erased,
    /// VF is set to 1, otherwise it is set to 0. If the sprite is positioned so part of
    /// it is outside the coordinates of the display, it wraps around to the opposite side
    /// of the screen. See instruction 8xy3 for more information on XOR, and section 2.4,
    /// Display, for more information on the Chip-8 screen and sprites.
    DrawVxVyNibble(u8, u8, u8),
    /// Ex9E
    /// Skip next instruction if key with the value of Vx is pressed.
    ///
    /// Checks the keyboard, and if the key corresponding to the value of Vx is currently
    /// in the down position, PC is increased by 2.
    SkpVx(u8),
    /// ExA1
    /// Skip next instruction if key with the value of Vx is not pressed.
    ///
    /// Checks the keyboard, and if the key corresponding to the value of Vx is currently in
    /// the up position, PC is increased by 2.
    SnkpVx(u8),
    /// Fx07
    /// The value of DT is placed into Vx.
    LdVxDt(u8),
    /// Fx0A
    /// Wait for a key press, store the value of the key in Vx.
    ///
    /// All execution stops until a key is pressed, then the value of that key is stored in Vx.
    LdVxK(u8),
    /// Fx15
    /// Set delay timer = Vx.
    ///
    /// DT is set equal to the value of Vx.
    LdDtVx(u8),                   // 0xFx15 Set Delay Timer
    /// Fx18
    /// Set sound timer = Vx.
    ///
    /// ST is set equal to the value of Vx.
    LdStVx(u8),
    /// Fx1E - ADD I, Vx
    /// Set I = I + Vx.
    ///
    /// The values of I and Vx are added, and the results are stored in I.
    AddIVx(u8),
    /// Fx29 - LD F, Vx
    /// Set I = location of sprite for digit Vx.
    ///
    /// The value of I is set to the location for the hexadecimal sprite
    /// corresponding to the value of Vx. See section 2.4, Display, for
    /// more information on the Chip-8 hexadecimal font.
    LdFVx(u8),
    /// Fx33 - LD B, Vx
    /// Store BCD representation of Vx in memory locations I, I+1, and I+2.
    ///
    /// The interpreter takes the decimal value of Vx, and places the hundreds
    /// digit in memory at location in I, the tens digit at location I+1, and the
    /// ones digit at location I+2.
    LdBVx(u8),
    /// Fx55 - LD [I], Vx
    /// Store registers V0 through Vx in memory starting at location I.
    ///
    /// The interpreter copies the values of registers V0 through Vx into memory,
    /// starting at the address in I.
    LdIVx(u8),
    /// Fx65 - LD Vx, [I]
    /// Read registers V0 through Vx from memory starting at location I.
    ///
    /// The interpreter reads values from memory starting at location I into registers
    /// V0 through Vx.
    LdVxI(u8),
    XXXXERRORINSTRUCTION,
}

impl Display for Chip8CpuInstructions {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", format!("{}", match self {
            Chip8CpuInstructions::SysAddr(x) => {
                format!("SYS 0x{:04x}", x)
            }
            Chip8CpuInstructions::CLS => {
                String::from("CLS")
            }
            Chip8CpuInstructions::RET => {
                String::from("RET")
            }
            Chip8CpuInstructions::JpAddr(x) => {
                format!("JP 0x{:04x}", x)
            }
            Chip8CpuInstructions::CallAddr(x) => {
                format!("SUB 0x{:04x}", x)
            }
            Chip8CpuInstructions::SeVxByte(x, byte) => {
                format!("SeVxByte 0x{:02x}, 0x{:02x}", x, byte)
            }
            Chip8CpuInstructions::SneVxByte(x, byte) => {
                format!("SneVxByte 0x{:02x}, 0x{:02x}", x, byte)
            }
            Chip8CpuInstructions::SeVxVy(x, y) => {
                format!("SeVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::LdVxByte(x, byte) => {
                format!("LdVxByte 0x{:02x}, 0x{:02x}", x, byte)
            }
            Chip8CpuInstructions::AddVxByte(x, byte) => {
                format!("AddVxByte 0x{:02x}, 0x{:02x}", x, byte)
            }
            Chip8CpuInstructions::LdVxVy(x, y) => {
                format!("LdVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::OrVxVy(x, y) => {
                format!("OrVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::AndVxVy(x, y) => {
                format!("AndVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::XorVxVy(x, y) => {
                format!("XorVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::AddVxVy(x, y) => {
                format!("AddVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::SubVxVy(x, y) => {
                format!("SubVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::ShrVxVy(x, y) => {
                format!("ShrVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::SubnVxVy(x, y) => {
                format!("SubnVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::ShlVxVy(x, y) => {
                format!("ShlVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::SneVxVy(x, y) => {
                format!("SneVxVy 0x{:02x}, 0x{:02x}", x, y)
            }
            Chip8CpuInstructions::LdIAddr(addr) => {
                format!("LdIAddr 0x{:04x}", addr)
            }
            Chip8CpuInstructions::JpV0Addr(addr) => {
                format!("JpV0Addr 0x{:04x}", addr)
            }
            Chip8CpuInstructions::RndVxByte(x, byte) => {
                format!("RndVxByte 0x:{:02x}, 0x{:02x}", x, byte)
            }
            Chip8CpuInstructions::DrawVxVyNibble(x, y, nibble) => {
                format!("DrawVxVyNibble 0x:{:02x}, 0x{:02x}, 0x{:02x}", x, y, nibble)
            }
            Chip8CpuInstructions::SkpVx(x) => {
                format!("SkpVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::SnkpVx(x) => {
                format!("SnkpVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdVxDt(x) => {
                format!("LdVxDt 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdVxK(x) => {
                format!("LdVxK 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdDtVx(x) => {
                format!("LdDtVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdStVx(x) => {
                format!("LdStVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::AddIVx(x) => {
                format!("AddIVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdFVx(x) => {
                format!("LdFVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdBVx(x) => {
                format!("LdBVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdIVx(x) => {
                format!("LdIVx 0x{:02x}", x)
            }
            Chip8CpuInstructions::LdVxI(i) => {
                format!("LdVxI 0x{:02x}", i)
            }
            XXXXERRORINSTRUCTION => {
                String::from("INVALID INSTRUCTION")
            }
        }))
    }
}

impl Chip8CpuInstructions {
    pub fn encode(&self) -> u16 {
        match self {
            Chip8CpuInstructions::SysAddr(target) => 0x0000 | (target & 0x0FFF) as u16,
            Chip8CpuInstructions::CLS => 0x00E0,
            Chip8CpuInstructions::RET => 0x00EE,
            Chip8CpuInstructions::JpAddr(new_addr) => 0x1000 | (new_addr & 0x0FFF) as u16,
            Chip8CpuInstructions::CallAddr(address) => 0x2000 | (address & 0x0FFF) as u16,
            Chip8CpuInstructions::SeVxByte(vx_register, byte) => 0x3000 | ((*vx_register as u16) << 8) | (*byte as u16),
            Chip8CpuInstructions::SneVxByte(vx_register, byte) => 0x4000 | ((*vx_register as u16) << 8) | (*byte as u16),
            Chip8CpuInstructions::SeVxVy(x_register, y_register) => 0x5000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::LdVxByte(x_register, byte) => 0x6000 | ((*x_register as u16) << 8) | (*byte as u16),
            Chip8CpuInstructions::AddVxByte(x_register, byte) => 0x7000 | ((*x_register as u16) << 8) | (*byte as u16),
            Chip8CpuInstructions::LdVxVy(x_register, y_register) => 0x8000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::OrVxVy(x_register, y_register) => 0x8001 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::AndVxVy(x_register, y_register) => 0x8002 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::XorVxVy(x_register, y_register) => 0x8003 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::AddVxVy(x_register, y_register) => 0x8004 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::SubVxVy(x_register, y_register) => 0x8005 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::ShrVxVy(x_register, y_register) => 0x8006 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::SubnVxVy(x_register, y_register) => 0x8007 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::ShlVxVy(x_register, y_register) => 0x800E | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::SneVxVy(x_register, y_register) => 0x9000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4),
            Chip8CpuInstructions::LdIAddr(addr) => 0xA000 | addr,
            Chip8CpuInstructions::JpV0Addr(addr) => 0xB000 | addr,
            Chip8CpuInstructions::RndVxByte(x_register, byte) => 0xC000 | ((*x_register as u16) << 8) | (*byte as u16),
            Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, height) => {
                0xD000 | ((*x_register as u16) << 8) | ((*y_register as u16) << 4) | (*height as u16)
            }
            Chip8CpuInstructions::SkpVx(x_register) => 0xE09E | ((*x_register as u16) << 8),
            Chip8CpuInstructions::SnkpVx(x_register) => 0xE0A1 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdVxDt(x_register) => 0xF007 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdVxK(x_register) => 0xF00A | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdDtVx(x_register) => 0xF015 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdStVx(x_register) => 0xF018 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::AddIVx(x_register) => 0xF01E | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdFVx(x_register) => 0xF029 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdBVx(x_register) => 0xF033 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdIVx(x_register) => 0xF055 | ((*x_register as u16) << 8),
            Chip8CpuInstructions::LdVxI(x_register) => 0xF065 | ((*x_register as u16) << 8),
            XXXXERRORINSTRUCTION => 0xFFFF,
        }
    }
    pub fn decode(input: u16) -> Chip8CpuInstructions {
        let x_param = InstructionUtil::read_x_from_instruction(input);
        let y_param = InstructionUtil::read_y_from_instruction(input);
        let addr_param = InstructionUtil::read_addr_from_instruction(input);
        let byte_param = InstructionUtil::read_byte_from_instruction(input);
        let nibble_param = InstructionUtil::read_nibble_from_instruction(input);
        let ubln = InstructionUtil::read_upper_byte_lower_nibble(input);
        let last_byte = (input & 0xFF) as u8;
        let last_nibble = (input & 0xF) as u8;

        match input {
            0x00E0 => Chip8CpuInstructions::CLS,
            0x00EE => Chip8CpuInstructions::RET,
            0x0000..=0x0FFF => Chip8CpuInstructions::SysAddr(addr_param as i16),
            0x1000..=0x1FFF =>  Chip8CpuInstructions::JpAddr(addr_param as i16),
            0x2000..=0x2FFF => Chip8CpuInstructions::CallAddr(addr_param as i16),
            0x3000..=0x3FFF => Chip8CpuInstructions::SeVxByte(x_param, byte_param),
            0x4000..=0x4FFF => Chip8CpuInstructions::SneVxByte(x_param, byte_param),
            0x5000..=0x5FF0 if input & 0x01 == 0 => Chip8CpuInstructions::SeVxVy(x_param, y_param),
            0x6000..=0x6FFF => Chip8CpuInstructions::LdVxByte(x_param, byte_param),
            0x7000..=0x7FFF => Chip8CpuInstructions::AddVxByte(x_param, byte_param),
            0x8000..=0x8FFE => match last_nibble {
                0x0 => Chip8CpuInstructions::LdVxVy(x_param, y_param),
                0x1 => Chip8CpuInstructions::OrVxVy(x_param, y_param),
                0x2 => Chip8CpuInstructions::AndVxVy(x_param, y_param),
                0x3 => Chip8CpuInstructions::XorVxVy(x_param, y_param),
                0x4 => Chip8CpuInstructions::AddVxVy(x_param, y_param),
                0x5 => Chip8CpuInstructions::SubVxVy(x_param, y_param),
                0x6 => Chip8CpuInstructions::ShrVxVy(x_param, y_param),
                0x7 => Chip8CpuInstructions::SubnVxVy(x_param, y_param),
                0xE => Chip8CpuInstructions::ShlVxVy(x_param, y_param),
                _ => XXXXERRORINSTRUCTION
            }
            0x9000..=0x9FF0 if input & 0x01 == 0 => Chip8CpuInstructions::SneVxVy(x_param, y_param),
            0xA000..=0xAFFF => Chip8CpuInstructions::LdIAddr(addr_param),
            0xB000..=0xBFFF => Chip8CpuInstructions::JpV0Addr(addr_param),
            0xC000..=0xCFFF => Chip8CpuInstructions::RndVxByte(x_param, byte_param),
            0xD000..=0xDFFF => Chip8CpuInstructions::DrawVxVyNibble(x_param, y_param, nibble_param),
            0xE09E..=0xEFA1 => match last_byte {
                    0x9E => Chip8CpuInstructions::SkpVx(ubln),
                    0xA1 => Chip8CpuInstructions::SnkpVx(ubln),
                    _ => XXXXERRORINSTRUCTION
            }
            0xF007..=0xFF65 => match last_byte {
                0x07 => Chip8CpuInstructions::LdVxDt(ubln),
                0x0A => Chip8CpuInstructions::LdVxK(ubln),
                0x15 => Chip8CpuInstructions::LdDtVx(ubln),
                0x18 => Chip8CpuInstructions::LdStVx(ubln),
                0x1E => Chip8CpuInstructions::AddIVx(ubln),
                0x29 => Chip8CpuInstructions::LdFVx(ubln),
                0x33 => Chip8CpuInstructions::LdBVx(ubln),
                0x55 => Chip8CpuInstructions::LdIVx(ubln),
                0x65 => Chip8CpuInstructions::LdVxI(ubln),
                _ => XXXXERRORINSTRUCTION
            }
            _ => XXXXERRORINSTRUCTION
        }
    }
    pub fn execute(&self, input: &mut Chip8Computer) -> Chip8Computer {
//        print!("INSTRUCTION {}", self);
        let start_time = Instant::now();
        let start_pc = input.registers.peek_pc();
        input.registers.poke_pc(start_pc + 2);
        let _ = match self {
            // 0x0nnn Exit to System Call
            Chip8CpuInstructions::SysAddr(new_address) => {
                input.registers.poke_pc(*new_address as u16);
            }
            // * 0x00E0 Clear Screen
            Chip8CpuInstructions::CLS => {
                input.video_memory.cls();
            }
            // 0x00EE Return from Subroutine
            Chip8CpuInstructions::RET => {
                input.registers.poke_pc(input.stack.pop());
            }
            // 0x1nnn Jump to Address
            Chip8CpuInstructions::JpAddr(new_address) => {
                input.registers.poke_pc(*new_address as u16);
            }
            // 0x2nnn Call Subroutine
            Chip8CpuInstructions::CallAddr(new_address) => {
                let return_address = input.registers.peek_pc();
                input.registers.poke_pc(*new_address as u16);
                input.stack.push(&return_address);
            }
            // 0x3xkk Skip next instruction if Vx = kk.
            Chip8CpuInstructions::SeVxByte(vx_register, byte) => {
                if input.registers.peek(*vx_register as u8) == *byte as u8 {
                    input.registers.advance_pc();
                }
            }
            // 0x4xkk Skip next instruction if Vx != kk
            Chip8CpuInstructions::SneVxByte(x, byte) => {
                // 4xkk - SNE Vx, byte
                // Skip next instruction if Vx != kk.
                //
                // The interpreter compares register Vx to kk, and if they are not equal,
                // increments the program counter by 2.
                if input.registers.peek(*x) != *byte {
                    input.registers.advance_pc();
                }
            }
            // 0x5xy0 Skip next instruction if Vx == Vy
            Chip8CpuInstructions::SeVxVy(x, y) => {
                if input.registers.peek(*x as u8) == input.registers.peek(*y as u8) {
                    input.registers.advance_pc();
                }
            }
            // 0x6xkk Set Vx = kk
            Chip8CpuInstructions::LdVxByte(register, byte) => {
                input.registers.poke(*register, *byte);
            }
            // 0x7xkk Set Vx = Vx + kk
            Chip8CpuInstructions::AddVxByte(vx_register, byte) => {
                input.registers.poke(*vx_register, (input.registers.peek(*vx_register) as u16 + *byte as u16) as u8);
            }
            // 0x8xy0 Set value of Vy in Vx
            Chip8CpuInstructions::LdVxVy(x, y) => {
                input.registers.poke(*x as u8, input.registers.peek(*y as u8));
            }
            // 0x8xy1 Set Vx = Vx OR Vy
            Chip8CpuInstructions::OrVxVy(x, y) => {
                // shift them to 16 bit
                let working_16_x = input.registers.peek(*x as u8) as u16;
                let working_16_y = input.registers.peek(*y as u8) as u16;
                // OR them
                let working_16_or = working_16_x | working_16_y;
                // shift them back to 8 bit.
                input.registers.poke(*x as u8, working_16_or as u8);
                debug!("OrVxVy [0x{x:1x}] [0x{y:1x}]")
            }
            // 0x8xy2 Set Vx = Vx AND Vy
            Chip8CpuInstructions::AndVxVy(x, y) => {
                let lhs_16 = input.registers.peek(*x as u8) as u16;
                let rhs_16 = input.registers.peek(*y as u8) as u16;
                input.registers.poke(*x as u8, (lhs_16 & rhs_16) as u8);
            }
            // 0x8xy3 Set Vx = Vx XOR Vy
            Chip8CpuInstructions::XorVxVy(x, y) => {
                let lhs_16 = input.registers.peek(*x as u8) as u16;
                let rhs_16 = input.registers.peek(*y as u8) as u16;
                input.registers.poke(*x as u8, (lhs_16 ^ rhs_16) as u8);
            }
            // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
            Chip8CpuInstructions::AddVxVy(x, y) => {
                let lhs = input.registers.peek(*x as u8) as i16;
                let rhs = input.registers.peek(*y as u8) as i16;
                let working = lhs + rhs;
                input.registers.poke(*x as u8, working as u8);

                if working >= 0x100 {
                    input.registers.poke(0xf, 0x01);
                } else {
                    input.registers.poke(0x0f, 0x00);
                }
            }
            Chip8CpuInstructions::SubVxVy(x, y) => {
                // 8xy5 - SUB Vx, Vy
                // Set Vx = Vx - Vy, set VF = NOT borrow.
                //
                // If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is subtracted from Vx, and the results stored in Vx.

                let lhs = input.registers.peek(*x);
                let rhs = input.registers.peek(*y);

                let mut result = 0;

                let borrow_flag: u8 = if rhs > lhs {
                    result = (lhs as u16 + 0x100) - rhs as u16;
                    0
                } else {
                    result = lhs as u16 - rhs as u16;
                    1
                };

                input.registers.poke(*x as u8, result as u8);
                input.registers.poke(0x0f, borrow_flag);
            }
            Chip8CpuInstructions::ShrVxVy(x, _) => {
                // 8xy6 - SHR Vx {, Vy}
                // Set Vx = Vx SHR 1.
                // SHIFT 1 Bit ---->
                // If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
                let initial_value = input.registers.peek(*x);

                // overflow check
                let rotated = initial_value >> 1;
                input.registers.poke(*x as u8, rotated);
                if initial_value.bitand(0b1) == 1 {
                    input.registers.poke(0x0f, 0x01);
                } else {
                    input.registers.poke(0x0f, 0x00);
                }
            }
            Chip8CpuInstructions::SubnVxVy(x, y) => {
                // 8xy7 - SUBN Vx, Vy
                // Set Vx = Vy - Vx, set VF = NOT borrow.
                //
                // If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is subtracted from Vy, and the results stored in Vx.
                let y_register = input.registers.peek(*y as u8);
                let x_register = input.registers.peek(*x as u8);
                let mut value_to_poke = 0;

                let new_value = if y_register < x_register {
                    value_to_poke = (y_register as u16 + 256) - x_register as u16;
                    0
                } else {
                    value_to_poke = (y_register - x_register) as u16;
                    1
                };

                debug!("SUB CARRY -> REGISTER 1 = [0x{x:02x}] / [{x_register}] REGISTER 2 = [0x{y:02x}] / [{y_register}] SUB = {value_to_poke} CARRY = {new_value}");

                input.registers.poke(*x, value_to_poke as u8);
                input.registers.poke(0xf, new_value);
            }

            Chip8CpuInstructions::ShlVxVy(x, _) => {
                // 8xyE - SHL Vx {, Vy}
                // Set Vx = Vx SHL 1.
                //
                // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.
                let initial_value = input.registers.peek(*x);

                // overflow check
                let rotated = initial_value << 1;
                input.registers.poke(*x as u8, rotated);
                if initial_value.bitand(0b10000000) == 0b10000000 {
                    input.registers.poke(0x0f, 0x01);
                } else {
                    input.registers.poke(0x0f, 0x00);
                }
            }
            Chip8CpuInstructions::SneVxVy(vx_register, vy_register) => {
                // 9xy0 - SNE Vx, Vy
                // Skip next instruction if Vx != Vy.
                //
                // The values of Vx and Vy are compared, and if they are not equal, the program counter is increased by 2.

                let x_reg_value = input.registers.peek(*vx_register as u8);
                let y_reg_value = input.registers.peek(*vy_register as u8);
                if x_reg_value != y_reg_value {
                    input.registers.advance_pc();
                }
            }
            Chip8CpuInstructions::LdIAddr(new_index) => {
                // Annn - LD I, addr
                // Set I = nnn.
                //
                // The value of register I is set to nnn.
              //  debug!("LdiAddr [0x{new_index:3x}]");
                input.registers.poke_i(*new_index);
            }
            // 0xBnnn Jump to nnn+V0
            Chip8CpuInstructions::JpV0Addr(addr) => {
                // Bnnn - JP V0, addr
                // Jump to location nnn + V0.
                //
                // The program counter is set to nnn plus the value of V0.
                input.registers.poke_pc(input.registers.peek(0) as u16 + addr);
            }
            Chip8CpuInstructions::RndVxByte(x, byte) => {
                // Cxkk - RND Vx, byte
                // Set Vx = random byte AND kk.
                //
                // The interpreter generates a random number from 0 to 255,
                // which is then ANDed with the value kk.
                // The results are stored in Vx.
                let mut rng = rand::thread_rng();
                let new_value: u8 = rng.random();
                let and_value: u8 = *byte;
                let result = new_value & and_value;
                debug!("RANDOM: [{new_value:02x}] AND: [{and_value:02x} Result: [{result:02x}]");
                input.registers.poke(*x as u8, new_value & *byte as u8)
            }
            Chip8CpuInstructions::DrawVxVyNibble(y, x, n) => {
                // Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision.

                // The interpreter reads n bytes from memory, starting at the address stored in I.

                // These bytes are then displayed as sprites on screen at coordinates (Vx, Vy).
                // Sprites are XORed onto the existing screen.
                // If this causes any pixels to be erased, VF is set to 1,
                //     otherwise it is set to 0.
                // If the sprite is positioned so part of it is outside the coordinates of the display,
                //     it wraps around to the opposite side of the screen.

                let source_memory_offset = input.registers.peek_i();
                let x_offset = input.registers.peek(*x) as u16;
                let y_offset = input.registers.peek(*y) as u16;

                for byte_index in 0..*n {
                    let current_byte = input.memory.peek(byte_index as u16 + source_memory_offset);
                    let x_offset: u16 = (x_offset + byte_index as u16) * 64;
                    for bit_index in 0..8 {
                        input.video_memory.poke(x_offset + (y_offset + bit_index as u16), (current_byte & (0x80 >> bit_index)) != 0);
                    }
                }

                let target = if input.video_memory.has_frame_changed {
                    1u8
                } else {
                    0u8
                };

                input.registers.poke(0xf, target);

            }
            Chip8CpuInstructions::SkpVx(x) => {
                // Ex9E - SKP Vx
                // Skip next instruction if key with the value of Vx is pressed.
                //
                // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
                let key_to_check = input.registers.peek(*x as u8);
                let is_pressed = input.keypad.pressed(key_to_check);
                if is_pressed {
                    input.registers.advance_pc();
                }
            }
            Chip8CpuInstructions::SnkpVx(x) => {
                // ExA1 - SKNP Vx
                // Skip next instruction if key with the value of Vx is not pressed.
                //

                // Checks the keyboard,
                // and if the key corresponding to the value of Vx is currently in the up position,
                // PC is increased by 2.
                let target_key = input.registers.peek(*x as u8);
                let is_pressed = input.keypad.pressed(target_key);
                debug!("SnKpVx [{x:1x}]");
                if !is_pressed {
                    input.registers.advance_pc();
                }
            }
            Chip8CpuInstructions::LdVxDt(x) => {
                // Fx07 - LD Vx, DT
                // Set Vx = delay timer value.
                //
                // The value of DT is placed into Vx.
                let value_to_set = input.delay_timer.current();
                input.registers.poke(*x as u8, value_to_set as u8);
            }
            Chip8CpuInstructions::LdVxK(x) => {
                // Fx0A - LD Vx, K
                // Wait for a key press, store the value of the key in Vx.
                //
                // All execution stops until a key is pressed, then the value of that key is stored in Vx.
                input.state = WaitingForKey;
            }
            Chip8CpuInstructions::LdDtVx(source_register) => {
                // Fx15 - LD DT, Vx
                // Set delay timer = Vx.
                //
                // DT is set equal to the value of Vx.
                let new_time = input.registers.peek(*source_register as u8);
                input.delay_timer.set_timer(new_time as i32);
            }
            Chip8CpuInstructions::LdStVx(new_time) => {
                let new_value = input.registers.peek(*new_time as u8);
                input.sound_timer.set_timer(new_value as i32);
            }
            Chip8CpuInstructions::AddIVx(x) => {
                // Fx1E - ADD I, Vx
                // Set I = I + Vx.
                //
                // The values of I and Vx are added, and the results are stored in I.
                let base = input.registers.peek_i();
                let x_value = input.registers.peek(*x as u8);
                input.registers.poke_i(base + x_value as u16);
            }
            Chip8CpuInstructions::LdFVx(x) => {
                // Fx29 - LD F, Vx
                // Set I = location of sprite for digit Vx.
                //
                // The value of I is set to the location for the hexadecimal sprite corresponding
                // to the value of Vx. See section 2.4, Display, for more information on
                // the Chip-8 hexadecimal font.

                let x_value: u8 = input.registers.peek(*x);

                let real_offset = x_value as u16 * 5;
                input.registers.poke_i(real_offset as u16);
            }
            Chip8CpuInstructions::LdBVx(x) => {
                // Fx33 - LD B, Vx
                // Store BCD representation of Vx in memory locations I, I+1, and I+2.
                //
                // The interpreter takes the decimal value of Vx, and places the hundreds
                // digit in memory at location in I, the tens digit at location I+1,
                // and the ones digit at location I+2.

                let to_convert = input.registers.peek(*x as u8);

                // how many hundreds
                let hundreds = to_convert / 100;
                // how many tens, minus the hundreds
                let tens = (to_convert / 10) - (hundreds * 10);
                // whats the leftover when dividing by 10
                let units = to_convert % 10;

                // Convert to BCD
                let result = ((hundreds as u16) << 8) | units as u16 | ((tens as u16) << 4) | units as u16;
                // write them to the memory pointed to by I, I+1, and I+2
                let target_start_offset = input.registers.peek_i();
                input.memory.poke(target_start_offset, hundreds);
                input.memory.poke(target_start_offset + 1, tens);
                input.memory.poke(target_start_offset + 2, units);
            }
            Chip8CpuInstructions::LdIVx(x) => {
                // Store registers V0 through Vx in memory starting at location I.
                //
                // The interpreter copies the values of registers V0 through Vx into memory,
                // starting at the address in I.
                let offset = input.registers.peek_i();
                for i in 0..=*x {
                    input.memory.poke(offset + i as u16, input.registers.peek(i as u8));
                }
                input.registers.poke_i(offset + 1);
            }
            Chip8CpuInstructions::LdVxI(x) => {
                // Read registers V0 through Vx from memory starting at location I.
                //
                // The interpreter reads values from memory starting at location I into registers V0 through Vx.
                let offset = input.registers.peek_i();
                debug!("STARTING TO READ AT {offset:03x}");
                let num_loops = x + 1;
                debug!("WILL READ {num_loops:x} BYTES");
                for index in 0..num_loops {
                    let src_value = input.memory.peek(index as u16 + offset);
                    input.registers.poke(index as u8, src_value);
                    debug!("POKING Register 0x{index:02x} with 0x{src_value:04x} using offset 0x{offset:04x}");
                }
                input.registers.poke_i(offset + 1);
            }
            Chip8CpuInstructions::XXXXERRORINSTRUCTION => {}
        };
        let cycle_time = Instant::now().duration_since(start_time).as_nanos();
//        println!("\t\tTook {cycle_time}ms");
        input.to_owned()
    }
}

#[cfg(test)]
mod test {
    use crate::chip8::system_memory::{CHIP8FONT_0, CHIP8FONT_1, CHIP8FONT_2, CHIP8FONT_9};
    use crate::constants::CHIP8_VIDEO_MEMORY;
    use super::*;

    #[test]
    fn smoke() {
        assert!(true)
    }

    #[test]
    fn encode_decode_test() {
        assert_eq!(Chip8CpuInstructions::CLS.encode(), 0x00E0);
        assert_eq!(Chip8CpuInstructions::RET.encode(), 0x00EE);
        assert_eq!(Chip8CpuInstructions::SysAddr(0x123).encode(), 0x0123);
        assert_eq!(Chip8CpuInstructions::JpAddr(0x234).encode(), 0x1234);
        assert_eq!(Chip8CpuInstructions::CallAddr(0x345).encode(), 0x2345);
        assert_eq!(Chip8CpuInstructions::SeVxByte(0x4, 0x56).encode(), 0x3456);
        assert_eq!(Chip8CpuInstructions::SneVxByte(0xa, 0xbc).encode(), 0x4abc);
        assert_eq!(Chip8CpuInstructions::SeVxVy(0xa, 0xb).encode(), 0x5ab0);
        assert_eq!(Chip8CpuInstructions::LdVxByte(0xa, 0xff).encode(), 0x6aff);
        assert_eq!(Chip8CpuInstructions::AddVxByte(0xa, 0xbc).encode(), 0x7abc);
        assert_eq!(Chip8CpuInstructions::LdVxVy(0xa, 0xb).encode(), 0x8ab0);
        assert_eq!(Chip8CpuInstructions::OrVxVy(0xb, 0xa).encode(), 0x8ba1);
        assert_eq!(Chip8CpuInstructions::AndVxVy(0xc, 0xd).encode(), 0x8cd2);
        assert_eq!(Chip8CpuInstructions::XorVxVy(0xd, 0xe).encode(), 0x8de3);
        assert_eq!(Chip8CpuInstructions::AddVxVy(0xe, 0xf).encode(), 0x8ef4);
        assert_eq!(Chip8CpuInstructions::SubVxVy(0xf, 0x0).encode(), 0x8f05);
        assert_eq!(Chip8CpuInstructions::ShrVxVy(0x0, 0x1).encode(), 0x8016);
        assert_eq!(Chip8CpuInstructions::SubnVxVy(0x1, 0x2).encode(), 0x8127);
        assert_eq!(Chip8CpuInstructions::ShlVxVy(0x3, 0x4).encode(), 0x834e);
        assert_eq!(Chip8CpuInstructions::SneVxVy(0xa, 0xb).encode(), 0x9ab0);
        assert_eq!(Chip8CpuInstructions::LdIAddr(0x123).encode(), 0xa123);
        assert_eq!(Chip8CpuInstructions::JpV0Addr(0x234).encode(), 0xb234);
        assert_eq!(Chip8CpuInstructions::RndVxByte(0xa, 0xca).encode(), 0xcaca);
        assert_eq!(Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4).encode(), 0xdab4);
        assert_eq!(Chip8CpuInstructions::SkpVx(0x1).encode(), 0xe19e);
        assert_eq!(Chip8CpuInstructions::SnkpVx(0x2).encode(), 0xe2a1);
        assert_eq!(Chip8CpuInstructions::LdVxDt(0x1).encode(), 0xf107);
        assert_eq!(Chip8CpuInstructions::LdVxK(0x4).encode(), 0xf40a);
        assert_eq!(Chip8CpuInstructions::LdDtVx(0x6).encode(), 0xf615);
        assert_eq!(Chip8CpuInstructions::LdStVx(0xb).encode(), 0xfb18);
        assert_eq!(Chip8CpuInstructions::AddIVx(0xd).encode(), 0xfd1e);
        assert_eq!(Chip8CpuInstructions::LdFVx(0xc).encode(), 0xfc29);
        assert_eq!(Chip8CpuInstructions::LdBVx(0xd).encode(), 0xfd33);
        assert_eq!(Chip8CpuInstructions::LdIVx(0xe).encode(), 0xfe55);
        assert_eq!(Chip8CpuInstructions::LdVxI(0x3).encode(), 0xf365);
        assert!(matches!(Chip8CpuInstructions::decode(0x00E0u16), Chip8CpuInstructions::CLS));
        assert!(matches!(Chip8CpuInstructions::decode(0x00EEu16), Chip8CpuInstructions::RET));
        assert!(matches!(Chip8CpuInstructions::decode(0x0123), Chip8CpuInstructions::SysAddr(0x123)));
        assert!(matches!(Chip8CpuInstructions::decode(0x0FFF), Chip8CpuInstructions::SysAddr(0xfff)));
        assert!(matches!(Chip8CpuInstructions::decode(0x1002), Chip8CpuInstructions::JpAddr(0x2)));
        assert!(matches!(Chip8CpuInstructions::decode(0x1FF0), Chip8CpuInstructions::JpAddr(0xFF0)));
        assert!(matches!(Chip8CpuInstructions::decode(0x2002), Chip8CpuInstructions::CallAddr(0x2)));
        assert!(matches!(Chip8CpuInstructions::decode(0x3123), Chip8CpuInstructions::SeVxByte(0x1, 0x23)));
        assert!(matches!(Chip8CpuInstructions::decode(0x4abc), Chip8CpuInstructions::SneVxByte(0xa, 0xbc)));
        assert!(matches!(Chip8CpuInstructions::decode(0x5ab0), Chip8CpuInstructions::SeVxVy(0xa, 0xb)));
        assert!(matches!(Chip8CpuInstructions::decode(0x6aff), Chip8CpuInstructions::LdVxByte(0xa, 0xff)));
        assert!(matches!(Chip8CpuInstructions::decode(0x7abc), Chip8CpuInstructions::AddVxByte(0xa, 0xbc)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8ab0), Chip8CpuInstructions::LdVxVy(0xa, 0xb)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8ba1), Chip8CpuInstructions::OrVxVy(0xb, 0xa)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8cd2), Chip8CpuInstructions::AndVxVy(0xc, 0xd)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8de3), Chip8CpuInstructions::XorVxVy(0xd, 0xe)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8ef4), Chip8CpuInstructions::AddVxVy(0xe, 0xf)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8f05), Chip8CpuInstructions::SubVxVy(0xf, 0x0)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8016), Chip8CpuInstructions::ShrVxVy(0x0, 0x1)));
        assert!(matches!(Chip8CpuInstructions::decode(0x8127), Chip8CpuInstructions::SubnVxVy(0x1, 0x2)));
        assert!(matches!(Chip8CpuInstructions::decode(0x834e), Chip8CpuInstructions::ShlVxVy(0x3, 0x4)));
        assert!(matches!(Chip8CpuInstructions::decode(0x9ab0), Chip8CpuInstructions::SneVxVy(0xa, 0xb)));
        assert!(matches!(Chip8CpuInstructions::decode(0xa123), Chip8CpuInstructions::LdIAddr(0x123)));
        assert!(matches!(Chip8CpuInstructions::decode(0xb234), Chip8CpuInstructions::JpV0Addr(0x234)));
        assert!(matches!(Chip8CpuInstructions::decode(0xcaca), Chip8CpuInstructions::RndVxByte(0xa, 0xca)));
        assert!(matches!(Chip8CpuInstructions::decode(0xdab4), Chip8CpuInstructions::DrawVxVyNibble(0xa, 0xb, 0x4)));
        assert!(matches!(Chip8CpuInstructions::decode(0xe19e), Chip8CpuInstructions::SkpVx(0x1)));
        assert!(matches!(Chip8CpuInstructions::decode(0xe2a1), Chip8CpuInstructions::SnkpVx(0x2)));
        assert!(matches!(Chip8CpuInstructions::decode(0xf107), Chip8CpuInstructions::LdVxDt(0x1)));
        assert!(matches!(Chip8CpuInstructions::decode(0xf40a), Chip8CpuInstructions::LdVxK(0x4)));
        assert!(matches!(Chip8CpuInstructions::decode(0xf615), Chip8CpuInstructions::LdDtVx(0x6)));
        assert!(matches!(Chip8CpuInstructions::decode(0xfb18), Chip8CpuInstructions::LdStVx(0xb)));
        assert!(matches!(Chip8CpuInstructions::decode(0xfd1e), Chip8CpuInstructions::AddIVx(0xd)));
        assert!(matches!(Chip8CpuInstructions::decode(0xfc29), Chip8CpuInstructions::LdFVx(0xc)));
        assert!(matches!(Chip8CpuInstructions::decode(0xfd33), Chip8CpuInstructions::LdBVx(0xd)));
        assert!(matches!(Chip8CpuInstructions::decode(0xfe55), Chip8CpuInstructions::LdIVx(0xe)));
        assert!(matches!(Chip8CpuInstructions::decode(0xf365), Chip8CpuInstructions::LdVxI(0x3)));
    }

    #[test]
    fn decoder_test_invalid_instructions() {
        let invalid_to_encode = [
            0x5ab1, 0x5abf, 0x8ab8, 0x8abd, 0x8abf,
            0x9ab1, 0x9abf, 0xea9d, 0xea9f, 0xeaa0,
            0xeaa2, 0xf006, 0xf008
        ];

        for i in invalid_to_encode {
            assert_eq!(Chip8CpuInstructions::decode(i).encode(), 0xffff);
            assert!(matches!(Chip8CpuInstructions::decode(i), Chip8CpuInstructions::XXXXERRORINSTRUCTION));
        }
    }

    /// START OF THE EXECUTION TESTS
    #[test]
    fn sys_test() {
        // 0x0nnn Exit to System Call
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::SysAddr(0).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0);

        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::SysAddr(0xFA0).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0xFA0);

        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::SysAddr(0x0AF).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x0AF);
    }

    #[test]
    fn jpaddr_test() {
        // 0x1nnn Jump to Address
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::JpAddr(0).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0);

        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::JpAddr(0xABC).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0xABC);
    }

    // ** test moved up so it can be used later
    #[test]
    fn ld_vx_byte_test() {
        // 0x6xkk Set Vx = kk
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::LdVxByte(1, 0x12).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0x12);

        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::LdVxByte(2, 0x21).execute(&mut x);
        assert_eq!(x.registers.peek(2), 0x21);
    }

    #[test]
    fn sevxbyte_match_test() {
        // 0x3xkk Skip next instruction if Vx = kk.
        // The interpreter compares register Vx to kk,
        // and if they are equal, increments the program counter by 2.

        // test setup: Load value 0x84 into V1
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0x84);
        Chip8CpuInstructions::SeVxByte(1, 0x48).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0x84);
        Chip8CpuInstructions::SeVxByte(1, 0x84).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x204);
    }

    #[test]
    fn se_vx_vy_test() {
        // 0x4xkk Skip next instruction if Vx != kk
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x84);
        x.registers.poke(0x2, 0x84);
        // skip, compare 0x84 to 0x84
        Chip8CpuInstructions::SeVxVy(0x1, 0x2).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x204);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x84);
        x.registers.poke(0x2, 0x48);
        Chip8CpuInstructions::SeVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);
    }

    #[test]
    fn ld_vx_vy_test() {
        // 0x8xy0 Set value of Vy in Vx
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x01);
        x.registers.poke(0x02, 0x02);
        Chip8CpuInstructions::LdVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0x02);
    }

    #[test]
    fn or_vx_vy_test() {
        // 0x8xy1 Set Vx = Vx OR Vy
        //   0b0101 0000 (0x50)
        // | 0b0000 1010 (0x0A)
        //   0b0101 1010 (0x5A)
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0b01010000);
        x.registers.poke(0x02, 0b00001010);
        Chip8CpuInstructions::OrVxVy(1, 2).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0b01011010);
    }

    #[test]
    fn and_vx_vy_test() {
        // 0x8xy2 Set Vx = Vx AND Vy
        //   0b1111 1100 (0xFC)
        // & 0b1100 1010 (0xCA)
        //   0b1100 1000 (0xC8)
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0xFC);
        x.registers.poke(0x02, 0xCA);
        Chip8CpuInstructions::AndVxVy(1, 2).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0xC8);
    }

    #[test]
    fn xor_vx_vy_test() {
        // 0x8xy3 Set Vx = Vx XOR Vy
        //   0b1111 1100 (0xFC)
        // ^ 0b1100 1010 (0xCA)
        //   0b0011 0110 (0x36)
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0b11111100);
        x.registers.poke(0x02, 0b11001010);
        Chip8CpuInstructions::XorVxVy(1, 2).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0b00110110);
    }

    #[test]
    fn add_vx_vy_test() {
        // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
        //    T1             T2: Judgement Test
        //   0x01           0xFF
        // + 0x01           0x01
        //   0x02 F0        0x00 F1
        let mut x = Chip8Computer::new();
        x.registers.poke(0x0f, 00);
        x.registers.poke(0x01, 0x01);
        x.registers.poke(0x02, 0x01);
        Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(0xf), 0x00);
        assert_eq!(x.registers.peek(0x01), 0x02);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x0f, 0x00);
        x.registers.poke(0x01, 0xff);
        x.registers.poke(0x02, 0x01);
        Chip8CpuInstructions::AddVxVy(1, 2).execute(&mut x);
        assert_eq!(x.registers.peek(0xf), 1);
        assert_eq!(x.registers.peek(1), 0);
    }

    #[test]
    fn shr_vx_vy_test() {
        /*
        Set Vx = Vx SHR 1.

        If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. Then Vx is divided by 2.
        */
        let mut x = Chip8Computer::new();
        x.registers.poke(0x0f, 0x00);
        x.registers.poke(0x01, 0b00001000);
        x.registers.poke(0x02, 0b00000000);
        Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x);     // 0b0000 0010 (0x02) (Not Set)
        assert_eq!(x.registers.peek(1), 0b00000100);
        assert_eq!(x.registers.peek(0xf), 0);

        x = Chip8Computer::new();
        x.registers.poke(0x0f, 0x00);
        x.registers.poke(0x01, 0b00001001);
        Chip8CpuInstructions::ShrVxVy(0x1, 0x2).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0b00000100);
        assert_eq!(x.registers.peek(0xf), 1);
    }

    #[test]
    fn ldi_addr_test() {
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::LdIAddr(0x123).execute(&mut x);
        assert_eq!(x.registers.peek_i(), 0x123);
        assert_eq!(x.registers.peek_pc(), 0x202);
    }

    #[test]
    fn jp_v0addr_test() {
        let mut x = Chip8Computer::new();
        /// jump to I + nnn
        x.registers.poke(0x0, 0xff);
        Chip8CpuInstructions::JpV0Addr(0x100).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x1FF);
    }

    #[test]
    fn cls_test() {
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::CLS.execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);
        for i in 0..CHIP8_VIDEO_MEMORY {
            assert_eq!(x.video_memory.peek(i as u16), false);
        }
        // draw some thing to the video memory
        x.video_memory.poke(0x01, true);
        x.video_memory.poke(0x03, true);
        x.video_memory.poke(0x05, true);

        Chip8CpuInstructions::CLS.execute(&mut x);

        for i in 0..CHIP8_VIDEO_MEMORY {
            assert_eq!(x.video_memory.peek(i as u16), false);
        }
    }

    #[test]
    fn skip_next_instruction_ne_text() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0xf0);
        Chip8CpuInstructions::SneVxByte(0x1, 0x0f).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x204);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0xf0);
        Chip8CpuInstructions::SneVxByte(0x1, 0xf0).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);
    }

    #[test]
    fn addivx_test() {
        let mut x = Chip8Computer::new();
        x.registers.poke_i(0xabc);
        x.registers.poke(0x0, 0x10);
        Chip8CpuInstructions::AddIVx(0x0).execute(&mut x);
        assert_eq!(x.registers.peek_i(), 0xacc);
    }

    #[test]
    fn ldstvt_test() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0xf0);
        Chip8CpuInstructions::LdStVx(0x01).execute(&mut x);
        assert_eq!(x.sound_timer.current(), 0xf0);
        x.sound_timer.tick();
        x.sound_timer.tick();
        x.sound_timer.tick();
        assert_eq!(x.sound_timer.current(), 0xed);
    }

    #[test]
    fn rnd_vx_byte_text() {
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::RndVxByte(0x1, 0x0f).execute(&mut x);
        let new_value = x.registers.peek(0x1);
        assert!(new_value < 0x10);
    }

    #[test]
    fn add_vx_byte_test() {
        let mut x = Chip8Computer::new();
        // set a value in the register
        x.registers.poke(0x01, 0xab);
        // add 0x10 to register
        Chip8CpuInstructions::AddVxByte(0x1, 0x10).execute(&mut x);
        assert_eq!(x.registers.peek(1), 0xbb);
    }

    #[test]
    fn sub_vx_vy_test() {
        let mut x = Chip8Computer::new();
        // load values in 2 registers
        x.registers.poke(0x1, 0x10);
        x.registers.poke(0x2, 0x08);
        Chip8CpuInstructions::SubVxVy(0x1, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(0xf), 1);
        assert_eq!(x.registers.peek(0x1), 0x8);
        assert_eq!(x.registers.peek_pc(), 0x202);
    }

    #[test]
    fn sne_vx_vy_test() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0x10);
        x.registers.poke(0x2, 0x10);
        Chip8CpuInstructions::SneVxVy(0x1, 0x2).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0x10);
        x.registers.poke(0x2, 0x00);
        Chip8CpuInstructions::SneVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x204)
    }

    #[test]
    fn ld_dt_vx_test() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0x10);
        Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
        assert_eq!(x.delay_timer.current(), 0x10);
        for i in 0..0x20 {
            x.delay_timer.tick();
        }
        assert_eq!(x.delay_timer.current(), 0);
    }

    #[test]
    fn ld_vx_dt_test() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0xf0);
        Chip8CpuInstructions::LdDtVx(0x1).execute(&mut x);
        x.delay_timer.tick();
        x.delay_timer.tick();
        x.delay_timer.tick();
        assert_eq!(x.delay_timer.current(), 0xed);
    }

    #[test]
    fn subn_vx_vy_test() {
        // This instruction subtracts the value in
        // register Vx from the value in register Vy and stores the result in register Vx.
        // The subtraction is performed as follows: Vx = Vy - Vx. If Vy is less than Vx,
        // the result will wrap around (due to the 8-bit nature of the registers).
        // The carry flag (VF) is set to 1 if there is no borrow (i.e., Vy is greater
        // than or equal to Vx), and it is set to 0 if there is a borrow.

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0xa0);
        x.registers.poke(0x2, 0xab);
        Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);
        // expect the result to be 0x0b
        assert_eq!(x.registers.peek(0x1), 0x0b);
        // expect the vf register to be set to 1 as there was overflow
        assert_eq!(x.registers.peek(0xf), 0x1);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0xab);
        x.registers.poke(0x02, 0xa0);
        Chip8CpuInstructions::SubnVxVy(0x1, 0x2).execute(&mut x);

        // expect the result to be 11110101, -0xB, -11, 245, 0xF5
        assert_eq!(x.registers.peek(0x1), 0xf5);
        assert_eq!(x.registers.peek(0xf), 0x0);
    }

    #[test]
    fn shl_vx_vy_test_1() {
        // 8xyE - SHL Vx {, Vy}
        // Set Vx = Vx SHL 1.
        //
        // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise to 0. Then Vx is multiplied by 2.

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0b00100000);
        Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
        assert_eq!(x.registers.peek(0x1), 0b01000000);
        assert_eq!(x.registers.peek(0xf), 0x0);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x1, 0b10101010);
        Chip8CpuInstructions::ShlVxVy(0x1, 0x1).execute(&mut x);
        assert_eq!(x.registers.peek(0x1), 0b01010100);
        assert_eq!(x.registers.peek(0xf), 0x1);
    }

    #[test]
    fn ld_f_vx_test() {
        // Fx29 - LD F, Vx
        // Set I = location of sprite for digit Vx.
        //
        // The value of I is set to the location for the hexadecimal sprite corresponding to the value of Vx. See section 2.4, Display, for more information on the Chip-8 hexadecimal font.
        let mut x = Chip8Computer::new();
        // target_sprite = 2
        // target_offset = 0x5
        x.registers.poke(0x1, 0x2);
        Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);

        assert_eq!(x.registers.peek_i(), 10);

        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x06);
        Chip8CpuInstructions::LdFVx(0x1).execute(&mut x);
        assert_eq!(x.registers.peek_i(), 30);
    }

    #[test]
    fn ld_b_vx_test() {
        // Fx33 - LD B, Vx
        // Store BCD representation of Vx in memory locations I, I+1, and I+2.
        //
        // The interpreter takes the decimal value of Vx, and places the hundreds digit
        // in memory at location in I, the tens digit at location I+1,
        // and the ones digit at location I+2.
        let mut x = Chip8Computer::new();

        // load the value 123 (0x7b)
        x.registers.poke(0x1, 0x7b);
        x.registers.poke_i(0x500);
        Chip8CpuInstructions::LdBVx(0x1).execute(&mut x);
        assert_eq!(x.memory.peek(0x500), 0x1);
        assert_eq!(x.memory.peek(0x501), 0x2);
        assert_eq!(x.memory.peek(0x502), 0x3);
    }

    #[test]
    fn ld_i_vx_test() {
        // Store registers V0 through Vx in memory starting at location I.
        //
        // The interpreter copies the values of registers V0 through Vx into memory,
        // starting at the address in I.
        let mut x = Chip8Computer::new();

        // Load Registers.
        let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];
        for (idx, val) in to_load.iter().enumerate() {
            x.registers.poke(idx as u8, *val);
        }
        x.registers.poke_i(0x500);

        Chip8CpuInstructions::LdIVx(to_load.len() as u8).execute(&mut x);

        // Verify the values are in memory from 0x500 to 0x507
        for (idx, value) in to_load.iter().enumerate() {
            assert_eq!(x.memory.peek(0x500 + idx as u16), *value);
        }
    }

    #[test]
    fn ld_vx_i_test() {
        // Read registers V0 through Vx from memory starting at location I.
        //
        // The interpreter reads values from memory starting at location I into registers V0 through Vx.

        let mut x = Chip8Computer::new();

        let base_offset = 0x500;
        let to_load = [0xab, 0xba, 0xca, 0xca, 0xbe, 0xef];

        // start by setting values in memory
        for (idx, memory) in to_load.iter().enumerate() {
            let target_address = base_offset + idx;
            let target_value = *memory;
            x.memory.poke(target_address as u16, target_value);
        }
        // where to load from
        x.registers.poke_i(0x500);
        // how much to load
        x.registers.poke(0x6, to_load.len() as u8);

        // then copying them values memory to registers
        Chip8CpuInstructions::LdVxI(0x6).execute(&mut x);

        // now check that we have the right values in our registers
        for (idx, value) in to_load.iter().enumerate() {
            assert_eq!(x.registers.peek(idx as u8), *value);
        }
    }

    #[test]
    fn Sknkpvx_test() {
        // ExA1 - SKNP Vx
        // Skip next instruction if key with the value of Vx is not pressed.
        //
        // Checks the keyboard,
        // and if the key corresponding to the value of Vx is currently in the up position,
        // PC is increased by 2.
        let mut x = Chip8Computer::new();
        x.keypad.push_key(0x5);
        x.registers.poke(0x1, 0x5);
        Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x202);
        x.keypad.release_key(0x5);
        Chip8CpuInstructions::SnkpVx(0x1).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x206);
    }

    #[test]
    fn skpvx_test() {
        // Ex9E - SKP Vx
        // Skip next instruction if key with the value of Vx is pressed.
        //
        // Checks the keyboard, and if the key corresponding to the value of Vx is currently in the down position, PC is increased by 2.
        let mut x = Chip8Computer::new();
        x.keypad.push_key(0x5);
        x.registers.poke(0x1, 0x5);
        Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x204);

        x.keypad.release_key(0x5);
        Chip8CpuInstructions::SkpVx(0x1).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x206);
    }

    #[test]
    fn draw_nibble_vx_vy_n_test() {
        let mut x = Chip8Computer::new();
        let x_register = 0x1;
        let y_register = 0x2;
        let x_offset = 1;
        let y_offset = 2;
        let char_offset = 0x0A;

        // now lets set the X and Y to 1,2
        x.registers.poke(x_register, x_offset);
        x.registers.poke(y_register, y_offset);
        x.registers.poke_i(char_offset);
        // we are using 5 rows.
        Chip8CpuInstructions::DrawVxVyNibble(x_register, y_register, 5).execute(&mut x);

        // now check that video memory has the values at
        // 1,2->1,9
        // 2,2->2,9
        // 3,2->3,9
        // 4,2->4,9
        // 5,2->5,9
        //  let byte_to_check = CHIP8FONT_0[0];
        for row_in_sprite in 0..5 {
            let row_data = CHIP8FONT_2[row_in_sprite];
            for bit_in_byte in 0..8 {
                let data_offset = (x_offset
                    as u16 + row_in_sprite as u16) * 64 + (bit_in_byte + y_offset) as u16;
                let real_bit_in_byte = 7 - bit_in_byte;
                let shifted_one = 0x01 << real_bit_in_byte;
                let one_shift_set = (shifted_one & row_data) > 0;
                debug!("ROWDATA = \t\t[{row_data:08b}]\tBIT IN BYTE = \t[{bit_in_byte}]\tONE_SHIFT_SET = [{one_shift_set}]\tSHIFTED ONE = [{shifted_one:08b}]");
                debug!("DATA_OFFSET FOR SOURCE DATA {}x{} is {} / offset by {}x{} and should be {} working with byte {:08b}",
                         bit_in_byte, row_in_sprite, data_offset, x_offset, y_offset, one_shift_set, row_data);
            }
        }
    }

    #[test]
    fn sub_test() {
        // 2nnn
        // Call a subroutine at 2nnn
        let mut x = Chip8Computer::new();
        Chip8CpuInstructions::CallAddr(0x124).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x124);
        assert_eq!(x.stack.depth(), 1);
        Chip8CpuInstructions::CallAddr(0x564).execute(&mut x);
        assert_eq!(x.registers.peek_pc(), 0x564);
        assert_eq!(x.stack.depth(), 2);
    }

    #[test]
    fn ret_test() {
        // SETUP
        // Return from a subroutine.
        let mut x = Chip8Computer::new();
        x.stack.push(&0x132);
        x.stack.push(&0xabc);
        // EXECUTE
        Chip8CpuInstructions::RET.execute(&mut x);
        // VERIFY
        assert_eq!(x.registers.peek_pc(), 0xabc);
        assert_eq!(x.stack.depth(), 1);
        // EXECUTE
        Chip8CpuInstructions::RET.execute(&mut x);
        // VERIFY
        assert_eq!(x.registers.peek_pc(), 0x132);
        assert_eq!(x.stack.depth(), 0);
    }


    #[test]
    fn ldvxk_test() {
        // SETUP
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x01);
        Chip8CpuInstructions::LdVxK(0x1).execute(&mut x);
        assert!(matches!(x.state, WaitingForKey));
    }

    #[test]
    fn series8xy4_corex_tests() {
        /// 8xy4
        /// Set Vx = Vx + Vy
        /// Set VF=1 if Carry
        ///

        // 1 + 1
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x01);
        x.registers.poke(0x02, 0x01);
        Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0x02);
        assert_eq!(x.registers.peek(0x0f), 0x00);

        // 255+1
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0xff);
        x.registers.poke(0x02, 0x01);
        Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0x00);
        assert_eq!(x.registers.peek(0x0f), 0x01);

        // 128+192
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 128);
        x.registers.poke(0x02, 192);
        Chip8CpuInstructions::AddVxVy(0x01, 0x02).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 64);
        assert_eq!(x.registers.peek(0x0f), 1);
    }

    #[test]
    fn series8xy6_corex_tests() {
        // 8xy6 - SHR Vx {, Vy}
        // Set Vx = Vx SHR 1.
        //
        // If the least-significant bit of Vx is 1, then VF is set to 1,
        // otherwise 0. Then Vx is divided by 2.
        let mut x = Chip8Computer::new();
        // 0b10101010 -> 0b01010101
        x.registers.poke(0x01, 0b10101010);
        x.registers.poke(0x0f, 0x0);

        Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0b01010100);
        assert_eq!(x.registers.peek(0x0f), 1);

        Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0b10101000);
        assert_eq!(x.registers.peek(0x0f), 0x00);

        Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0b01010000);
        assert_eq!(x.registers.peek(0x0f), 0x01);

        Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0b10100000);
        assert_eq!(x.registers.peek(0x0f), 0x00);

        Chip8CpuInstructions::ShlVxVy(0x01, 0x00).execute(&mut x);
        assert_eq!(x.registers.peek(0x01), 0b01000000);
        assert_eq!(x.registers.peek(0x0f), 0x01);
    }

    #[test]
    fn random_produces_different_numbers() {
        let mut x = Chip8Computer::new();
        x.registers.poke(0x01, 0x00);
        let first_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01);
        let second_number = Chip8CpuInstructions::RndVxByte(0x01, 0xff).execute(&mut x).registers.peek(0x01);
        assert_ne!(first_number, second_number);
    }
}
