pub struct InstructionUtil {}

impl InstructionUtil {
    pub fn byte_to_bools(to_convert: u8) -> [bool; 8] {
        let mut return_values = [false; 8];
        for i in 0..8 {
            let new_value = to_convert >> i & 0x1 == 1;
            return_values[i as usize] = new_value;
        }
        return_values
    }

    pub fn bools_to_byte(to_convert: [bool; 8]) -> u8 {
        let mut return_value = 0u8;
        for i in 0..to_convert.len() {
            let new_bit = 0x1 << i;
            if to_convert[i] {
                return_value = return_value | new_bit
            }
        }
        return_value
    }

    pub fn split_bytes(to_split: u16) -> (u8, u8) {
        let high = to_split.rotate_left(8) as u8;
        let low = (to_split & 0xff) as u8;

        (high, low)
    }

    pub fn join_bytes(high: u8, low: u8) -> u16 {
        let result = (high as u16) << 8 | low as u16;
        result
    }

    // nnn or addr - A 12-bit value, the lowest 12 bits of the instruction
    pub fn read_addr_from_instruction(instruction_to_read_from: u16) -> u16 {
        instruction_to_read_from & 0x0FFF
    }

    // n or nibble - A 4-bit value, the lowest 4 bits of the instruction
    pub fn read_nibble_from_instruction(instruction_to_read_from: u16) -> u16 {
        instruction_to_read_from & 0x000F
    }

    // x - A 4-bit value, the lower 4 bits of the high byte of the instruction
    pub fn read_x_from_instruction(instruction_to_read_from: u16) -> u16 {
        (instruction_to_read_from & 0x0F00).rotate_right(8)
    }

    // y - A 4-bit value, the upper 4 bits of the low byte of the instruction
    pub fn read_y_from_instruction(instruction_to_read_from: u16) -> u16 {
        (instruction_to_read_from & 0x00F0).rotate_right(4)
    }

    // kk or byte - An 8-bit value, the lowest 8 bits of the instruction
    pub fn read_byte_from_instruction(instruction_to_read_from: u16) -> u16 {
        (instruction_to_read_from & 0x00FF)
    }

    pub fn read_upper_byte_lower_nibble(to_read_from: u16) -> u16 {
        (to_read_from & 0x0f00) >> 8
    }
}

#[cfg(test)]
mod test {
    use super::*;

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

    #[test]
    fn split_bytes() {
        // from 0xABCD we should have AB high, CD low
        let (low, high) = InstructionUtil::split_bytes(0xabcd);
        assert_eq!(low, 0xAB);
        assert_eq!(high, 0xCD);
    }

    #[test]
    fn join_bytes() {
        // from 0xAB low and 0xCD high we get 0xABCD
        let merged = InstructionUtil::join_bytes(0xcd, 0xab);
        assert_eq!(merged, 0xcdab);
    }

    #[test]
    fn read_from_instruction() {
        // from 0xABCD
        let source = 0xABCD;
        assert_eq!(InstructionUtil::read_addr_from_instruction(source), 0xBCD);
        assert_eq!(InstructionUtil::read_nibble_from_instruction(source), 0xD);
        assert_eq!(InstructionUtil::read_x_from_instruction(source), 0xB);
        assert_eq!(InstructionUtil::read_y_from_instruction(source), 0xC);
        assert_eq!(InstructionUtil::read_byte_from_instruction(source), 0xCD);
    }

    #[test]
    fn ubln() {
        // from 0xABCD we should see B
        assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0xABCD), 0xB);
        assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0123), 0x1);
        assert_eq!(InstructionUtil::read_upper_byte_lower_nibble(0x0000), 0x0);
    }

    #[test]
    fn byte_to_bool_changes() {
        assert_eq!(InstructionUtil::byte_to_bools(0b00000000), [false, false, false, false, false, false, false, false]);
        assert_eq!(InstructionUtil::byte_to_bools(0b11111111), [true, true, true, true, true, true, true, true]);
        assert_eq!(InstructionUtil::byte_to_bools(0b11001100), [false, false, true, true, false, false, true, true]);
        assert_eq!(InstructionUtil::byte_to_bools(0b11110000), [false, false, false, false, true, true, true, true]);
        assert_eq!(InstructionUtil::bools_to_byte([false, false, false, false, false, false, false, false]), 0b00000000);
        assert_eq!(InstructionUtil::bools_to_byte([true, true, true, true, true, true, true, true]), 0b11111111);
        assert_eq!(InstructionUtil::bools_to_byte([false, false, true, true, false, false, true, true]), 0b11001100);
        assert_eq!(InstructionUtil::bools_to_byte([false, false, false, false, true, true, true, true]), 0b11110000);
    }
}
