use crate::constants::{CHIP8_MEMORY_SIZE, CHIP8_REGISTER_COUNT};

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


#[derive(Clone, Copy)]
pub struct Chip8Computer {
    pub pc: u16,
    pub sp: u8,
    pub memory: Chip8SystemMemory,
    pub registers: [u8; 16],
    pub sound_timer: u8,
    pub delay_timer: u8,
    pub i_register: u16,
    pub video_memory: Chip8Video,
    pub state: Chip8CpuStates
}

impl Default for Chip8Computer {
    fn default() -> Self {
        Self {
            pc: 0x0200,
            sp: 0x00,
            memory: Chip8SystemMemory::default(),
            video_memory: Chip8Video::default(),
            registers: [0; CHIP8_REGISTER_COUNT as usize],
            sound_timer: 0xFF,
            delay_timer: 0xFF,
            i_register: 0x0000,
            state: Chip8CpuStates::WaitingForInstruction
        }
    }
}

impl Chip8Computer {
    pub fn new() -> Self {
        Chip8Computer::default()
    }

    pub fn step_system(mut self) -> Self {
        // read the next instruction

        let mut working_instruction: u16 = 0b0000000000000000;
        let high_byte = (self.memory.clone().peek(self.pc) as u16).rotate_left(8);
        let low_byte = self.memory.clone().peek(self.pc + 1) as u16;
        working_instruction = high_byte | low_byte;

        let decided_instruction =
            Chip8Computer::decode_instruction(self.clone(), working_instruction);


        match (self.state, decided_instruction) {
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SysAddr(target_address)) => {
                self.pc = target_address as u16;
            },
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CLS) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RET) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpAddr(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::CallAddr(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxByte(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxByte(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SeVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxByte(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxByte(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::OrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AndVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::XorVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SubnVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::ShlVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SneVxVy(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIAddr(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::JpV0Addr(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::RndVxByte(_, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::DrawVxVyNibble(_, _, _)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SkpVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::SnkpVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxDt(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxK(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdDtVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdStVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::AddIVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdFVu(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdBVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdIVx(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::LdVxI(_)) => todo!(),
            (Chip8CpuStates::WaitingForInstruction, Chip8CpuInstructions::XXXXERRORINSTRUCTION) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SysAddr(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::CLS) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::RET) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::JpAddr(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::CallAddr(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SeVxByte(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SneVxByte(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SeVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdVxByte(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::AddVxByte(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::OrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::AndVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::XorVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::AddVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SubVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::ShrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SubnVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::ShlVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SneVxVy(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdIAddr(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::JpV0Addr(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::RndVxByte(_, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::DrawVxVyNibble(_, _, _)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SkpVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::SnkpVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdVxDt(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdVxK(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdDtVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdStVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::AddIVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdFVu(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdBVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdIVx(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::LdVxI(_)) => todo!(),
            (Chip8CpuStates::ExecutingInstruction, Chip8CpuInstructions::XXXXERRORINSTRUCTION) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SysAddr(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::CLS) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::RET) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::JpAddr(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::CallAddr(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SeVxByte(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SneVxByte(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SeVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdVxByte(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::AddVxByte(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::OrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::AndVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::XorVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::AddVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SubVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::ShrVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SubnVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::ShlVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SneVxVy(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdIAddr(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::JpV0Addr(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::RndVxByte(_, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::DrawVxVyNibble(_, _, _)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SkpVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::SnkpVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdVxDt(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdVxK(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdDtVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdStVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::AddIVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdFVu(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdBVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdIVx(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::LdVxI(_)) => todo!(),
            (Chip8CpuStates::Error, Chip8CpuInstructions::XXXXERRORINSTRUCTION) => todo!(),
        }

        self.clone()
    }

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

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

    // x - A 4-bit value, the lower 4 bits of the high byte of the instruction
    pub fn read_x_from_instruction(&self, instruction_to_read_from: u16) -> u16 {
        (instruction_to_read_from & 0b0000111100000000).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(&self, instruction_to_read_from: u16) -> u16 {
        (instruction_to_read_from & 0b0000000011110000).rotate_right(4)
    }

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

    fn decode_instruction(self, to_read: u16) -> Chip8CpuInstructions {
        let mut decoded_instruction = Chip8CpuInstructions::XXXXERRORINSTRUCTION;

        let x_param = self.read_x_from_instruction(to_read);
        let y_param = self.read_y_from_instruction(to_read);
        let addr_param = self.read_addr_from_instruction(to_read);
        let byte_param = self.read_byte_from_instruction(to_read);
        let nibble_param = self.read_nibble_from_instruction(to_read);

        match to_read {
            0x00E0 => {
                // 00E0 - CLS
                // Clear the display.
                decoded_instruction = Chip8CpuInstructions::CLS;
            }
            0x00EE => {
                // 00EE - RET
                // Return from a subroutine.

                decoded_instruction = Chip8CpuInstructions::RET;
            }
            0x0000..=0x0FFF => {
                // 0nnn - SYS addr
                // Jump to a machine code routine at nnn.
                decoded_instruction = Chip8CpuInstructions::SysAddr(addr_param as i16);
            }
            0x1000..=0x1FFF => {
                // 1nnn - JP addr
                // Jump to location nnn.
                decoded_instruction = Chip8CpuInstructions::JpAddr(addr_param as i16);
            }
            0x2000..=0x2FFF => {
                // 2nnn - CALL addr
                // Call subroutine at nnn.
                decoded_instruction = Chip8CpuInstructions::CallAddr(addr_param as i16);
            }
            0x3000..=0x3FFF => {
                // 3xkk - SE Vx, byte
                // Skip next instruction if Vx = kk.
                decoded_instruction = Chip8CpuInstructions::SeVxByte(x_param as i16, byte_param as i16)
            }
            0x4000..=0x4FFF => {
                // 4xkk - SNE Vx, byte
                // Skip next instruction if Vx != kk.
                decoded_instruction = Chip8CpuInstructions::SneVxByte(x_param as i16, byte_param as i16);
            }
            0x5000..=0x5FF0 => {
                // 5xy0 - SE Vx, Vy
                // Skip next instruction if Vx = Vy.
                decoded_instruction = Chip8CpuInstructions::SeVxVy(x_param, y_param)
            }
            0x6000..=0x6FFF => {
                // 6xkk - LD Vx, byte
                // Set Vx = kk.
                decoded_instruction = Chip8CpuInstructions::LdVxVy(x_param, byte_param);
            }
            0x7000..=0x7FFF => {
                // ADD Vx, Byte
                decoded_instruction = Chip8CpuInstructions::AddVxByte(x_param, byte_param);
            }
            0x8000..=0x8FFE => {
                // 0x8000 Series
                let last_nibble = to_read | 0x8000;
                match last_nibble {
                    0x0 => {
                        // LD Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::LdVxVy(x_param, y_param);
                    }
                    0x1 => {
                        // OR Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::OrVxVy(x_param, y_param);
                    }
                    0x2 => {
                        // AND Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::AndVxVy(x_param, y_param);
                    }
                    0x3 => {
                        // XOR Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::XorVxVy(x_param, y_param);
                    }
                    0x4 => {
                        // AND Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::AndVxVy(x_param, y_param);
                    }
                    0x5 => {
                        // SUB Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::SubnVxVy(x_param, y_param);
                    }
                    0x6 => {
                        // SHR Vx, {, Vy }
                        decoded_instruction = Chip8CpuInstructions::ShrVxVy(x_param, y_param);
                    }
                    0x7 => {
                        // SUBN Vx, Vy
                        decoded_instruction = Chip8CpuInstructions::SubnVxVy(x_param, y_param);
                    }
                    0xE => {
                        // SHL Vx, {, Vy}
                        decoded_instruction = Chip8CpuInstructions::ShlVxVy(x_param, y_param);
                    }
                    _ => {
                        panic!("UNABLE TO DECODE 0x8000 SERIES INSTRUCTION");
                    }
                }
            }
            0x9000..=0x9FF0 => {
                // SNE Vx, Vy
                decoded_instruction = Chip8CpuInstructions::SneVxVy(x_param, y_param);
            }
            0xA000..=0xAFFF => {
                // LD I, Addr
                decoded_instruction = Chip8CpuInstructions::LdIAddr(addr_param);
            }
            0xB000..=0xBFFF => {
                decoded_instruction = Chip8CpuInstructions::JpV0Addr(addr_param);
                // JP V0, Addr
            }
            0xC000..=0xCFFF => {
                // RND Vx, byte
                decoded_instruction = Chip8CpuInstructions::RndVxByte(x_param, byte_param);
            }
            0xD000..0xDFFF => {
                // DRAW Vx, Vy, nibble
                decoded_instruction =
                    Chip8CpuInstructions::DrawVxVyNibble(x_param, y_param, nibble_param);
            }
            0xE09E..=0xEFA1 => {}
            _ => {
                panic!("UNABLE TO DECODE INSTRUCTION")
            }
        }

        return decoded_instruction;
    }
}
