use log::{error, info};

use super::helpers::Splitu16;

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct GbCore<'rom> {
    cpu: Cpu,
    memory: Memory<8, 8>,
    screen: Screen,
    rom: Option<&'rom [u8]>,
}



#[derive(PartialEq, Eq, Clone, Debug)]
struct Screen;

#[derive(PartialEq, Eq, Clone, Debug)]
struct Cpu {
    registers: Registers,
    
}

#[derive(PartialEq, Eq, Clone, Debug)]
struct Memory<const W: usize, const V: usize> {
    wram: [u8; W],
    vram: [u8; V],
}

impl<const W: usize, const V: usize> Default for Memory<W, V> {
    fn default() -> Self {
        Memory::<W, V> {
            wram: [0x0; W],
            vram: [0x0; V],
        }
    }
}

#[allow(non_snake_case)]
#[derive(PartialEq, Eq, Clone, Debug)]
struct Registers {
    AF: Splitu16,
    BC: Splitu16,
    DE: Splitu16,
    HL: Splitu16,
    SP: u16,
    PC: u16,
}


impl<'rom> GbCore<'rom> {

    
    pub fn new() -> GbCore<'rom> {
        GbCore {
            cpu: Cpu {
                registers: Registers {
                    AF: Splitu16::from(0x01B0),
                    BC: Splitu16::from(0x0013),
                    DE: Splitu16::from(0x00D8),
                    HL: Splitu16::from(0x014D),
                    SP: 0xFFFE,
                    PC: 0x0100,
                    
                }
            },
            memory: Memory::<8, 8>::default(),
            screen: Screen,
            rom: None,
        }
    }
    
    pub fn load(&mut self, rom: &'rom [u8]) -> &mut GbCore<'rom> {
        self.rom = Some(rom);
        self
    }

    pub fn fetch(&self) -> u8 {
        debug_assert!(self.rom.is_some());

        self.rom.unwrap()[usize::from(self.cpu.registers.PC)]
    }

    pub fn fetch_op(&mut self) -> TranslatedOpCode {
        let op = self.fetch();
        match op & 0xF0 {
            0x00 => 
                match op & 0xF {
                    0x0 => TranslatedOpCode::Noop,
                    _ => {error!("Unkown Op Code {:#04X}", op); todo!();}
                },
            0x60 =>
                match op & 0xF {
                    0x6 => TranslatedOpCode::LoadHFromHL,
                    _ => {error!("Unkown Op Code {:#04X}", op); todo!();} 
                }
            0xC0 =>
                match op & 0xF {
                    0x3 => {
                        let immediate1 = self.inc_pc().fetch();
                        let immediate2 = self.inc_pc().fetch();
                        TranslatedOpCode::Jmp(immediate1, immediate2)
                    },
                    0xC => {
                        let address1 = self.inc_pc().fetch();
                        let address2 = self.inc_pc().fetch();
                        TranslatedOpCode::CallIfZero(address1, address2)
                    }
                    0xE => {
                        let immediate = self.inc_pc().fetch();
                        TranslatedOpCode::AccummulatorAdd(immediate)
                    }
                    _ => {error!("Unkown Op Code {:#04X}", op); todo!();}
                },
            _ => {
                error!("Unknown Op Code {:#04X}", op);
                todo!();
            }
        }
    }

    pub fn inc_pc(&mut self) -> &mut GbCore<'rom> {
        self.cpu.registers.PC += 1;
        self
    }

    pub fn test(&mut self) {
        let mut counter = 1;
        loop {
            let op = self.fetch_op();
            info!("Op {}: {:#?}", counter, op);
            self.inc_pc();
            counter += 1;
        }
        
        
    }
}

impl Default for GbCore<'_> {
    fn default() -> Self {
        Self::new()
    }
}

#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug)]
pub enum TranslatedOpCode {

    // 0x00-0x0F
    Noop,
    LoadBC(u8, u8),
    StoreAatBC,
    IncBC,
    IncB,
    DecB,
    LoadB(u8),
    LeftShiftA,
    StoreSP(u8, u8),
    AddBCtoHL,
    LoadAFromBC,
    DecBC,
    IncC,
    DecC,
    LoadC(u8),
    RightShiftA,

    //0x10-0x1F
    Stop,
    LoadDE(u8, u8),
    StoreAatDE,
    IncDE,
    IncD,
    DecD,
    LoadD(u8),
    LeftShiftAWithCary,
    JmpForward(u8),
    AddDEtoHL,
    LoadAFromDE,
    DecDE,
    IncE,
    DecE,
    LoadE(u8),
    RightShiftAwithCarry,

    //0x20-0x2F
    JmpForwardIfNotZero(u8),
    LoadHL(u8, u8),
    StoreAatHLandInc,
    IncHL,
    IncH,
    DecH,
    LoadH(u8),
    DecimalAdjustAfterAddition,
    JmpForwardIfZero(u8),
    AddHLtoHL,
    LoadAFromHLandInc,
    DecHL,
    IncL,
    DecL,
    LoadL(u8),
    FlipA,

    //0x60-0x6F
    //..skip a few
    LoadHFromHL,

    //0xC0-0xCF
    Jmp(u8, u8),
    CallIfZero(u8, u8),
    AccummulatorAdd(u8),
}