use std::fmt;

use bytes::{Bytes, BytesMut};

use crate::mem::{Mapped, PpuMem, Value};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl fmt::Display for Color {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RenderBuffer<const W: usize, const H: usize> {
    buffer: Box<[Color]>,
    raw_rgba: BytesMut,
}

impl<const W: usize, const H: usize> RenderBuffer<W, H> {
    pub fn empty() -> Self {
        Self {
            buffer: vec![Color { r: 0, g: 0, b: 0 }; W * H].into_boxed_slice(),
            raw_rgba: BytesMut::from_iter(vec![0; W * H * 4]),
        }
    }

    pub fn write(&mut self, line: usize, pixel: usize, color: Color) {
        assert!(line < H && pixel < W);
        self.buffer[line * W + pixel] = color;
        let pos = (line * W + pixel) * 4;
        self.raw_rgba[pos] = color.r;
        self.raw_rgba[pos + 1] = color.g;
        self.raw_rgba[pos + 2] = color.b;
        self.raw_rgba[pos + 3] = 0xFF;
    }
    pub fn read(&self, line: usize, pixel: usize) -> Color {
        assert!(line < H && pixel < W);
        self.buffer[line * W + pixel]
    }

    pub fn clone_from(&mut self, other: &Self) {
        self.buffer.copy_from_slice(&other.buffer);
        // self.raw_rgba.fr
        self.raw_rgba.copy_from_slice(&other.raw_rgba);
    }

    pub fn image(&self) -> Bytes {
        Bytes::copy_from_slice(&self.raw_rgba)
    }

    pub fn raw_image(&self) -> &[u8] {
        &self.raw_rgba
    }

    pub fn assert_eq(&self, other: &Self) {
        // if self.buffer != other.buffer {
        for y in 0..H {
            for x in 0..W {
                if self.read(y, x) != other.read(y, x) {
                    panic!(
                        "Rendered Buffers do not match\n    Mismatch at ({x}, {y})\n    Left:  {}\n    Right: {}",
                        self.read(y, x),
                        other.read(y, x)
                    );
                }
            }
        }
        // }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PPUMMRegisters {
    Palette,
}

bitfield::bitfield! {
    #[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct SpriteAttrs(u8);
    impl Debug;
    vflip, set_vflip: 7;
    hflip, set_hflip: 6;
    priority, set_priority: 5;
    palette, set_palette: 1, 0;
}

#[derive(Debug, Clone, Default)]
struct SpriteOutputUnit {
    high: u8,
    low: u8,
    x_pos: u8,
    y_pos: u8,
    tile: u8,
    attrs: SpriteAttrs,
}

#[derive(Debug, Clone)]
enum OamState {
    ReadY,
    ReadTile,
    ReadAttrs,
    ReadX,
    // OverflowScan,
    Wait,
}

#[derive(Debug, Clone)]
pub struct OAM {
    mem: Vec<u8>,
    secondary: Vec<u8>,
    addr: u8,
    count: u8,
    state: OamState,
    oam_read_buffer: u8,
    edit_ver: usize,
    sprite_output_units: Vec<SpriteOutputUnit>,
    large_sprites: bool,
    pub overflow: bool,
    sprite_offset_0x1000: bool,
}

impl OAM {
    fn new() -> Self {
        Self {
            mem: vec![0u8; 256],
            addr: 0,
            count: 0,
            edit_ver: 0,
            secondary: vec![0xFF; 32],
            sprite_output_units: vec![SpriteOutputUnit::default(); 8],
            state: OamState::ReadY,
            oam_read_buffer: 0,
            overflow: false,
            sprite_offset_0x1000: false,
            large_sprites: false,
        }
    }

    fn ppu_cycle(&mut self, pixel: usize, line: usize, mem: &mut PpuMem<'_>) {
        if pixel < 64 {
        } else if pixel == 64 {
            self.secondary.fill(0xFF);
            self.state = OamState::ReadY;
            self.addr = 0;
            self.count = 0;
        } else if pixel <= 256 {
            // Fetch/evalute sprites for next line
            if pixel % 2 == 1 && !matches!(self.state, OamState::Wait) {
                self.oam_read_buffer = self.mem[self.addr as usize];
                if self.addr == 255 {
                    self.addr = 0;
                } else {
                    self.addr += 1;
                }
            } else {
                match self.state {
                    OamState::ReadY => {
                        let l = self.oam_read_buffer as usize;
                        if self.count < 8 {
                            if l <= line && line < l + if self.large_sprites { 16 } else { 8 } {
                                self.secondary[self.count as usize * 4] = self.oam_read_buffer;
                                self.state = OamState::ReadTile;
                            } else {
                                if self.addr < 0xFD {
                                    self.addr += 3;
                                } else {
                                    self.state = OamState::Wait; // Should be Overflow scan...
                                }
                            }
                        } else {
                            self.state = OamState::Wait; // Should be Overflow scan...
                        }
                    }
                    OamState::ReadTile => {
                        self.secondary[self.count as usize * 4 + 1] = self.oam_read_buffer;
                        self.state = OamState::ReadAttrs;
                    }
                    OamState::ReadAttrs => {
                        self.secondary[self.count as usize * 4 + 2] = self.oam_read_buffer;
                        self.state = OamState::ReadX;
                    }
                    OamState::ReadX => {
                        self.secondary[self.count as usize * 4 + 3] = self.oam_read_buffer;
                        self.state = OamState::ReadY;
                        self.count += 1;
                        if self.count == 8 {
                            self.state = OamState::Wait; // Should be Overflow scan...
                        }
                    }
                    // OamState::OverflowScan => todo!(),
                    OamState::Wait => (),
                }
            }
        } else if pixel <= 320 {
            if pixel == 257 {
                // Reset output units
                self.sprite_output_units.fill(SpriteOutputUnit::default());
            }
            let run = pixel - 257;
            if run / 8 < self.count as usize {
                if run % 8 == 0 {
                    self.sprite_output_units[run / 8].y_pos = self.secondary[(run / 8) * 4];
                } else if run % 8 == 1 {
                    self.sprite_output_units[run / 8].tile = self.secondary[(run / 8) * 4 + 1];
                } else if run % 8 == 2 {
                    self.sprite_output_units[run / 8].attrs.0 = self.secondary[(run / 8) * 4 + 2];
                } else if run % 8 == 3 {
                    self.sprite_output_units[run / 8].x_pos = self.secondary[(run / 8) * 4 + 3];
                } else if run % 8 == 4 {
                    // } else if run % 8 == 5 {
                    //     let off = line - self.sprite_output_units[run / 8].y_pos as usize;
                    //     let off = if self.sprite_output_units[run / 8].attrs.vflip() {
                    //         7 - off
                    //     } else {
                    //         off
                    //     };
                    //     let addr = if self.sprite_offset_0x1000 {
                    //         0x1000
                    //     } else {
                    //         0x0000
                    //     } + 16 * self.sprite_output_units[run / 8].tile as u16
                    //         + off as u16;
                    //     self.sprite_output_units[run / 8].low = mem.read(addr).reg_map(|_, _| todo!());
                } else if run % 8 == 6 {
                } else if run % 8 == 5 || run % 8 == 7 {
                    let off = line - self.sprite_output_units[run / 8].y_pos as usize;
                    let addr = if self.large_sprites {
                        let off = if self.sprite_output_units[run / 8].attrs.vflip() {
                            15 - off
                        } else {
                            off
                        };
                        (if self.sprite_output_units[run / 8].tile & 1 == 1 {
                            0x1000
                        } else {
                            0x0000
                        } + 16 * (self.sprite_output_units[run / 8].tile & !1) as u16
                            + off as u16 + if off > 7 { 8 } else { 0 })
                    } else {
                        let off = if self.sprite_output_units[run / 8].attrs.vflip() {
                            7 - off
                        } else {
                            off
                        };
                        (if self.sprite_offset_0x1000 {
                            0x1000
                        } else {
                            0x0000
                        } + 16 * self.sprite_output_units[run / 8].tile as u16
                            + off as u16)
                    };
                    if run % 8 == 5 {
                        self.sprite_output_units[run / 8].low =
                            mem.read(addr).reg_map(|_, _| todo!());
                    } else {
                        self.sprite_output_units[run / 8].high =
                            mem.read(addr + 8).reg_map(|_, _| todo!());
                    }
                }
            }
        }
    }

    /// Returns Some((palette index, above background))
    fn pixel(&mut self, pixel: usize, _line: usize) -> Option<(u8, bool, bool)> {
        self.sprite_output_units
            .iter()
            .enumerate()
            .filter_map(|(i, s)| {
                if pixel >= s.x_pos as usize && pixel < s.x_pos as usize + 8 && s.x_pos < 0xFF {
                    let bit_pos = pixel - s.x_pos as usize;
                    let bit_pos = if s.attrs.hflip() {
                        bit_pos
                    } else {
                        7 - bit_pos
                    };
                    // println!("Shifting: 0b{:08b} by {}", s.high, 8 - bit_pos);
                    let hi = (s.high >> bit_pos) & 1;
                    // println!("Shifting: 0b{:08b} by {}", s.low, bit_pos);
                    let lo = (s.low >> bit_pos) & 1;
                    let idx = (hi << 1) | lo;
                    if idx != 0 {
                        Some((
                            idx + s.attrs.palette() * 4 + 0x10,
                            !s.attrs.priority(),
                            i == 0,
                        ))
                    } else {
                        None
                    }
                } else {
                    None
                }
            })
            .next()
    }
}

#[derive(Debug, Clone)]
pub struct Background {
    /// Current vram address, 15 bits
    /// yyy NN YYYYY XXXXX (0yyy NNYY YYYX XXXX)
    pub v: u16,
    /// Temp vram address, 15 bits
    /// yyy NN YYYYY XXXXX
    pub t: u16,
    /// Fine X control, 3 bits
    pub x: u8,
    /// Whether this is the first or second write to PPUSCROLL
    /// When false, writes to x
    pub w: bool,

    copy_v: u8,

    /// When true, v is incremented by 32 after each read
    pub vram_column: bool,
    pub second_pattern: bool,

    pub cur_nametable: u8,
    pub next_attr: u8,
    pub cur_high: u8,
    pub cur_low: u8,
    pub cur_shift_high: u32,
    pub cur_shift_low: u32,
    pub cur_attr_shift_high: u32,
    pub cur_attr_shift_low: u32,
}

#[derive(Debug, Clone, Copy)]
pub struct Mask {
    pub grayscale: bool,
    pub background_on_left_edge: bool,
    pub sprites_on_left_edge: bool,
    pub enable_background: bool,
    pub enable_sprites: bool,
    pub em_red: bool,
    pub em_green: bool,
    pub em_blue: bool,
}

const COLORS: &'static [Color; 0b100_0000] = &[
    Color {
        r: 0x66,
        g: 0x66,
        b: 0x66,
    }, // 00
    Color {
        r: 0x00,
        g: 0x2A,
        b: 0x88,
    }, // 01
    Color {
        r: 0x14,
        g: 0x12,
        b: 0xA7,
    }, // 02
    Color {
        r: 0x3B,
        g: 0x00,
        b: 0xA4,
    }, // 03
    Color {
        r: 0x5C,
        g: 0x00,
        b: 0x7E,
    }, // 04
    Color {
        r: 0x6E,
        g: 0x00,
        b: 0x40,
    }, // 05
    Color {
        r: 0x6C,
        g: 0x06,
        b: 0x00,
    }, // 06
    Color {
        r: 0x56,
        g: 0x1D,
        b: 0x00,
    }, // 07
    Color {
        r: 0x33,
        g: 0x35,
        b: 0x00,
    }, // 08
    Color {
        r: 0x0B,
        g: 0x48,
        b: 0x00,
    }, // 09
    Color {
        r: 0x00,
        g: 0x52,
        b: 0x00,
    }, // 0A
    Color {
        r: 0x00,
        g: 0x4F,
        b: 0x08,
    }, // 0B
    Color {
        r: 0x00,
        g: 0x40,
        b: 0x4D,
    }, // 0C
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 0D
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 0E
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 0F
    Color {
        r: 0xAD,
        g: 0xAD,
        b: 0xAD,
    }, // 10
    Color {
        r: 0x15,
        g: 0x5F,
        b: 0xD9,
    }, // 11
    Color {
        r: 0x42,
        g: 0x40,
        b: 0xFF,
    }, // 12
    Color {
        r: 0x75,
        g: 0x27,
        b: 0xFE,
    }, // 13
    Color {
        r: 0xA0,
        g: 0x1A,
        b: 0xCC,
    }, // 14
    Color {
        r: 0xB7,
        g: 0x1E,
        b: 0x7B,
    }, // 15
    Color {
        r: 0xB5,
        g: 0x31,
        b: 0x20,
    }, // 16
    Color {
        r: 0x99,
        g: 0x4E,
        b: 0x00,
    }, // 17
    Color {
        r: 0x6B,
        g: 0x6D,
        b: 0x00,
    }, // 18
    Color {
        r: 0x38,
        g: 0x87,
        b: 0x00,
    }, // 19
    Color {
        r: 0x0C,
        g: 0x93,
        b: 0x00,
    }, // 1A
    Color {
        r: 0x00,
        g: 0x8F,
        b: 0x32,
    }, // 1B
    Color {
        r: 0x00,
        g: 0x7C,
        b: 0x8D,
    }, // 1C
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 1D
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 1E
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 1F
    Color {
        r: 0xFF,
        g: 0xFE,
        b: 0xFF,
    }, // 20
    Color {
        r: 0x64,
        g: 0xB0,
        b: 0xFF,
    }, // 21
    Color {
        r: 0x92,
        g: 0x90,
        b: 0xFF,
    }, // 22
    Color {
        r: 0xC6,
        g: 0x76,
        b: 0xFF,
    }, // 23
    Color {
        r: 0xF3,
        g: 0x6A,
        b: 0xFF,
    }, // 24
    Color {
        r: 0xFE,
        g: 0x6E,
        b: 0xCC,
    }, // 25
    Color {
        r: 0xFE,
        g: 0x81,
        b: 0x70,
    }, // 26
    Color {
        r: 0xEA,
        g: 0x9E,
        b: 0x22,
    }, // 27
    Color {
        r: 0xBC,
        g: 0xBE,
        b: 0x00,
    }, // 28
    Color {
        r: 0x88,
        g: 0xD8,
        b: 0x00,
    }, // 29
    Color {
        r: 0x5C,
        g: 0xE4,
        b: 0x30,
    }, // 2A
    Color {
        r: 0x45,
        g: 0xE0,
        b: 0x82,
    }, // 2B
    Color {
        r: 0x48,
        g: 0xCD,
        b: 0xDE,
    }, // 2C
    Color {
        r: 0x4F,
        g: 0x4F,
        b: 0x4F,
    }, // 2D
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 2E
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 2F
    Color {
        r: 0xFF,
        g: 0xFE,
        b: 0xFF,
    }, // 30
    Color {
        r: 0xC0,
        g: 0xDF,
        b: 0xFF,
    }, // 31
    Color {
        r: 0xD3,
        g: 0xD2,
        b: 0xFF,
    }, // 32
    Color {
        r: 0xE8,
        g: 0xC8,
        b: 0xFF,
    }, // 33
    Color {
        r: 0xFB,
        g: 0xC2,
        b: 0xFF,
    }, // 34
    Color {
        r: 0xFE,
        g: 0xC4,
        b: 0xEA,
    }, // 35
    Color {
        r: 0xFE,
        g: 0xCC,
        b: 0xC5,
    }, // 36
    Color {
        r: 0xF7,
        g: 0xD8,
        b: 0xA5,
    }, // 37
    Color {
        r: 0xE4,
        g: 0xE5,
        b: 0x94,
    }, // 38
    Color {
        r: 0xCF,
        g: 0xEF,
        b: 0x96,
    }, // 39
    Color {
        r: 0xBD,
        g: 0xF4,
        b: 0xAB,
    }, // 3A
    Color {
        r: 0xB3,
        g: 0xF3,
        b: 0xCC,
    }, // 3B
    Color {
        r: 0xB5,
        g: 0xEB,
        b: 0xF2,
    }, // 3C
    Color {
        r: 0xB8,
        g: 0xB8,
        b: 0xB8,
    }, // 3D
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 3E
    Color {
        r: 0x00,
        g: 0x00,
        b: 0x00,
    }, // 3F
];

#[derive(Debug, Clone)]
pub struct Palette {
    colors: &'static [Color; 0x40],
    ram: [u8; 0x20],
}

impl Palette {
    pub fn color(&self, idx: u8) -> Color {
        debug_assert!(idx < 0x20, "Palette index out of range");
        self.colors[(self.ram[idx as usize] & 0x3F) as usize]
    }

    pub fn ram(&self, offset: u8) -> u8 {
        self.ram[offset as usize]
    }
}

#[derive(Clone)]
pub struct PPU {
    // registers: PPURegisters,
    pub frame_count: usize,
    nmi_enabled: bool,
    // nmi_waiting: bool,
    pub even: bool,
    pub scanline: usize,
    pub pixel: usize,

    vram_buffer: u8,

    pub mask: Mask,
    pub vblank: bool,
    pub sprite_zero_hit: bool,

    pub palette: Palette,
    pub background: Background,
    pub oam: OAM,
    pub render_buffer: RenderBuffer<256, 240>,
    pub dbg_int: bool,
    pub cycle: usize,
}

bitfield::bitfield! {
    pub struct PPUStatus(u8);
    impl Debug;
    vblank, set_vblank: 7;
    sprite0, set_sprite0: 6;
    sprite_overflow, set_sprite_overflow: 5;
}

impl std::fmt::Debug for PPU {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "PPU: f {}, s {}, p {}",
            self.frame_count, self.scanline, self.pixel
        )
    }
}

impl PPU {
    // pub fn with_chr_rom(rom: &[u8], mapper: Mapper) -> Self {
    //     todo!()
    // }
    pub fn init() -> Self {
        Self {
            cycle: 25,
            dbg_int: false,

            mask: Mask {
                grayscale: false,
                background_on_left_edge: false,
                sprites_on_left_edge: false,
                enable_background: false,
                enable_sprites: false,
                em_red: false,
                em_green: false,
                em_blue: false,
            },
            palette: Palette {
                colors: COLORS,
                ram: [
                    0x09, 0x01, 0x00, 0x01, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C,
                    0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
                    0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
                ],
            },
            vblank: false,
            sprite_zero_hit: false,
            frame_count: 0,
            nmi_enabled: false,
            even: false,
            scanline: 0,
            pixel: 25,
            render_buffer: RenderBuffer::empty(),
            background: Background {
                v: 0,
                t: 0,
                x: 0,
                w: false,
                copy_v: 0,
                vram_column: false,
                second_pattern: false,
                cur_high: 0,
                cur_low: 0,
                cur_shift_high: 0,
                cur_shift_low: 0,
                cur_attr_shift_high: 0,
                cur_attr_shift_low: 0,
                cur_nametable: 0,
                next_attr: 0,
            },
            oam: OAM::new(),
            vram_buffer: 0,
        }
    }
    pub fn reset(&mut self) {
        *self = Self::init();
    }

    pub fn rendering_enabled(&self) -> bool {
        self.mask.enable_background || self.mask.enable_sprites
    }

    pub fn read_reg(&mut self, mem: &mut PpuMem<'_>, offset: u16) -> u8 {
        match offset {
            0 => panic!("ppuctrl is write-only"),
            1 => panic!("ppumask is write-only"),
            2 => {
                let tmp = if self.vblank { 0b1000_0000 } else { 0 }
                    | if self.sprite_zero_hit { 0b0100_0000 } else { 0 }
                    | if self.oam.overflow { 0b0010_0000 } else { 0 };
                self.vblank = false;
                self.background.w = false;
                tmp
            }
            3 => panic!("oamaddr is write-only"),
            4 => self.oam.mem[self.oam.addr as usize],
            5 => panic!("ppuscroll is write-only"),
            6 => panic!("ppuaddr is write-only"),
            7 => {
                let val = match mem.read(self.background.v) {
                    Value::Value(v) => {
                        let val = self.vram_buffer;
                        self.vram_buffer = v;
                        val
                    }
                    Value::Register {
                        reg: PPUMMRegisters::Palette,
                        offset,
                    } => self.palette.ram[offset as usize],
                };
                self.increment_v();
                val
            }
            // 7 => self.registers.data,
            _ => panic!("No register at {:02X}", offset),
        }
    }
    pub fn write_reg(&mut self, mem: &mut PpuMem, offset: u16, mut val: u8) {
        match offset {
            0x00 => {
                self.background.t =
                    (self.background.t & !0b0000_1100_0000_0000) | (((val & 0b11) as u16) << 10);
                self.background.vram_column = val & 0b0000_0100 != 0;
                self.oam.sprite_offset_0x1000 = val & 0b0000_1000 != 0;
                self.background.second_pattern = val & 0b0001_0000 != 0;
                self.oam.large_sprites = val & 0b0010_0000 != 0;
                if val & 0b0100_0000 != 0 {
                    println!(
                        "WARNING: Bit 6 set in PPUCTRL - may cause damage on physical hardware"
                    );
                }
                self.nmi_enabled = val & 0b1000_0000 != 0;
            }
            0x01 => {
                // self.dbg_int = true;
                self.mask.grayscale = val & 0b0000_0001 != 0;
                self.mask.background_on_left_edge = val & 0b0000_0010 != 0;
                self.mask.sprites_on_left_edge = val & 0b0000_0100 != 0;
                self.mask.enable_background = val & 0b0000_1000 != 0;
                self.mask.enable_sprites = val & 0b0001_0000 != 0;
                self.mask.em_red = val & 0b0010_0000 != 0;
                self.mask.em_green = val & 0b0100_0000 != 0;
                self.mask.em_blue = val & 0b1000_0000 != 0;
                // todo!("Mask: {:02X}", val)
            }
            0x02 => {
                todo!("Unable to write to PPU status")
            }
            0x03 => self.oam.addr = val,
            0x04 => {
                if self.oam.addr % 4 == 2 {
                    val &= 0b11100011;
                }
                self.oam.mem[self.oam.addr as usize] = val;
                self.oam.addr = self.oam.addr.wrapping_add(1);
                self.oam.edit_ver += 1;
            }
            0x05 => {
                if self.background.w {
                    // Is y scroll
                    self.background.t = (self.background.t & 0b0000_1100_0001_1111)
                        | (((val as u16) << 2) & 0b0000_0011_1110_0000)
                        | (((val as u16) << 12) & 0b0111_0000_0000_0000);
                    self.background.w = false;
                } else {
                    // Is x scroll
                    self.background.x = val & 0b111; // Lowest three bits
                    self.background.t =
                        (self.background.t & 0b0111_1111_1110_0000) | (val as u16 >> 3);
                    self.background.w = true;
                }
            }
            0x06 => {
                if self.background.w {
                    self.background.t =
                        u16::from_le_bytes([val, self.background.t.to_le_bytes()[1]]);
                    self.background.w = false;
                    self.background.copy_v = 2; // Set t to be copied to v in a pixel or so
                } else {
                    self.background.t =
                        u16::from_le_bytes([self.background.t.to_le_bytes()[0], val & 0b0011_1111]);
                    self.background.w = true;
                }
            }
            0x07 => {
                // println!("Writing: {:02X}, @{:04X}", val, self.background.v);
                mem.write(self.background.v, val, |r, mut o, v| match r {
                    PPUMMRegisters::Palette => {
                        // println!("Writing {:02X} to {:02X}", v & 0x3F, o);
                        if o % 4 == 0 {
                            o = o & !0b1_0000;
                            self.palette.ram[o as usize] = v & 0x3F;
                            self.palette.ram[(o | 0b1_0000) as usize] = v & 0x3F;
                        } else {
                            self.palette.ram[o as usize] = v & 0x3F;
                        }
                    }
                });
                self.increment_v();
            }
            _ => panic!("No register at {:02X}", offset),
        }
    }
    /// Apply either row wise or column wise increment
    fn increment_v(&mut self) {
        if self.background.vram_column {
            // if self.background.v
            self.background.v += 32;
        } else {
            self.background.v += 1;
        }
    }

    pub fn run_one_clock_cycle(&mut self, mem: &mut PpuMem<'_>) -> bool {
        self.cycle += 1;
        self.pixel += 1;
        if self.background.copy_v > 0 {
            self.background.copy_v -= 1;
            if self.background.copy_v == 0 {
                self.background.v = self.background.t;
            }
        }
        if self.scanline == 261
            && (self.pixel == 341 || (self.pixel == 340 && self.even && self.rendering_enabled()))
        {
            self.scanline = 0;
            self.pixel = 0;
            self.even = !self.even;
            self.frame_count += 1;
        }
        if self.pixel == 341 {
            self.pixel = 0;
            self.scanline += 1;
        }
        if self.mask.enable_background || self.mask.enable_sprites {
            if self.scanline < 240 || self.scanline == 261 {
                if self.pixel == 257 {
                    self.background.v = (self.background.v & 0b0111_1011_1110_0000)
                        | (self.background.t & 0b0000_0100_0001_1111);
                }
                if self.pixel >= 280 && self.pixel <= 304 && self.scanline == 261 {
                    self.background.v = (self.background.v & 0b0000_0100_0001_1111)
                        | (self.background.t & 0b0111_1011_1110_0000);
                }
                if self.scanline != 261 {
                    self.oam.ppu_cycle(self.pixel, self.scanline, mem);
                }
                if self.pixel == 0 {
                    if self.scanline == 1 {
                        // let h_scroll_offset = self.background.x as usize + ((self.background.t as usize & 0b11) << 3);
                        // dbg!(h_scroll_offset);
                        // dbg!((self.scanline, &self.oam.sprite_output_units[0]));
                        // dbg!(&self.oam.secondary);
                    }
                    // self.dbg_int = true;
                    // idle cycle
                } else if self.pixel < 257 || self.pixel > 320 {
                    // self.dbg_int = true;
                    // const POS: u32 = 1 << 15;
                    let bg_color = if self.mask.enable_background {
                        let bit_pos = 15 - self.background.x;
                        // Determine background color
                        let a = (self.background.cur_shift_high >> bit_pos) & 1;
                        let b = (self.background.cur_shift_low >> bit_pos) & 1;
                        let val = (a << 1) | b;
                        debug_assert!(val < 4);

                        let a = (self.background.cur_attr_shift_high >> bit_pos) & 1;
                        let b = (self.background.cur_attr_shift_low >> bit_pos) & 1;
                        let palette = ((a << 1) | b) as u8;
                        debug_assert!(palette < 4);
                        val as u8 + if val != 0 { palette * 4 } else { 0 }
                    } else {
                        0
                    };

                    if self.scanline < 240 && self.pixel < 257 {
                        let color = if let Some((sp_color, above, is_sprite_zero)) = self
                            .mask
                            .enable_sprites
                            .then_some(())
                            .and(self.oam.pixel(self.pixel, self.scanline))
                        {
                            if is_sprite_zero && sp_color != 0 && bg_color != 0 {
                                self.sprite_zero_hit = true;
                            }
                            if sp_color == 0 || (bg_color != 0 && !above) {
                                bg_color
                            } else {
                                sp_color
                            }
                        } else {
                            bg_color
                        };
                        // Write to screen
                        self.render_buffer.write(
                            self.scanline,
                            self.pixel - 1,
                            self.palette.color(color),
                        );
                    }
                    if self.pixel < 337 {
                        self.background.cur_shift_high <<= 1;
                        self.background.cur_shift_low <<= 1;
                        self.background.cur_attr_shift_high <<= 1;
                        self.background.cur_attr_shift_low <<= 1;
                    }

                    if self.scanline < 240 || self.scanline == 261 {
                        if self.pixel <= 260 || self.pixel >= 321 {
                            if self.pixel % 8 == 2 {
                                // Name table fetch
                                // let addr = 0x2000 + self.pixel / 8 + self.scanline / 8;
                                let addr = 0x2000 | (self.background.v & 0x0FFF);
                                // println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
                                let val = mem.read(addr).reg_map(|_, _| 0);
                                self.background.cur_nametable = val;
                            } else if self.pixel % 8 == 4 {
                                // Attr table fetch
                                // let addr = 0x23C0 + self.pixel / 16 + self.scanline / 16;
                                let addr = 0x23C0
                                    | (self.background.v & 0x0C00)
                                    | ((self.background.v >> 4) & 0x38)
                                    | ((self.background.v >> 2) & 0x07);
                                // println!("Cur: {:04X}, comp: {:04X}", addr, addr_2);
                                // assert_eq!(addr, addr_2);
                                let val = mem
                                    .read(addr)
                                    .reg_map(|_, offset| self.palette.ram(offset as u8));
                                self.background.next_attr = val;
                            } else if self.pixel % 8 == 6 {
                                // BG pattern low
                                let addr = self.background.cur_nametable as u16 * 16
                                    + ((self.background.v & 0x7000) >> 12)
                                    // + (self.scanline % 8) as u16
                                    + if self.background.second_pattern {
                                        0x1000
                                    } else {
                                        0
                                    };
                                let val = mem.read(addr).reg_map(|_, _| 0);
                                self.background.cur_low = val;
                            } else if self.pixel % 8 == 0 && self.pixel > 0 {
                                let addr = self.background.cur_nametable as u16 * 16
                                    + 8
                                    + ((self.background.v & 0x7000) >> 12)
                                    // (self.scanline % 8) as u16
                                    + if self.background.second_pattern {
                                        0x1000
                                    } else {
                                        0
                                    };
                                let val = mem.read(addr).reg_map(|_, _| todo!());
                                self.background.cur_high = val;
                                self.background.cur_shift_low |= self.background.cur_low as u32;
                                self.background.cur_shift_high |= self.background.cur_high as u32;
                                let h_off = (self.background.v >> 1) % 2;
                                let v_off = (self.background.v >> 6) % 2;
                                let off = h_off * 2 + v_off * 4;
                                let palette = (self.background.next_attr >> off) & 0b11;
                                self.background.cur_attr_shift_low |=
                                    if palette & 0b01 == 0 { 0 } else { 0xFF };
                                self.background.cur_attr_shift_high |=
                                    if palette & 0b10 == 0 { 0 } else { 0xFF };
                                // Inc horizontal
                                if self.background.v & 0x1F == 31 {
                                    self.background.v = (self.background.v & !0x1F) ^ 0x400;
                                } else {
                                    self.background.v += 1;
                                }
                                // Inc vertical
                                if self.pixel == 256 {
                                    //  && self.scanline % 4 == 0
                                    if self.background.v & 0x7000 != 0x7000 {
                                        self.background.v += 0x1000;
                                    } else {
                                        self.background.v &= !0x7000;
                                        let mut y = (self.background.v & 0x03E0) >> 5;
                                        if y == 29 {
                                            y = 0;
                                            self.background.v ^= 0x0800
                                        } else if y == 31 {
                                            y = 0;
                                        } else {
                                            y += 1;
                                        }
                                        self.background.v =
                                            (self.background.v & !0x03E0) | (y << 5);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if self.scanline == 261 && self.pixel == 1 {
            self.vblank = false;
            self.sprite_zero_hit = false;
            self.oam.overflow = false;
        }
        if self.scanline == 241 && self.pixel == 1 {
            self.vblank = true;
            return true;
        }
        return false;
    }

    pub fn nmi_on_vblank(&self) -> bool {
        self.nmi_enabled
    }
    pub fn peek_nmi(&self) -> bool {
        self.vblank && self.nmi_enabled
    }
    pub fn nmi_waiting(&mut self) -> bool {
        self.vblank && self.nmi_enabled
    }
    pub fn peek_irq(&self) -> bool {
        false
    }
    pub fn irq_waiting(&mut self) -> bool {
        false
    }
    pub fn peek_oam(&self, addr: u8) -> u8 {
        self.oam.mem[addr as usize]
    }
    pub fn oam_edit_ver(&self) -> usize {
        self.oam.edit_ver
    }
}

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

    #[test]
    fn ppu_registers() {
        // let mut ppu = PPU::with_chr_rom(&[0u8; 8192], Mapper::from_header(0));
        let mut ppu = PPU::init();
        // let mut mem = MemoryMap::new(vec![Segment::ram("")]);
        let mut mem = Mapped::test_ram();
        let mut mem = PpuMem::new(&mut mem);
        assert_eq!(ppu.background.v, 0);
        assert_eq!(ppu.background.t, 0);
        assert_eq!(ppu.background.x, 0);
        assert_eq!(ppu.background.w, false);
        ppu.write_reg(&mut mem, 0, 0);
        assert_eq!(ppu.background.v, 0);
        ppu.write_reg(&mut mem, 0, 0b11);
        assert_eq!(
            ppu.background.t, 0b0001100_00000000,
            "Actual: {:016b}",
            ppu.background.t
        );
        assert_eq!(ppu.background.w, false);

        ppu.write_reg(&mut mem, 5, 0x7D);
        assert_eq!(
            ppu.background.t, 0b0001100_00001111,
            "Actual: {:016b}",
            ppu.background.t
        );
        assert_eq!(ppu.background.x, 0b101);
        assert_eq!(ppu.background.w, true);
        ppu.write_reg(&mut mem, 5, 0x5E);
        assert_eq!(
            ppu.background.t, 0b1101101_01101111,
            "Actual: {:016b}",
            ppu.background.t
        );
        assert_eq!(ppu.background.x, 0b101);
        assert_eq!(ppu.background.w, false);

        ppu.write_reg(&mut mem, 5, 0x7D);
        assert_eq!(
            ppu.background.t, 0b1101101_01101111,
            "Actual: {:016b}",
            ppu.background.t
        );
        assert_eq!(ppu.background.x, 0b101);
        assert_eq!(ppu.background.w, true);
        ppu.read_reg(&mut mem, 2);
        assert_eq!(ppu.background.w, false);
    }
}

#[cfg(feature = "iced")]
mod ppu_iced {
    use super::*;
    use iced::{
        Point, Size,
        advanced::graphics::geometry::Renderer,
        widget::canvas::{Fill, Frame},
    };

    impl Into<Fill> for Color {
        fn into(self) -> Fill {
            iced::Color::from_rgb8(self.r, self.g, self.b).into()
        }
    }

    impl PPU {
        pub fn render_name_table<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
            for y in 0..60 {
                for x in 0..64 {
                    let row = y % 30;
                    let col = x % 32;
                    let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
                    let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as u16 * 16
                        + if self.background.second_pattern {
                            0x1000
                        } else {
                            0
                        };
                    let attr = mem
                        .peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
                        .unwrap();
                    // attr << (((col & 1) << 1) | ((row & 1) << 0)) * 2
                    // let h_off = ((self.pixel - 1) / 16) % 2;
                    // let v_off = (self.scanline / 16) % 2;
                    // let off = v_off * 4 + h_off * 2;
                    let palette = (attr >> (((col & 2) << 0) | ((row & 2) << 1))) & 0x3;
                    for y_off in 0..8 {
                        let low = mem.peek_ppu(name + y_off).unwrap();
                        let high = mem.peek_ppu(name + y_off + 8).unwrap();
                        for bit in 0..8 {
                            frame.fill_rectangle(
                                Point::new(
                                    x as f32 * 8. + 8. - bit as f32,
                                    y as f32 * 8. + y_off as f32,
                                ),
                                Size::new(1., 1.),
                                match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
                                    (false, false) => self.palette.color(0),
                                    (true, false) => self.palette.color(1 + 4 * palette),
                                    (false, true) => self.palette.color(2 + 4 * palette),
                                    (true, true) => self.palette.color(3 + 4 * palette),
                                    // (false, false) => Color { r: 0, g: 0, b: 0 },
                                    // (true, false) => Color {
                                    //     r: 64,
                                    //     g: 64,
                                    //     b: 64,
                                    // },
                                    // (false, true) => Color {
                                    //     r: 128,
                                    //     g: 128,
                                    //     b: 128,
                                    // },
                                    // (true, true) => Color {
                                    //     r: 255,
                                    //     g: 255,
                                    //     b: 255,
                                    // },
                                },
                            );
                        }
                    }
                    // for
                    // let pat = mem.peek();
                }
            }
        }
        pub fn name_cursor_info(&self, mem: &Mapped, cursor: Point) -> Option<String> {
            let x = (cursor.x / 8.) as usize;
            let y = (cursor.y / 8.) as usize;
            if x < 64 && y < 60 {
                let row = y % 30;
                let col = x % 32;
                let off = 0x2000 + 0x400 * (y / 30 * 2 + x / 32);
                let name = mem.peek_ppu((off + col + row * 32) as u16).unwrap() as usize;
                let attr = mem
                    .peek_ppu((col / 4 + row / 4 * 8 + 0x3C0 + off) as u16)
                    .unwrap();
                Some(format!(
                    "Row, Column: {}, {}
X, Y: {}, {}
Tilemap address: ${:04X}

Tile Index: ${:02X}
Tile Address (PPU): ${:04X}
Tile Address (CHR): ${:04X}

Palette Index {}
Palette Address ${:04X}

Attribute Address ${:04X}
Attribute data: ${:02X}
",
                    row,
                    col,
                    col * 8,
                    row * 8,
                    off + col + row * 32,
                    name,
                    name * 16
                        + if self.background.second_pattern {
                            0x1000
                        } else {
                            0
                        },
                    name * 16
                        + if self.background.second_pattern {
                            0x1000
                        } else {
                            0
                        },
                    (attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3,
                    ((attr >> (((col & 1) << 1) | ((row & 1) << 2))) & 0x3) as usize * 4 + 0x3F00,
                    col / 4 + row / 4 * 8 + 0x3C0 + off,
                    attr,
                ))
            } else {
                None
            }
        }

        pub fn render_pattern_tables<R: Renderer>(&self, mem: &Mapped, frame: &mut Frame<R>) {
            for y in 0..16 {
                for x in 0..16 {
                    let name = (y * 16 + x) * 16;
                    for y_off in 0..8 {
                        let low = mem.peek_ppu(name + y_off).unwrap();
                        let high = mem.peek_ppu(name + y_off + 8).unwrap();
                        for bit in 0..8 {
                            frame.fill_rectangle(
                                Point::new(
                                    x as f32 * 8. + 8. - bit as f32,
                                    y as f32 * 8. + y_off as f32,
                                ),
                                Size::new(1., 1.),
                                match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
                                    (false, false) => Color { r: 0, g: 0, b: 0 },
                                    (true, false) => Color {
                                        r: 64,
                                        g: 64,
                                        b: 64,
                                    },
                                    (false, true) => Color {
                                        r: 128,
                                        g: 128,
                                        b: 128,
                                    },
                                    (true, true) => Color {
                                        r: 255,
                                        g: 255,
                                        b: 255,
                                    },
                                },
                            );
                        }
                    }
                }
            }

            for y in 0..16 {
                for x in 0..16 {
                    let name = (y * 16 + x) * 16;
                    for y_off in 0..8 {
                        let low = mem.peek_ppu(name + y_off + 0x1000).unwrap();
                        let high = mem.peek_ppu(name + y_off + 8 + 0x1000).unwrap();
                        for bit in 0..8 {
                            frame.fill_rectangle(
                                Point::new(
                                    x as f32 * 8. + 8. - bit as f32,
                                    y as f32 * 8. + y_off as f32 + 130.,
                                ),
                                Size::new(1., 1.),
                                match (low & (1 << bit) != 0, high & (1 << bit) != 0) {
                                    (false, false) => Color { r: 0, g: 0, b: 0 },
                                    (true, false) => Color {
                                        r: 64,
                                        g: 64,
                                        b: 64,
                                    },
                                    (false, true) => Color {
                                        r: 128,
                                        g: 128,
                                        b: 128,
                                    },
                                    (true, true) => Color {
                                        r: 255,
                                        g: 255,
                                        b: 255,
                                    },
                                },
                            );
                        }
                    }
                    // for
                    // let pat = mem.peek();
                }
            }
        }
        pub fn pattern_cursor_info(&self, cursor: Point) -> Option<String> {
            let x = (cursor.x / 8.) as usize;
            let y = (cursor.y / 8.) as usize;
            if x < 16 && y < 32 {
                Some(format!(
                    "Tile address (PPU):   {:04X}\nTile address (CHR):    {:04X}\nIndex:    {:02X}",
                    (y * 16 + x) * 16,
                    (y * 16 + x) * 16,
                    ((y % 16) * 16 + x),
                ))
            } else {
                None
            }
        }

        pub fn render_palette<R: Renderer>(&self, frame: &mut Frame<R>) {
            for y in 0..8 {
                for x in 0..4 {
                    frame.fill_rectangle(
                        Point::new(x as f32 * 10., y as f32 * 10.),
                        Size::new(10., 10.),
                        self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
                    );
                }
            }
        }
        pub fn palette_cursor_info(&self, cursor: Point) -> Option<String> {
            let x = (cursor.x / 10.) as usize;
            let y = (cursor.y / 10.) as usize;
            if x < 4 && y < 8 {
                Some(format!(
                    "Index:   {:02X}\nValue:    {:02X}\nColor code:    {}",
                    x + y * 4,
                    self.palette.ram[x + y * 4] & 0x3F,
                    self.palette.colors[(self.palette.ram[x + y * 4] & 0x3F) as usize],
                ))
            } else {
                None
            }
        }
    }
}
