mod apu;
mod controllers;
mod cpu;
pub mod debug;
// pub mod debugger;
// pub mod header_menu;
// pub mod hex_view;
mod mem;
mod ppu;
#[cfg(test)]
mod test_roms;
pub use mem::{Mapped, Memory};

pub use cpu::{CPUMMRegisters, Cpu, CycleResult};
pub use ppu::{Color, PPU, PPUMMRegisters, RenderBuffer};
// #[cfg(not(target_arch = "wasm32"))]
// use tokio::io::AsyncReadExt as _;

use std::{fs::File, io::Read, path::Path};

use thiserror::Error;

use crate::{
    apu::APU,
    controllers::{ControllerState, Controllers},
    cpu::{ClockState, DmaState},
    debug::DebugLog,
    mem::{CpuMem, PpuMem},
};

#[derive(Error, Debug)]
pub enum NESError {
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("File loaded was invalid")]
    InvalidFile,
}
type Result<T> = std::result::Result<T, NESError>;

#[derive(Clone)]
pub struct NES {
    clock_count: usize,
    // memory: MemoryMap<CPUMMRegisters>,
    mapped: Mapped,
    cpu: Cpu,
    dma: DmaState,
    ppu: PPU,
    apu: APU,
    controller: Controllers,

    dbg_int: bool,
}

impl std::fmt::Debug for NES {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "NES Emulator")?;
        writeln!(
            f,
            "CPU: {{ a: {:02X}, x: {:02X}, y: {:02X}, flags: {:?}, sp: {:02X}, pc: {:04X} }}",
            self.cpu.a, self.cpu.x, self.cpu.y, self.cpu.status, self.cpu.sp, self.cpu.pc
        )?;
        writeln!(f, "  Decode {:X?}", self.cpu.clock_state)?;
        writeln!(f, "  Last instruction: {}", self.cpu.last_instruction)?;
        writeln!(f, "  Cycle: CPU={}, PPU={}", self.cpu.cycle, self.ppu.cycle)?;
        // writeln!(
        //     f,
        //     "MEM: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
        //     self.peek(self.cpu.pc),
        //     self.peek(self.cpu.pc + 1),
        //     self.peek(self.cpu.pc + 2),
        //     self.peek(self.cpu.pc + 3),
        //     self.peek(self.cpu.pc + 4),
        //     self.peek(self.cpu.pc + 5),
        //     self.peek(self.cpu.pc + 6),
        //     self.peek(self.cpu.pc + 7),
        //     self.peek(self.cpu.pc + 8),
        //     self.peek(self.cpu.pc + 9),
        // )?;
        // writeln!(
        //     f,
        //     "0x1F0: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
        //     self.peek(0x1F0),
        //     self.peek(0x1F1),
        //     self.peek(0x1F2),
        //     self.peek(0x1F3),
        //     self.peek(0x1F4),
        //     self.peek(0x1F5),
        //     self.peek(0x1F6),
        //     self.peek(0x1F7),
        //     self.peek(0x1F8),
        //     self.peek(0x1F9),
        //     self.peek(0x1FA),
        //     self.peek(0x1FB),
        //     self.peek(0x1FC),
        //     self.peek(0x1FD),
        //     self.peek(0x1FE),
        //     self.peek(0x1FF),
        // )?;
        // writeln!(
        //     f,
        //     "STACK: {:02X} {:02X} | {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
        //     self.peek(self.cpu.sp as u16 + 0xFF),
        //     self.peek(self.cpu.sp as u16 + 0x100),
        //     self.peek(self.cpu.sp as u16 + 0x101),
        //     self.peek(self.cpu.sp as u16 + 0x102),
        //     self.peek(self.cpu.sp as u16 + 0x103),
        //     self.peek(self.cpu.sp as u16 + 0x104),
        //     self.peek(self.cpu.sp as u16 + 0x105),
        //     self.peek(self.cpu.sp as u16 + 0x106),
        //     self.peek(self.cpu.sp as u16 + 0x107),
        //     self.peek(self.cpu.sp as u16 + 0x108),
        // )?;
        write!(f, "PPU: {:?}", self.ppu)?;
        Ok(())
    }
}

impl NES {
    #[cfg(feature = "fs")]
    pub fn load_nes_file(file: impl AsRef<Path>) -> Result<Self> {
        let mut raw = Vec::new();
        File::open(file)?.read_to_end(&mut raw)?;
        Self::load_nes_file_mem(&raw)
    }

    // #[cfg(not(target_arch = "wasm32"))]
    #[cfg(all(feature = "fs", feature = "tokio"))]
    pub async fn async_load_nes_file(file: impl AsRef<Path>) -> Result<Self> {
        use tokio::io::AsyncReadExt as _;

        let mut raw = Vec::new();
        tokio::fs::File::open(file)
            .await?
            .read_to_end(&mut raw)
            .await?;
        Self::load_nes_file_mem(&raw)
    }

    pub fn load_nes_file_mem(raw: &[u8]) -> Result<Self> {
        if &raw[0..4] != &[0x4E, 0x45, 0x53, 0x1A] {
            return Err(NESError::InvalidFile);
        }
        let mapper = Mapped::from_rom(raw);
        Ok(Self::from_mem(mapper))
    }

    fn from_mem(mapped: Mapped) -> Self {
        Self {
            dbg_int: false,

            clock_count: 0,
            mapped,
            cpu: Cpu::init(),
            dma: DmaState::Passive,
            // ppu: PPU::with_chr_rom(chr_rom, mapper),
            ppu: PPU::init(),
            apu: APU::init(),
            controller: Controllers::init(),
        }
    }

    // pub fn peek_opcode(&self) -> u8 {
    //     self.peek(self.cpu.pc)
    // }

    pub fn peek_nmi(&self) -> bool {
        self.ppu.peek_nmi() || self.apu.peek_nmi()
    }
    pub fn peek_irq(&self) -> bool {
        self.ppu.peek_irq() || self.apu.peek_irq()
    }

    pub fn run_cpu_cycle(
        &mut self,
        // mem: &mut CpuMem<'_>,
        // dma: &mut DmaState,
        // nmi: bool,
        // irq: bool,
        br: &Break,
    ) -> bool {
        if let Some(dma) = self.dma.cycle(
            &mut CpuMem::new(
                &mut self.mapped,
                &mut self.ppu,
                &mut self.apu,
                &mut self.dma,
                &mut self.controller,
            ),
            &mut self.cpu,
        ) {
            self.dma = dma;
            false
        } else {
            let nmi = self.ppu.nmi_waiting() || self.apu.nmi_waiting();
            let irq = self.ppu.irq_waiting() || self.apu.irq_waiting();
            self.cpu.clock_cpu(
                &mut CpuMem::new(
                    &mut self.mapped,
                    &mut self.ppu,
                    &mut self.apu,
                    &mut self.dma,
                    &mut self.controller,
                ),
                nmi,
                irq,
                br,
            )
        }
    }

    pub fn run_one_clock_cycle(&mut self, br: &Break) -> CycleResult {
        if self.cpu.halted() {
            return CycleResult {
                cpu_exec: false,
                ppu_frame: false,
                dma: false,
                dbg_int: true,
            };
        }
        let cpu_exec = if self.clock_count % 3 == 0 && self.clock_count > 0 {
            if self.run_cpu_cycle(br) {
                println!("Returning early from clock_cycle");
                return CycleResult {
                    cpu_exec: true,
                    ppu_frame: false,
                    dma: false,
                    dbg_int: true,
                };
            }
            self.cpu.cpu_cycle_update();
            self.cpu.executed()
        } else if self.clock_count == 0 {
            self.cpu.cpu_cycle_update();
            false
        } else {
            false
        };
        // let cpu_exec = self.clock_count % 3 == CPU_CLOCK_OFFSET && self.cpu.executed();
        if !self.cpu.halted() {
            let _apu_exec = self.apu.run_one_clock_cycle(self.clock_count);
        }
        let ppu_frame = if !self.cpu.halted() {
            self.ppu
                .run_one_clock_cycle(&mut PpuMem::new(&mut self.mapped))
        } else {
            false
        };
        // 3 PPU clock cycles for each CPU clock cycle
        let dbg_int = self.dbg_int | self.ppu.dbg_int;
        self.dbg_int = false;
        self.ppu.dbg_int = false;
        self.clock_count += 1;
        CycleResult {
            cpu_exec,
            ppu_frame,
            dma: false, //matches!(self.dma, DmaState::Running { .. }), TODO
            dbg_int,
        }
    }

    pub fn reset(&mut self) {
        self.clock_count = 0;
        // dbg_int: false,

        // clock_count: 0,
        // mapped,
        // cpu: Cpu::init(),
        // dma: DmaState::Passive,
        // // ppu: PPU::with_chr_rom(chr_rom, mapper),
        // ppu: PPU::init(),
        // apu: APU::init(),
        // controller: Controllers::init(),
        self.cpu.reset(&mut CpuMem::new(
            &mut self.mapped,
            &mut self.ppu,
            &mut self.apu,
            &mut self.dma,
            &mut self.controller,
        ));
        self.dma.reset();
        self.ppu.reset();
        self.apu.reset();
        self.controller.reset();
    }

    pub fn power_cycle(&mut self) {
        let debug_log = self.debug_log().enabled();
        *self = Self::from_mem(self.mapped.power_cylce());
        if debug_log {
            self.debug_log_mut().enable();
        }
        self.reset();
    }

    pub fn reset_and_run(&mut self) {
        self.reset();
        while !self.cpu.halted() {
            // info!("Running clock cycle: {}", self.clock_count);
            self.run_one_clock_cycle(&Break::default());
        }
    }

    /// Act as though the last STP instruction was a NOP.
    ///
    /// Clocks the PPU & APU forward once if appropriate
    pub fn repl_nop(&mut self) {
        assert!(
            self.cpu.halted(),
            "This method may only be called when the CPU is halted"
        );
        assert!(
            // self.debug_log.pop().is_some_and(|s| s.contains("HLT")),
            self.cpu.last_instruction.contains("HLT"),
            "This method may only be called after a STP instruction"
        );
        self.cpu.unhalt();
        // Turn final STP into NOP
        self.ppu
            .run_one_clock_cycle(&mut PpuMem::new(&mut self.mapped));
        self.apu.run_one_clock_cycle(self.clock_count);
        self.cpu.clock_state = ClockState::Hold {
            cycles: 0,
            instruction: 0xEA,
            ops: [0; 5],
            count: 0,
            addr: self.cpu.pc,
        };
        self.cpu.pc += 1;
        self.cpu.cycle += 1;
        // self.clock_cpu();
    }

    pub fn run_with_timeout(&mut self, max_clock_cycles: usize) {
        let mut cur = 0;
        while !self.cpu.halted() && cur < max_clock_cycles {
            // info!("Running clock cycle: {}", self.clock_count);
            self.run_one_clock_cycle(&Break::default());
            cur += 1;
        }
    }

    pub fn reset_and_run_with_timeout(&mut self, max_clock_cycles: usize) {
        self.reset();
        self.run_with_timeout(max_clock_cycles);
    }

    pub fn image(&self) -> &RenderBuffer<256, 240> {
        &self.ppu.render_buffer
    }

    pub fn cpu(&self) -> &Cpu {
        &self.cpu
    }

    pub fn ppu(&self) -> &PPU {
        &self.ppu
    }

    pub fn apu(&self) -> &APU {
        &self.apu
    }

    pub fn apu_mut(&mut self) -> &mut APU {
        &mut self.apu
    }

    pub fn debug_log(&self) -> &DebugLog {
        &self.cpu.debug_log
    }

    pub fn debug_log_mut(&mut self) -> &mut DebugLog {
        &mut self.cpu.debug_log
    }

    pub fn halted(&self) -> bool {
        self.cpu.halted()
    }

    pub fn mem(&self) -> &Mapped {
        &self.mapped
    }

    pub fn last_instruction(&self) -> &str {
        &self.cpu.last_instruction
    }
    pub fn cpu_cycle(&self) -> usize {
        self.cpu.cycle
    }

    pub fn controller_1(&mut self) -> &mut ControllerState {
        self.controller.controller_1()
    }
    pub fn controller_2(&mut self) -> &mut ControllerState {
        self.controller.controller_2()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Break {
    pub dma: bool,
    pub nmi: bool,
    pub irq: bool,
    pub cpu_exec: bool,
    pub ppu_frame: bool,
    pub ppu_scanline: bool,
    pub break_points: Vec<u16>,
    // pub dma: bool,
    // pub dbg_int: bool,
}
