/// Registers.  numbered 1-16 publicly.
/// Privately using zero base array so -1 to shift from pub to priv.
#[derive(Clone, Copy)]
pub struct Chip8Registers {
    registers: [u8; 16],
    i_register: u16,
    pc: u16,
}

impl Chip8Registers {
    pub fn advance_pc(&mut self) {
        self.pc += 2;
    }
}

impl Default for Chip8Registers {
    fn default() -> Self {
        Chip8Registers {
            registers: [0x00; 16],
            i_register: 0x00,
            pc: 0x200,
        }
    }
}

impl Chip8Registers {
    pub fn reset(&mut self) {
        self.registers = [0x00; 16];
        self.i_register = 0x00;
        self.pc = 0x200;
    }

    pub fn poke_pc(&mut self, new_pc: u16) {
        self.pc = new_pc
    }
    pub fn peek_i(&self) -> u16 {
        self.i_register
    }

    pub fn poke_i(&mut self, new_value: u16) {
        self.i_register = new_value;
    }
    pub fn peek(&self, register_number: u8) -> u8 {
        self.registers[register_number as usize]
    }

    pub fn poke(&mut self, register_number: u8, value: u8) {
        if register_number > 0xf {
            panic!("INVALID REGISTER");
        }
        self.registers[register_number as usize] = value;
    }

    pub fn peek_pc(&self) -> u16 {
        self.pc
    }

    pub fn set_pc(&mut self, new_pc: u16) {
        self.pc = new_pc
    }

    pub fn format_as_string(&self) -> String {
        format!("Vx: 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}\n    0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}\nI: 0x{:04x}\tPC: 0x{:04x}",
                self.registers[0x0],
                self.registers[0x1],
                self.registers[0x2],
                self.registers[0x3],
                self.registers[0x4],
                self.registers[0x5],
                self.registers[0x6],
                self.registers[0x7],
                self.registers[0x8],
                self.registers[0x9],
                self.registers[0xa],
                self.registers[0xb],
                self.registers[0xc],
                self.registers[0xd],
                self.registers[0xe],
                self.registers[0xf],
                self.i_register,
                self.pc
        )
    }
}

#[cfg(test)]
mod test {
    use rand::random;
    use crate::chip8::registers::Chip8Registers;

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

    #[test]
    fn register_rw_test() {
        let mut x = Chip8Registers::default();
        x.poke(0x0, 0xff);
        x.poke(0x1, 0xab);
        assert_eq!(x.peek(0x0), 0xff);
        assert_eq!(x.peek(0x1), 0xab);
    }

    #[test]
    fn pc_test() {
        let mut x = Chip8Registers::default();
        x.set_pc(0x300);
        assert_eq!(x.peek_pc(), 0x300);
    }

    #[test]
    #[should_panic]
    fn invalid_register() {
        let mut x = Chip8Registers::default();
        x.poke(0x10, 0xff);
    }

    #[test]
    fn format_as_string_looks_right() {
        let mut x = Chip8Registers::default();
        for i in 0..0x10 {
            x.registers[i] = i as u8;
        }
        x.pc = 0xabc;
        x.i_register = 0xcab;
        let result_string = x.format_as_string();
        assert_eq!(result_string, String::from("Vx: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07\n    0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f\nI: 0x0cab\tPC: 0x0abc"));
    }

    #[test]
    fn reset_clears_registers() {
        let mut x = Chip8Registers::default();

        for register in 0..0x10 {
            x.registers[register] = random::<u8>();
        }
        x.reset();
        for register in 0..0x10 {
            assert_eq!(x.registers[register], 0x00);
        }
    }
}