use log::{debug};
use crate::chip8::delay_timer::DelayTimer;
use crate::chip8::keypad::Keypad;
use crate::chip8::registers::Chip8Registers;
use crate::chip8::sound_timer::SoundTimer;
use crate::chip8::stack::Chip8Stack;

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

#[derive(Clone)]
pub struct Chip8Computer {
    pub num_cycles: i32,
    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 {
            num_cycles: 0,
            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.video_memory.reset();
        self.num_cycles = 0;
        self.registers.reset();
        self.delay_timer.reset();
        self.sound_timer.reset();
    }

    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: Vec<u16>) -> Self {
        let mut working = Chip8Computer::new();

        for (i, &word) in new_program.iter().enumerate() {
            let high = (word >> 8) as u8;
            let low = (word & 0xff) as u8;
            let base_offset = (i * 2) as u16;
            working.memory.poke(base_offset, high);
            working.memory.poke(base_offset + 1, low);
        }
        working
    }
    pub fn new() -> Self {
        Chip8Computer::default()
    }

    pub fn load_bytes_to_memory(&mut self, offset: u16, to_load: &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);
        }
        self.registers.set_pc(offset);
    }

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

    //    let mut working_instruction: u16 = 0b0000000000000000;
        let start_pc = self.registers.peek_pc();
        let high_byte = (local_memory.peek(start_pc) as u16).rotate_left(8);
        let low_byte = local_memory.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();
                self.video_memory.tick();
                self.num_cycles += 1;
            }
            Chip8CpuStates::WaitingForKey => {
                debug!("waiting for a key press...");
            }
            _ => {}
        }
        self
    }
}
