use super::GpuMode;

pub const CONTROL: usize = 0xFF40; // LCD Control
pub const STAT: usize = 0xFF41; // LCD Controller status
pub const SCY: usize = 0xFF42;
pub const SCX: usize = 0xFF43;
pub const LY: usize = 0xFF44; // read-only
pub const LYC: usize = 0xFF45;
pub const BGP: usize = 0xFF47; // ignored in CGB mode
pub const OBP_0: usize = 0xFF48; // ignored in CGB mode
pub const OBP_1: usize = 0xFF49; // ignored in CGB mode
pub const WY: usize = 0xFF4A;
pub const WX: usize = 0xFF4B;

pub enum LcdControl {
    BgDisplayEnable = 0,
    ObjDisplayEnable = 1,
    ObjSize = 2,
    BgTileMapDisplaySelect = 3,
    BgWindowTileDataSelect = 4,
    WindowDisplayEnable = 5,
    WindowTileMapDisplaySelect = 6,
    LcdDisplayEnable = 7,
}
impl LcdControl {
    pub fn is_set(self, register: u8) -> bool {
        let v = self as usize;
        ((register >> v) & 0x01) == 0x01
    }
}

pub enum LcdControllerStatus {
    HBlankInterrupt = 3,
    VBlankInterrupt = 4,
    OamInterrupt = 5,
    LyCoincidenceInterrupt = 6,
}
impl LcdControllerStatus {
    pub fn is_set(self, register: u8) -> bool {
        let v = self as usize;
        ((register >> v) & 0x01) == 0x01
    }

    pub fn with_mode(register: u8, mode: GpuMode) -> u8 {
        (register & 0xFC) | (mode as u8)
    }

    /// Set the register's bit 2 to true if LYC=LY, false otherwise.
    pub fn with_coincidence_flag(register: u8, coincidence: bool) -> u8 {
        (register & 0xFB) | if coincidence { 0x04 } else { 0x00 }
    }
}

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

    #[test]
    fn test_lcd_control_is_set() {
        assert_eq!(BgDisplayEnable.is_set(1 << 0), true);
        assert_eq!(ObjDisplayEnable.is_set(1 << 1), true);
        assert_eq!(ObjSize.is_set(1 << 2), true);
        assert_eq!(BgTileMapDisplaySelect.is_set(1 << 3), true);
        assert_eq!(BgWindowTileDataSelect.is_set(1 << 4), true);
        assert_eq!(WindowDisplayEnable.is_set(1 << 5), true);
        assert_eq!(WindowTileMapDisplaySelect.is_set(1 << 6), true);
        assert_eq!(LcdDisplayEnable.is_set(1 << 7), true);
    }

    #[test]
    fn test_lcdc_status_is_set() {
        assert_eq!(HBlankInterrupt.is_set(1 << 3), true);
        assert_eq!(VBlankInterrupt.is_set(1 << 4), true);
        assert_eq!(OamInterrupt.is_set(1 << 5), true);
        assert_eq!(LyCoincidenceInterrupt.is_set(1 << 6), true);
    }

    #[test]
    fn test_lcdc_status_with_mode() {
        use crate::gpu::GpuMode::*;
        let lcdc_status = 0b_0110_1011;
        assert_eq!(
            LcdControllerStatus::with_mode(lcdc_status, H_Blank),
            0b_0110_1000
        );
        assert_eq!(
            LcdControllerStatus::with_mode(lcdc_status, V_Blank),
            0b_0110_1001
        );
        assert_eq!(
            LcdControllerStatus::with_mode(lcdc_status, OAM_Read),
            0b_0110_1010
        );
        assert_eq!(
            LcdControllerStatus::with_mode(lcdc_status, VRAM_Read),
            0b_0110_1011
        );
    }

    #[test]
    fn test_lcdc_status_with_coincidence_flag() {
        assert_eq!(
            LcdControllerStatus::with_coincidence_flag(0b_1011_0011, true),
            0b_1011_0111
        );
        assert_eq!(
            LcdControllerStatus::with_coincidence_flag(0b_1011_0111, false),
            0b_1011_0011
        );
    }
}
