use log::{debug, error};
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::instructions::Chip8CpuInstructions::XXXXERRORINSTRUCTION;
use crate::chip8::keypad::Keypad;
use crate::chip8::registers::Chip8Registers;
use crate::chip8::sound_timer::SoundTimer;
use crate::chip8::stack::Chip8Stack;
use crate::chip8::util::InstructionUtil;
use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT};

use super::{
    cpu_states::Chip8CpuStates, instructions::Chip8CpuInstructions, system_memory::Chip8SystemMemory, video::Chip8Video,
};

const STACK_POINTER_DEFAULT: i16 = 0x100;

#[derive(Clone)]
pub struct Chip8Computer {
    pub memory: Chip8SystemMemory,
    pub registers: Chip8Registers,
    pub sound_timer: SoundTimer,
    pub delay_timer: DelayTimer,
    pub video_memory: Chip8Video,
    pub state: Chip8CpuStates,
    pub keypad: Keypad,
    pub stack: Chip8Stack
}
impl Default for Chip8Computer {
    fn default() -> Self {
        Self {
            memory: Chip8SystemMemory::default(),
            video_memory: Chip8Video::default(),
            registers: Chip8Registers::default(),
            sound_timer: SoundTimer::new(),
            delay_timer: DelayTimer::new(),
            state: Chip8CpuStates::default(),
            keypad: Keypad::default(),
            stack: Chip8Stack::default()
        }
    }
}

impl Chip8Computer {
    pub fn reset(&mut self) -> Self{
        Self::default()
    }

    pub fn dump_keypad_to_string(&self) -> String {
        self.keypad.format_as_string()
    }

    pub fn dump_registers_to_string(&self) -> String {
        self.registers.format_as_string()
    }

    pub fn dump_video_to_string(&self) -> String {
        self.video_memory.format_as_string()
    }

    pub fn new_with_program(new_program: Box<Vec<u16>>) -> Self {
        let mut working = Chip8Computer::new();
        for i in 0..new_program.len() {
            let high_byte = (new_program[i as usize] >> 8) as u8;
            let low_byte = (new_program[i] & 0xff) as u8;
            let base_offset = i * 2;
            working.memory.poke(base_offset as u16, high_byte);
            working.memory.poke((base_offset + 1) as u16, low_byte);
        }
        working
    }
    pub fn new() -> Self {
        Chip8Computer::default()
    }

    pub fn load_bytes_to_memory(&mut self, offset: u16, to_load: Box<Vec<u8>>) {
        let total_len = to_load.len() as u16;
        for current_index in 0..total_len {
            let new_value = to_load[current_index as usize];
            let new_location = current_index + offset;
            self.memory.poke(new_location, new_value);
        }
    }

    pub fn step_system(&mut self) -> &mut Chip8Computer {
        debug!("Stepping System 1 Step");
        // read the next instruction

        let mut working_instruction: u16 = 0b0000000000000000;
        let start_pc = self.registers.peek_pc();
        let high_byte = (self.memory.clone().peek(start_pc) as u16).rotate_left(8);
        let low_byte = self.memory.clone().peek(start_pc + 1) as u16;
        let result = high_byte | low_byte;
        let decoded_instruction =
            Chip8CpuInstructions::decode(result);
        // todo: THIS IS BAD AND IS A SIDE EFFECT
        decoded_instruction.execute(self);

        match self.state {
            Chip8CpuStates::WaitingForInstruction => {
                self.sound_timer.tick();
                self.delay_timer.tick();
            }
            Chip8CpuStates::WaitingForKey => {
                println!("waiting for a key press...");
            }
            _ => {}
        }
        self
    }
}

#[cfg(test)]
mod test {
    use rand::random;
    use crate::constants::CHIP8_VIDEO_MEMORY;
    use super::*;

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

}

