use alloc::sync::Arc;
use device_api::{
    clock::{ClockController, ClockHandle},
    device::{Device, DeviceInitContext},
};
use device_tree::{
    driver::{device_tree_driver, DeviceTreeClockController, Node, ProbeContext},
    DeviceTreePropertyRead, TProp,
};
use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
use tock_registers::{
    interfaces::ReadWriteable,
    register_bitfields, register_structs,
    registers::{ReadOnly, ReadWrite},
};
use yggdrasil_abi::error::Error;

register_bitfields! {
    u32,
    AUX_ENABLES [
        MU_ENABLE OFFSET(0) NUMBITS(1) [],
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    Regs {
        (0x00 => AUX_IRQ: ReadOnly<u32>),
        (0x04 => AUX_ENABLES: ReadWrite<u32, AUX_ENABLES::Register>),
        (0x08 => @END),
    }
}

struct Bcm2835Aux {
    // TODO will be used for parent `clocks` once I implement the driver
    _node: Arc<Node>,
    base: PhysicalAddress,
    regs: OneTimeInit<IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>>,
}

impl Bcm2835Aux {
    fn ensure_regs(&self) -> Result<&IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>, Error> {
        let base = self.base;
        self.regs.or_try_init_with(move || unsafe {
            DeviceMemoryIo::map(base, Default::default()).map(IrqSafeSpinlock::new)
        })
    }
}

impl ClockController for Bcm2835Aux {
    fn enable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
        let regs = self.ensure_regs()?;
        let regs = regs.lock();
        match clock {
            Some(0) => {
                regs.AUX_ENABLES.modify(AUX_ENABLES::MU_ENABLE::SET);
                Ok(())
            }
            None => todo!(),
            _ => Err(Error::InvalidArgument),
        }
    }

    fn disable_clock(&self, _clock: Option<u32>) -> Result<(), Error> {
        Err(Error::NotImplemented)
    }
}

impl DeviceTreeClockController for Bcm2835Aux {
    fn map_clock(self: Arc<Self>, property: &TProp, offset: usize) -> Option<(ClockHandle, usize)> {
        let clock = property.read_cell(offset, 1)? as u32;
        Some((
            ClockHandle {
                parent: self.clone(),
                clock: Some(clock),
            },
            1,
        ))
    }
}

impl Device for Bcm2835Aux {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        Ok(())
    }

    fn display_name(&self) -> &str {
        "bcm2835 AUX"
    }

    fn as_clock_controller(self: Arc<Self>) -> Option<Arc<dyn ClockController>> {
        Some(self)
    }
}

// TODO aux also has a parent `clocks`
device_tree_driver! {
    compatible: ["brcm,bcm2835-aux"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let base = node.map_base(context, 0)?;
            let aux = Arc::new(Bcm2835Aux {
                _node: node.clone(),
                base,
                regs: OneTimeInit::new(),
            });

            node.make_clock_controller(aux.clone());

            Some(aux)
        }
    }
}
