use log::debug;
use crate::periph::mos6520::Mos6520;
use crate::traits::memory_chip::MemoryChip;
use crate::traits::ram_chip::RamChip;
use crate::traits::rom_chip::RomChip;
use crate::traits::via_chip::ViaChip;

impl RomChip for Mos6520 {
    fn program(new_data: &[u8]) -> Box<Self> {
        debug!("This has no rom.  Cant program.");
        todo!()
    }
}

impl RamChip for Mos6520 {
    fn write(&mut self, offset: &u16, value: &u8) {
        debug!("This has no ROM / ${offset:04x} ${value:02x}");
    }
}

impl MemoryChip for Mos6520 {
    fn read(&self, offset: &u16) -> u8 {
        debug!("This has no ROM ${offset:04x}");
        0
    }
}

impl ViaChip for Mos6520 {
    fn set_port_ddr(&mut self, port_index: u8, value: u8) {
        match port_index {
            1 => {
                self.ddra = value;
            },
            2 => {
                self.ddrb = value;
            }
            _ => {
                debug!("Invalid Port");
            }
        }

        self.update_ports();
    }

    fn set_port_data(&mut self, port_index: u8, value: u8) {
        match port_index {
            1 => {
                self.port_a = value;
            },
            2 => {
                self.port_b = value;
            }
            _ => {
                debug!("Invalid Port");
            }
        }
        self.update_ports();
    }
}

impl Mos6520 {
    fn update_ports(&mut self) {
        debug!("PortA: DDR {:08b}", self.port_a);
        debug!("PortB: DDR {:08b}", self.port_b);
    }
}

#[cfg(test)]
mod test {
    use crate::periph::mos6520::Mos6520;

    const DDRA_OFFSET: u8 = 0x00;
    const PORTA_OFFSET: u8 = 0x01;
    const DDRB_OFFSET: u8 = 0x02;
    const PORTB_OFFSET: u8 = 0x03;

    fn actual(base: u16, offset: u8) -> u16 {
        base + offset as u16
    }

    #[test]
    fn ddrb_tests() {
        let mut x = Mos6520::new(0x1000);
        let params = vec![
            // Offset      data  outa  ina   ddra  porta
            (DDRB_OFFSET,  0xff, 0x00, 0x00, 0xff, 0x00),
            (PORTB_OFFSET, 0xff, 0xff, 0x00, 0xff, 0xff),
            (DDRB_OFFSET,  0xaa, 0xaa, 0x55, 0xaa, 0xff),
            (DDRB_OFFSET,  0x55, 0x55, 0xaa, 0x55, 0xff),
            (PORTB_OFFSET, 0xf0, 0x50, 0xa0, 0x55, 0xf0),
            (PORTB_OFFSET, 0x0f, 0x05, 0x0a, 0x55, 0x0f),
            (DDRB_OFFSET,  0xff, 0x0f, 0x00, 0xff, 0x0f)
        ];

        for (offset, data, outb, inb, ddrb, portb) in params {
            x.tick(actual(x.offset, offset), data);
            assert_eq!(outb, x.out_b);
            assert_eq!(inb, x.in_b);
            assert_eq!(ddrb, x.ddrb);
            assert_eq!(portb, x.port_b);
        }
    }

    #[test]
    fn ddra_tests() {
        let mut x = Mos6520::new(0x1000);
        let params = vec![
            // Offset      data  outa  ina   ddra  porta
            (DDRA_OFFSET,  0xff, 0x00, 0x00, 0xff, 0x00),
            (PORTA_OFFSET, 0xff, 0xff, 0x00, 0xff, 0xff),
            (DDRA_OFFSET,  0xaa, 0xaa, 0x55, 0xaa, 0xff),
            (DDRA_OFFSET,  0x55, 0x55, 0xaa, 0x55, 0xff),
            (PORTA_OFFSET, 0xf0, 0x50, 0xa0, 0x55, 0xf0),
            (PORTA_OFFSET, 0x0f, 0x05, 0x0a, 0x55, 0x0f),
            (DDRA_OFFSET,  0xff, 0x0f, 0x00, 0xff, 0x0f)
        ];

        for (offset, data, outa, ina, ddra, porta) in params {
            x.tick(actual(x.offset, offset), data);
            assert_eq!(outa, x.out_a);
            assert_eq!(ina, x.in_a);
            assert_eq!(ddra, x.ddra);
            assert_eq!(porta, x.port_a);
        }
    }
}
