use crate::nes::bus::bus::BusDevice;
use crate::nes::cartridge::Cartridge;
use crate::nes::ppu;
use crate::platform::debugging::log_info;
use std::cell::RefCell;
use std::ops::RangeInclusive;
use std::rc::Rc;

pub(crate) struct MapperDevice {
    cartridge: Rc<RefCell<Option<Rc<RefCell<Cartridge>>>>>,
    ppu: Rc<RefCell<ppu::Ppu>>,
}

impl MapperDevice {
    pub(crate) fn new(
        cartridge: Rc<RefCell<Option<Rc<RefCell<Cartridge>>>>>,
        ppu: Rc<RefCell<ppu::Ppu>>,
    ) -> Self {
        Self { cartridge, ppu }
    }
}

impl BusDevice for MapperDevice {
    fn read(&mut self, addr: u16, open_bus: u8, _is_dummy_read: bool) -> Option<u8> {
        let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
            return match addr {
                0x4020..=0x5FFF => Some(open_bus),
                0x6000..=0x7FFF => {
                    log_info(format!(
                        "Warning: Read from PRG-RAM {:04X} without cartridge, returning 0",
                        addr
                    ));
                    Some(0)
                }
                0x8000..=0xFFFF => panic!("No cartridge mapped, cannot read from {:04X}", addr),
                _ => None,
            };
        };

        Some(
            cartridge
                .borrow()
                .mapper()
                .read_prg_open_bus(addr, open_bus),
        )
    }

    fn write(&mut self, addr: u16, value: u8, _is_dummy_write: bool) -> bool {
        let Some(cartridge) = self.cartridge.borrow().as_ref().cloned() else {
            match addr {
                0x4020..=0x5FFF => log_info(format!(
                    "Warning: Write to mapper expansion area {:04X} without cartridge, ignored",
                    addr
                )),
                0x6000..=0x7FFF => log_info(format!(
                    "Warning: Write to PRG-RAM {:04X} without cartridge, ignored",
                    addr
                )),
                0x8000..=0xFFFF => log_info(format!(
                    "Warning: Write to PRG ROM area {:04X} without cartridge, ignored",
                    addr
                )),
                _ => {}
            }
            return true;
        };

        let old_mirroring = cartridge.borrow().mapper().get_mirroring();
        cartridge.borrow_mut().mapper_mut().write_prg(addr, value);
        let new_mirroring = cartridge.borrow().mapper().get_mirroring();
        if new_mirroring != old_mirroring {
            self.ppu.borrow_mut().set_mirroring(new_mirroring);
        }

        true
    }

    fn address_range(&self) -> RangeInclusive<u16> {
        0x4020..=0xFFFF
    }
}
