use alloc::sync::Arc;
use device_api::{
    clock::{ClockController, ClockHandle, Hertz},
    device::{Device, DeviceInitContext},
};
use device_tree::{
    DeviceTreePropertyRead, TProp,
    driver::{DeviceTreeClockController, Node, ProbeContext, device_tree_driver},
};
use libk::error::Error;

use crate::mbox::{Bcm2835Mbox, Bcm2835VcClock};

const CLK_VPU: u32 = 0x14;

struct Cprman {
    clk_osc: ClockHandle,
    mbox: Arc<Bcm2835Mbox>,
}

impl Device for Cprman {
    unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
        let _ = &self.clk_osc;
        Ok(())
    }

    fn display_name(&self) -> &str {
        "bcm2711-cprman"
    }
}

impl ClockController for Cprman {
    fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
        let vc_clock = match clock {
            Some(CLK_VPU) => Bcm2835VcClock::Core,
            Some(n) => todo!("clock_rate({n:#02x})"),
            None => return Err(Error::InvalidArgument),
        };

        let rate = self.mbox.vc_clock_rate(vc_clock)?;

        Ok(rate)
    }

    fn set_clock_rate(&self, clock: Option<u32>, rate: Hertz) -> Result<Hertz, Error> {
        let _ = clock;
        let _ = rate;
        todo!()
    }

    fn enable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
        let _ = clock;
        todo!()
    }

    fn disable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
        let _ = clock;
        todo!()
    }
}

impl DeviceTreeClockController for Cprman {
    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,
        ))
    }
}

device_tree_driver! {
    compatible: ["brcm,bcm2711-cprman"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let _base = node.map_base(context, 0)?;
            let mbox = Node::by_compatible("brcm,bcm2835-mbox", true).unwrap().device().unwrap();
            let mbox = Arc::downcast::<Bcm2835Mbox>(mbox.as_any()).unwrap();
            let clk_osc = node.clock(0)?;

            let cprman = Arc::new(Cprman {
                clk_osc,
                mbox,
            });

            node.make_clock_controller(cprman.clone());
            Some(cprman)
        }
    }
}
