pub enum Chip8CpuInstructions {
    SysAddr(i16),                  // 0x0nnn Exit to System Call
    CLS,                           // * 0x00E0 Clear Screen
    RET,                           // 0x00EE Return from Subroutine
    JpAddr(i16),                   // 0x1nnn Jump to Address
    CallAddr(i16),                 // 0x2nnn Call Subroutine
    SeVxByte(i16, i16),            // 0x3xkk Skip next instruction if Vx = kk.
    SneVxByte(i16, i16),           // 0x4xkk Skip next instruction if Vx != kk
    SeVxVy(u16, u16),              // 0x5xy0 Skip next instruction if Vx == Vy
    LdVxByte(u16, u16),            // * 0x6xkk Set Vx = kk
    AddVxByte(u16, u16),           // 0x7xkk Set Vx = Vx + kk
    LdVxVy(u16, u16),              // 0x8xy0 Set value of Vy in Vx
    OrVxVy(u16, u16),              // 0x8xy1 Set Vx = Vx OR Vy
    AndVxVy(u16, u16),             // 0x8xy2 Set Vx = Vx AND Vy
    XorVxVy(u16, u16),             // 0x8xy3 Set Vx = Vx XOR Vy
    AddVxVy(u16, u16),             // 0x8xy4 Set Vx = Vx + Vy (SET VF on Carry)
    SubVxVy(u16, u16),             // 0x8xy5 Set Vx = Vx - Vy (Set VF NOT Borrow)
    ShrVxVy(u16, u16),             // 0x8xy6 Set Vx = Vx SHR 1 (Shift Rotated Right 1)
    SubnVxVy(u16, u16),            // 0x8xy7 Set Vx = Vy - Vx (Set VF NOT Borrow)
    ShlVxVy(u16, u16),             // 0x8xyE Shift Left
    SneVxVy(u16, u16),             // 0x9xy0 Skip next instruction if Vx != Vy
    LdIAddr(u16),                  // * 0xAnnn VI = nnn
    JpV0Addr(u16),                 // 0xBnnn Jump to nnn+V0
    RndVxByte(u16, u16),           // 0xCxkk Vx = random byte AND kk
    DrawVxVyNibble(u16, u16, u16), // * 0xDxyn Display N byte sprite starting at Vx to Vy
    SkpVx(u16),                    // 0xE09E Skip next instruction if key in Vx pressed
    SnkpVx(u16),                   // 0xE0A1 Skip next instruction if key in Vx NOT pressed
    LdVxDt(u16),                   // 0xFx07 Set Vx = Delay timer
    LdVxK(u16),                    // 0xFx0A Wait for key, put in Vx
    LdDtVx(u16),                   // 0xFx15 Set Delay Timer
    LdStVx(u16),                   // 0xFx18 Set Sount Timer
    AddIVx(u16),                   // 0xFx1E I = I + Vx
    LdFVu(u16),                    // 0xFx29 Set I = Location of sprite for Digit Vx
    LdBVx(u16),                    // 0xFx33 Store BCD of Vx in I, I+1, I+2
    LdIVx(u16),                    // 0xFx55 Store V0 to Vx in memory starting at I
    LdVxI(u16),                    // 0xFx65 Load V0 to Vx in memory starting at I
    XXXXERRORINSTRUCTION,
}

#[derive(Clone)]
struct Chip8SystemState {
    pub video_memory: [bool; 512],
    pub memory: [u8; 4096],
    pub registers: [u8; 16],
    pub i: i16,
    pub pc: i16,
    pub sound_timer: i8,
    pub delay_timer: i8
}

trait Chip8Instruction {
    fn execute(&self, input: Chip8SystemState) -> Chip8SystemState;
}

impl Chip8Instruction for Chip8CpuInstructions {
    fn execute(&self, mut input: Chip8SystemState) -> Chip8SystemState {
        match self {
            Chip8CpuInstructions::SysAddr(new_address) => {
                let mut new_state = input.clone();
                new_state.pc = *new_address;
                new_state
            }
            Chip8CpuInstructions::CLS => {
                input.video_memory = [false; 512];
                input
            }
            Chip8CpuInstructions::RET => {
                input
            }
            Chip8CpuInstructions::JpAddr(new_address) => {
                input
            }
            Chip8CpuInstructions::CallAddr(_) => {
                input
            }
            Chip8CpuInstructions::SeVxByte(_, _) => {
                input
            }
            Chip8CpuInstructions::SneVxByte(_, _) => {
                input
            }
            Chip8CpuInstructions::SeVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::LdVxByte(register, byte) => {
                input.registers[*register as usize] = byte.to_be_bytes()[0];
                input
            }
            Chip8CpuInstructions::AddVxByte(_, _) => {
                input
            }
            Chip8CpuInstructions::LdVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::OrVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::AndVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::XorVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::AddVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::SubVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::ShrVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::SubnVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::ShlVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::SneVxVy(_, _) => {
                input
            }
            Chip8CpuInstructions::LdIAddr(new_index) => {
                input.i = *new_index as i16;
                input
            }
            Chip8CpuInstructions::JpV0Addr(_) => {
                input
            }
            Chip8CpuInstructions::RndVxByte(_, _) => {
                input
            }
            Chip8CpuInstructions::DrawVxVyNibble(x, y, n) => {
                // read nibble bytes from memory starting at I

                let mut did_change: bool = false;

                for draw_x in 0..*n {
                    let mut new_value = input.memory[(input.i + draw_x as i16) as usize];
                    for draw_y in 0..8 {
                    }
                }


                if did_change {
                    input.registers[0xF] = 1u8;
                } else {
                    input.registers[0xF] = 0u8;
                }
                input
            }
            Chip8CpuInstructions::SkpVx(_) => {
                input
            }
            Chip8CpuInstructions::SnkpVx(_) => {
                input
            }
            Chip8CpuInstructions::LdVxDt(_) => {
                input
            }
            Chip8CpuInstructions::LdVxK(_) => {
                input
            }
            Chip8CpuInstructions::LdDtVx(_) => {
                input
            }
            Chip8CpuInstructions::LdStVx(_) => {
                input
            }
            Chip8CpuInstructions::AddIVx(_) => {
                input
            }
            Chip8CpuInstructions::LdFVu(_) => {
                input
            }
            Chip8CpuInstructions::LdBVx(_) => {
                input

            }
            Chip8CpuInstructions::LdIVx(_) => {
                input
            }
            Chip8CpuInstructions::LdVxI(_) => {
                input
            }
            Chip8CpuInstructions::XXXXERRORINSTRUCTION => {
                input
            }
        }
    }
}