use log::{trace};

use crate::constants::{CHIP8_MEMORY_SIZE};

pub const CHIP8_PROGRAM_LOAD_OFFSET: i32 = 0x200;
pub const CHIP8FONT_0: [u8; 5] = [0xF0, 0x90, 0x90, 0x90, 0xF0];
pub const CHIP8FONT_1: [u8; 5] = [0x20, 0x60, 0x20, 0x20, 0x70];
pub const CHIP8FONT_2: [u8; 5] = [0xF0, 0x10, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_3: [u8; 5] = [0xF0, 0x10, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_4: [u8; 5] = [0x90, 0x90, 0xF0, 0x10, 0x10];
pub const CHIP8FONT_5: [u8; 5] = [0xF0, 0x80, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_6: [u8; 5] = [0xF0, 0x80, 0xF0, 0x90, 0xF0];
pub const CHIP8FONT_7: [u8; 5] = [0xF0, 0x10, 0x20, 0x40, 0x40];
pub const CHIP8FONT_8: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0xF0];
pub const CHIP8FONT_9: [u8; 5] = [0xF0, 0x90, 0xF0, 0x10, 0xF0];
pub const CHIP8FONT_A: [u8; 5] = [0xF0, 0x90, 0xF0, 0x90, 0x90];
pub const CHIP8FONT_B: [u8; 5] = [0xE0, 0x90, 0xE0, 0x90, 0xE0];
pub const CHIP8FONT_C: [u8; 5] = [0xF0, 0x80, 0x80, 0x80, 0xF0];
pub const CHIP8FONT_D: [u8; 5] = [0xE0, 0x90, 0x90, 0x90, 0xE0];
pub const CHIP8FONT_E: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0xF0];
pub const CHIP8FONT_F: [u8; 5] = [0xF0, 0x80, 0xF0, 0x80, 0x80];

#[derive(Clone, Copy)]
pub struct Chip8SystemMemory {
    memory: [u8; CHIP8_MEMORY_SIZE as usize],
}

impl Default for Chip8SystemMemory {
    fn default() -> Self {

        let mut x = Chip8SystemMemory {
            memory: [0x00; CHIP8_MEMORY_SIZE as usize],
        };

        x.load_fonts_to_memory();
        x
    }
}
impl Chip8SystemMemory {
    
    pub fn new() -> Self {
        Chip8SystemMemory {
            memory: [0x00; CHIP8_MEMORY_SIZE as usize],
        }
    }

    pub fn peek(&self, address: u16) -> u8 {
        trace!("PEEK: {} / {}", address, self.memory[address as usize].clone());
        self.memory[address as usize]
    }

    pub fn poke(&mut self, address: u16, value: u8)  {
        trace!("POKE: {} / {} to {}", address, self.memory[address as usize], value);
        self.memory[address as usize] = value;
    }

    pub fn load_program(&mut self, program_to_load: Box<Vec<u8>>) {
        for load_index in 0..program_to_load.len() {
            self.poke((load_index + 0x200) as u16, program_to_load[load_index]);
        }
    }

    pub fn load_fonts_to_memory(&mut self) {
        let all_font_characters = [
            CHIP8FONT_0,
            CHIP8FONT_1,
            CHIP8FONT_2,
            CHIP8FONT_3,
            CHIP8FONT_4,
            CHIP8FONT_5,
            CHIP8FONT_6,
            CHIP8FONT_7,
            CHIP8FONT_8,
            CHIP8FONT_9,
            CHIP8FONT_A,
            CHIP8FONT_B,
            CHIP8FONT_C,
            CHIP8FONT_D,
            CHIP8FONT_E,
            CHIP8FONT_F,
        ];

        for (font_index, current_font) in all_font_characters.iter().enumerate() {
            for font_mem_offset in 0..=4 {
                let real_offset = font_index * 5 + font_mem_offset;
                self.poke(real_offset as u16, current_font[font_mem_offset]);
            }
        }
    }
}

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

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

    #[test]
    fn model_smoke() {
        let m = Chip8SystemMemory::default();
        for i in 0..5 {
            assert_eq!(m.peek(i), CHIP8FONT_0[i as usize]);
        }

        assert_eq!(m.peek((CHIP8_MEMORY_SIZE - 1) as u16), 0);
    }

    #[test]
    fn known_data_loaded_correctly() {
        let to_load = [ 0x01, 0x02, 0x03, 0x04, 0x05 , 0x06 ];
        let mut x = Chip8SystemMemory::default();

        for (index, value) in [1..10].iter().enumerate() {
            assert_ne!(x.peek(0), 0x01);
            x.poke(0, 0x01);
            assert_eq!(x.peek(0), 0x01);
        }
    }

    #[test]
    fn verify_load_program() {
        // first line of 1-chip-logo.ch8
        let program_to_load = [0x00e0, 0x6101, 0x6008, 0xa250, 0xd01f, 0x6010, 0xa25f, 0xd01f];

        let mut x = Chip8SystemMemory::new();

        
    }
}
