/// PPU Control Register ($2000) bit constants
const GENERATE_NMI: u8 = 0b1000_0000;
const SPRITE_SIZE: u8 = 0b0010_0000;
const BG_PATTERN_TABLE_ADDR: u8 = 0b0001_0000;
const SPRITE_PATTERN_TABLE_ADDR: u8 = 0b0000_1000;
const VRAM_ADDR_INCREMENT: u8 = 0b0000_0100;
const BASE_NAMETABLE_ADDR: u8 = 0b0000_0011;

/// PPU Mask Register ($2001) bit constants
const SHOW_BACKGROUND: u8 = 0b0000_1000;
const SHOW_SPRITES: u8 = 0b0001_0000;
const SHOW_BACKGROUND_LEFT: u8 = 0b0000_0010;
const SHOW_SPRITES_LEFT: u8 = 0b0000_0100;
const GRAYSCALE: u8 = 0b0000_0001;
// const EMPHASIZE_GREEN: u8 = 0b0100_0000;
// const EMPHASIZE_BLUE: u8 = 0b1000_0000;

/// Manages PPU registers including PPUCTRL, PPUMASK, and Loopy scroll registers
#[derive(Debug)]
pub struct Registers {
    /// Control register value ($2000)
    control_register: u8,
    /// Mask register value ($2001)
    mask_register: u8,
    /// OAM address register ($2003)
    pub oam_address: u8,
    /// PPU data read buffer
    data_buffer: u8,
    /// PPU I/O bus latch - holds last value written or read from PPU registers
    /// This is separate from the CPU data bus!
    io_bus: u8,
    /// Time (in cycles) when each bit of io_bus was last refreshed
    /// Bits decay to 0 after ~600ms (36M cycles for NTSC) if not refreshed
    io_bus_refresh_time: [u64; 8],
    /// Current cycle count for decay timing
    cycle_count: u64,
    /// v: Current VRAM address (15 bits)
    v: u16,
    /// t: Temporary VRAM address (15 bits)
    t: u16,
    /// x: Fine X scroll (3 bits)
    x: u8,
    /// w: Write toggle (1 bit) - false=first write, true=second write
    w: bool,
}

impl Default for Registers {
    fn default() -> Self {
        Self::new()
    }
}

impl Registers {
    /// Create a new Registers instance
    pub fn new() -> Self {
        Self {
            control_register: 0,
            mask_register: 0,
            oam_address: 0,
            data_buffer: 0,
            io_bus: 0,
            io_bus_refresh_time: [0; 8],
            cycle_count: 0,
            v: 0,
            t: 0,
            x: 0,
            w: false,
        }
    }

    /// Reset registers to initial state
    pub fn reset(&mut self) {
        self.control_register = 0;
        self.mask_register = 0;
        self.oam_address = 0;
        self.data_buffer = 0;
        self.io_bus = 0;
        self.io_bus_refresh_time = [0; 8];
        self.cycle_count = 0;
        self.v = 0;
        self.t = 0;
        self.x = 0;
        self.w = false;
    }

    /// Write to control register ($2000)
    pub fn write_control(&mut self, value: u8) {
        self.control_register = value;
        // t: ...GH.. ........ <- d: ......GH
        // (Update nametable select bits in t register)
        let nametable_bits = (value & BASE_NAMETABLE_ADDR) as u16;
        self.t = (self.t & 0xF3FF) | (nametable_bits << 10);
    }

    /// Write to mask register ($2001)
    pub fn write_mask(&mut self, value: u8) {
        self.mask_register = value;
    }

    /// Get the current PPU I/O bus latch value (with decay applied)
    /// Bits that haven't been refreshed for ~600ms decay to 0
    pub fn io_bus(&self) -> u8 {
        // 600ms = 36 frames at 60Hz, each frame ~89342 PPU cycles = 3,216,312 cycles
        const DECAY_CYCLES: u64 = 3_216_312;
        let mut result = self.io_bus;

        // Check each bit for decay
        for i in 0..8 {
            if (result & (1 << i)) != 0 {
                // If bit is set, check if it should decay
                let cycles_since_refresh =
                    self.cycle_count.saturating_sub(self.io_bus_refresh_time[i]);
                if cycles_since_refresh > DECAY_CYCLES {
                    result &= !(1 << i); // Clear the decayed bit
                }
            }
        }

        result
    }

    /// Update the PPU I/O bus latch (called on PPU register reads/writes)
    /// Refreshes the decay timer for bits that are set to 1
    pub fn set_io_bus(&mut self, value: u8) {
        self.io_bus = value;
        // Refresh the timer for any bit that is being set to 1
        for i in 0..8 {
            if (value & (1 << i)) != 0 {
                self.io_bus_refresh_time[i] = self.cycle_count;
            }
        }
    }

    /// Update the PPU I/O bus latch with selective bit refresh
    /// Only refreshes decay timer for bits specified in refresh_mask
    pub fn set_io_bus_with_mask(&mut self, value: u8, refresh_mask: u8) {
        self.io_bus = value;
        // Only refresh the timer for bits that are both set to 1 AND in the mask
        for i in 0..8 {
            if (value & (1 << i)) != 0 && (refresh_mask & (1 << i)) != 0 {
                self.io_bus_refresh_time[i] = self.cycle_count;
            }
        }
    }

    /// Tick the cycle counter for decay timing (call every PPU cycle)
    pub fn tick(&mut self) {
        self.cycle_count = self.cycle_count.wrapping_add(1);
    }

    /// Write to scroll register ($2005)
    pub fn write_scroll(&mut self, value: u8, is_dummy_write: bool) {
        // Dummy writes from RMW instructions DO toggle w and modify registers
        // They just write the unmodified value (which was already there)
        // So the behavior is the same as a normal write - no special handling needed
        let _ = is_dummy_write; // Dummy writes behave identically to normal writes

        if !self.w {
            // First write: X scroll
            // t: ....... ...ABCDE <- d: ABCDE...
            // x:              FGH <- d: .....FGH
            // w:                  <- 1
            self.t = (self.t & 0xFFE0) | ((value as u16) >> 3);
            self.x = value & 0x07;
            self.w = true;
        } else {
            // Second write: Y scroll
            // t: FGH..AB CDE..... <- d: ABCDEFGH
            // w:                  <- 0
            self.t = (self.t & 0x8FFF) | (((value as u16) & 0x07) << 12);
            self.t = (self.t & 0xFC1F) | (((value as u16) & 0xF8) << 2);
            self.w = false;
        }
    }

    /// Write to address register ($2006).
    /// Returns `true` when this was the second write (v was updated from t).
    pub fn write_address(&mut self, value: u8, is_dummy_write: bool) -> bool {
        // Dummy writes from RMW instructions DO toggle w and modify registers
        // They just write the unmodified value (which was already there)
        // So the behavior is the same as a normal write - no special handling needed
        let _ = is_dummy_write; // Dummy writes behave identically to normal writes

        if !self.w {
            // First write: high byte
            // t: .CDEFGH ........ <- d: ..CDEFGH
            //        <-- d: AB......
            // t: Z...... ........ <- 0 (bit 14 is cleared)
            // w:                  <- 1
            self.t = (self.t & 0x80FF) | (((value & 0x3F) as u16) << 8);
            self.w = true;
            false
        } else {
            // Second write: low byte
            // t: ....... ABCDEFGH <- d: ABCDEFGH
            // v: <--t
            // w:                  <- 0
            self.t = (self.t & 0xFF00) | (value as u16);
            self.v = self.t;
            self.w = false;
            true
        }
    }

    /// Read data buffer
    pub fn data_buffer(&self) -> u8 {
        self.data_buffer
    }

    /// Set data buffer
    pub fn set_data_buffer(&mut self, value: u8) {
        self.data_buffer = value;
    }

    /// Increment VRAM address by the amount specified in control register
    pub fn increment_vram_address(&mut self) {
        let increment = if (self.control_register & VRAM_ADDR_INCREMENT) != 0 {
            32
        } else {
            1
        };
        self.v = self.v.wrapping_add(increment) & 0x3FFF;
    }

    /// Increment coarse X (used during rendering)
    pub fn increment_coarse_x(&mut self) {
        if (self.v & 0x001F) == 31 {
            // Coarse X = 0, switch horizontal nametable
            self.v &= !0x001F;
            self.v ^= 0x0400;
        } else {
            // Increment coarse X
            self.v += 1;
        }
    }

    /// Increment fine Y (used during rendering)
    pub fn increment_fine_y(&mut self) {
        if (self.v & 0x7000) != 0x7000 {
            // Fine Y < 7, just increment
            self.v += 0x1000;
        } else {
            // Fine Y = 7, reset and increment coarse Y
            self.v &= !0x7000;
            let mut y = (self.v & 0x03E0) >> 5;
            if y == 29 {
                // Coarse Y = 29, reset and switch vertical nametable
                y = 0;
                self.v ^= 0x0800;
            } else if y == 31 {
                // Coarse Y = 31, reset (no nametable switch)
                y = 0;
            } else {
                // Normal increment
                y += 1;
            }
            self.v = (self.v & !0x03E0) | (y << 5);
        }
    }

    /// Copy horizontal bits from t to v (used during rendering)
    pub fn copy_horizontal_bits(&mut self) {
        // v: ....A.. ...BCDEF <- t: ....A.. ...BCDEF
        self.v = (self.v & 0xFBE0) | (self.t & 0x041F);
    }

    /// Copy vertical bits from t to v (used during rendering)
    pub fn copy_vertical_bits(&mut self) {
        // v: GHIA.BC DEF..... <- t: GHIA.BC DEF.....
        self.v = (self.v & 0x841F) | (self.t & 0x7BE0);
    }

    /// Increment v register using the rendering glitch pattern
    /// During rendering, PPUDATA access increments both coarse X and fine Y
    /// This is a hardware quirk that some games rely on
    pub fn inc_address_with_rendering_glitch(&mut self) {
        self.increment_coarse_x();
        self.increment_fine_y();
    }

    /// Get current VRAM address
    pub fn v(&self) -> u16 {
        self.v
    }

    /// Get temporary VRAM address
    pub fn t(&self) -> u16 {
        self.t
    }

    /// Get fine X scroll
    pub fn x(&self) -> u8 {
        self.x
    }

    /// Get write toggle
    pub fn w(&self) -> bool {
        self.w
    }

    /// Set the VRAM address directly (used by delayed v=t update).
    /// The PPU v register is 15-bit internally (fine Y in bits 12-14),
    /// so mask off bit 15 as a safety invariant.
    pub fn set_v(&mut self, v: u16) {
        self.v = v & 0x7FFF;
    }

    /// Clear write toggle (used when reading status)
    pub fn clear_w(&mut self) {
        self.w = false;
    }

    /// Check if NMI should be generated
    pub fn should_generate_nmi(&self) -> bool {
        (self.control_register & GENERATE_NMI) != 0
    }

    /// Check if background rendering is enabled
    pub fn is_background_enabled(&self) -> bool {
        (self.mask_register & SHOW_BACKGROUND) != 0
    }

    /// Check if sprite rendering is enabled
    pub fn is_sprite_enabled(&self) -> bool {
        (self.mask_register & SHOW_SPRITES) != 0
    }

    /// Check if rendering is enabled (background or sprites)
    pub fn is_rendering_enabled(&self) -> bool {
        self.is_background_enabled() || self.is_sprite_enabled()
    }

    /// Check if background should be shown in leftmost 8 pixels
    pub fn show_background_left(&self) -> bool {
        (self.mask_register & SHOW_BACKGROUND_LEFT) != 0
    }

    /// Check if sprites should be shown in leftmost 8 pixels
    pub fn show_sprites_left(&self) -> bool {
        (self.mask_register & SHOW_SPRITES_LEFT) != 0
    }

    /// Check if grayscale mode is enabled
    pub fn is_grayscale(&self) -> bool {
        (self.mask_register & GRAYSCALE) != 0
    }

    /// Get color emphasis bits
    pub fn color_emphasis(&self) -> u8 {
        (self.mask_register >> 5) & 0x07
    }

    /// Get sprite size (0=8x8, 1=8x16)
    pub fn sprite_height(&self) -> u8 {
        if (self.control_register & SPRITE_SIZE) != 0 {
            16
        } else {
            8
        }
    }

    /// Get background pattern table address
    pub fn bg_pattern_table_addr(&self) -> u16 {
        if (self.control_register & BG_PATTERN_TABLE_ADDR) != 0 {
            0x1000
        } else {
            0x0000
        }
    }

    /// Get sprite pattern table address (ignored in 8x16 mode)
    pub fn sprite_pattern_table_addr(&self) -> u16 {
        if (self.control_register & SPRITE_PATTERN_TABLE_ADDR) != 0 {
            0x1000
        } else {
            0x0000
        }
    }

    /// Get control register value
    pub fn control(&self) -> u8 {
        self.control_register
    }

    /// Get base nametable address from control register bits 0-1
    /// Returns: 0x2000, 0x2400, 0x2800, or 0x2C00
    #[cfg(test)]
    #[allow(dead_code)]
    pub fn base_nametable_addr(&self) -> u16 {
        let nametable_select = (self.control_register & BASE_NAMETABLE_ADDR) as u16;
        0x2000 | (nametable_select << 10)
    }

    /// Get mask register value
    pub fn mask(&self) -> u8 {
        self.mask_register
    }

    /// Restore register state from a save-state.
    #[allow(clippy::too_many_arguments)]
    pub fn restore_state(
        &mut self,
        control: u8,
        mask: u8,
        oam_address: u8,
        v: u16,
        t: u16,
        fine_x: u8,
        w: bool,
        io_bus: u8,
        data_buffer: u8,
    ) {
        self.control_register = control;
        self.mask_register = mask;
        self.oam_address = oam_address;
        self.v = v;
        self.t = t;
        self.x = fine_x;
        self.w = w;
        self.io_bus = io_bus;
        self.data_buffer = data_buffer;
        // Note: io_bus_refresh_time and cycle_count not restored as they're for decay only
    }

    pub fn io_bus_refresh_time(&self) -> [u64; 8] {
        self.io_bus_refresh_time
    }

    pub fn cycle_count(&self) -> u64 {
        self.cycle_count
    }

    pub fn set_io_bus_refresh_time(&mut self, times: [u64; 8]) {
        self.io_bus_refresh_time = times;
    }

    pub fn set_cycle_count(&mut self, cycles: u64) {
        self.cycle_count = cycles;
    }

    #[allow(dead_code)]
    pub fn scroll_state(&self) -> (u16, u16, u8, bool) {
        (self.t, self.v, self.x, self.w)
    }
}

#[cfg(test)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegistersDebugState {
    pub control_register: u8,
    pub mask_register: u8,
    pub oam_address: u8,
    pub data_buffer: u8,
    pub io_bus: u8,
    pub io_bus_refresh_time: [u64; 8],
    pub cycle_count: u64,
    pub v: u16,
    pub t: u16,
    pub x: u8,
    pub w: bool,
}

#[cfg(test)]
impl Registers {
    pub fn debug_state(&self) -> RegistersDebugState {
        RegistersDebugState {
            control_register: self.control_register,
            mask_register: self.mask_register,
            oam_address: self.oam_address,
            data_buffer: self.data_buffer,
            io_bus: self.io_bus,
            io_bus_refresh_time: self.io_bus_refresh_time,
            cycle_count: self.cycle_count,
            v: self.v,
            t: self.t,
            x: self.x,
            w: self.w,
        }
    }

    pub fn set_debug_state(&mut self, state: RegistersDebugState) {
        self.control_register = state.control_register;
        self.mask_register = state.mask_register;
        self.oam_address = state.oam_address;
        self.data_buffer = state.data_buffer;
        self.io_bus = state.io_bus;
        self.io_bus_refresh_time = state.io_bus_refresh_time;
        self.cycle_count = state.cycle_count;
        self.v = state.v;
        self.t = state.t;
        self.x = state.x;
        self.w = state.w;
    }
}

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

    #[test]
    fn test_registers_new() {
        let regs = Registers::new();
        assert_eq!(regs.control(), 0);
        assert_eq!(regs.mask(), 0);
        assert_eq!(regs.v(), 0);
        assert_eq!(regs.t(), 0);
    }

    #[test]
    fn test_write_control() {
        let mut regs = Registers::new();
        regs.write_control(0b0000_0011);
        assert_eq!(regs.t() & 0x0C00, 0x0C00);
    }

    #[test]
    fn test_write_scroll_first() {
        let mut regs = Registers::new();
        regs.write_scroll(0b11111111, false);
        assert_eq!(regs.t() & 0x1F, 0b11111);
        assert_eq!(regs.x(), 0b111);
        assert!(regs.w());
    }

    #[test]
    fn test_write_scroll_second() {
        let mut regs = Registers::new();
        regs.write_scroll(0, false);
        regs.write_scroll(0b11111111, false);
        assert!(!regs.w());
    }

    #[test]
    fn test_write_address() {
        let mut regs = Registers::new();
        regs.write_address(0x3F, false);
        regs.write_address(0x00, false);
        assert_eq!(regs.v(), 0x3F00);
    }

    #[test]
    fn test_write_control_only_updates_nametable_bits_in_t() {
        let mut regs = Registers::new();

        // Seed t with a full value via $2006 writes.
        regs.write_address(0x3F, false);
        regs.write_address(0xFF, false);
        let before = regs.t();

        regs.write_control(0x00);
        let after = regs.t();

        assert_eq!(after & !0x0C00, before & !0x0C00);
        assert_eq!(after & 0x0C00, 0x0000);

        regs.write_control(0x03);
        let after2 = regs.t();
        assert_eq!(after2 & !0x0C00, before & !0x0C00);
        assert_eq!(after2 & 0x0C00, 0x0C00);
    }

    #[test]
    fn test_scroll_state_reports_internal_registers() {
        let mut regs = Registers::new();
        regs.write_scroll(0b1001_0110, false);
        let (t, v, x, w) = regs.scroll_state();
        assert_eq!(t, regs.t());
        assert_eq!(v, regs.v());
        assert_eq!(x, regs.x());
        assert_eq!(w, regs.w());
    }

    #[test]
    fn test_increment_coarse_x() {
        let mut regs = Registers::new();
        regs.v = 0;
        regs.increment_coarse_x();
        assert_eq!(regs.v() & 0x1F, 1);
    }

    #[test]
    fn test_increment_coarse_x_wrap() {
        let mut regs = Registers::new();
        regs.v = 31;
        regs.increment_coarse_x();
        assert_eq!(regs.v() & 0x1F, 0);
        assert_eq!(regs.v() & 0x0400, 0x0400);
    }

    #[test]
    fn test_increment_fine_y() {
        let mut regs = Registers::new();
        regs.v = 0;
        regs.increment_fine_y();
        assert_eq!(regs.v() & 0x7000, 0x1000);
    }

    #[test]
    fn test_copy_horizontal_bits() {
        let mut regs = Registers::new();
        regs.t = 0x041F;
        regs.v = 0;
        regs.copy_horizontal_bits();
        assert_eq!(regs.v() & 0x041F, 0x041F);
    }

    #[test]
    fn test_copy_vertical_bits() {
        let mut regs = Registers::new();
        regs.t = 0x7BE0;
        regs.v = 0;
        regs.copy_vertical_bits();
        assert_eq!(regs.v() & 0x7BE0, 0x7BE0);
    }

    #[test]
    fn test_sprite_height_8x8() {
        let mut regs = Registers::new();
        regs.write_control(0);
        assert_eq!(regs.sprite_height(), 8);
    }

    #[test]
    fn test_sprite_height_8x16() {
        let mut regs = Registers::new();
        regs.write_control(SPRITE_SIZE);
        assert_eq!(regs.sprite_height(), 16);
    }

    #[test]
    fn test_is_grayscale() {
        let mut regs = Registers::new();
        regs.write_mask(GRAYSCALE);
        assert!(regs.is_grayscale());
    }

    const EMPHASIZE_RED: u8 = 0b0010_0000;

    #[test]
    fn test_color_emphasis() {
        let mut regs = Registers::new();
        regs.write_mask(EMPHASIZE_RED);
        assert_eq!(regs.color_emphasis() & 0x01, 0x01);
    }

    const IO_BUS_DECAY_CYCLES: u64 = 3_216_312;

    #[test]
    fn test_io_bus_decay_after_threshold() {
        let mut regs = Registers::new();

        regs.set_cycle_count(0);
        regs.set_io_bus(0xF0);

        regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1);
        assert_eq!(regs.io_bus(), 0x00);
    }

    #[test]
    fn test_io_bus_decay_refreshes_on_update() {
        let mut regs = Registers::new();

        regs.set_cycle_count(0);
        regs.set_io_bus(0x01);

        regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1);
        assert_eq!(regs.io_bus(), 0x00);

        regs.set_io_bus(0x01);
        regs.set_cycle_count(IO_BUS_DECAY_CYCLES + 1 + (IO_BUS_DECAY_CYCLES / 2));
        assert_eq!(regs.io_bus(), 0x01);
    }
}
