use alloc::sync::Arc;
use device_api::{
    clock::{ClockController, ClockHandle, Hertz},
    device::Device,
};
use device_tree::{
    DeviceTreePropertyRead, TProp,
    driver::{DeviceTreeClockController, Node, ProbeContext, device_tree_driver},
};
use libk::error::Error;
use libk_mm::device::DeviceMemoryIo;
use libk_util::sync::IrqSafeSpinlock;
use tock_registers::{interfaces::Readable, register_structs, registers::ReadWrite};

const CLK_COREPLL: u32 = 0;
const CLK_DDRPLL: u32 = 1;
const CLK_GEMGXLPLL: u32 = 2;
const CLK_TLCLK: u32 = 3;

register_structs! {
    Regs {
        (0x000 => hfxosccfg: ReadWrite<u32>),
        (0x004 => corepllcfg0: ReadWrite<u32>),
        (0x008 => _0),
        (0x00C => ddrpllcfg0: ReadWrite<u32>),
        (0x010 => ddrpllcfg1: ReadWrite<u32>),
        (0x014 => _1),
        (0x01C => gemgxlpllcfg0: ReadWrite<u32>),
        (0x020 => gemgxlpllcfg1: ReadWrite<u32>),
        (0x024 => coreclksel: ReadWrite<u32>),
        (0x028 => deviceresetreg: ReadWrite<u32>),
        (0x02C => _2),
        (0x100 => @END),
    }
}

struct Prci {
    clk_osc: ClockHandle,
    clk_rtc: ClockHandle,
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}

impl Device for Prci {
    fn display_name(&self) -> &str {
        "sifive,fu540-c000-prci"
    }
}

impl ClockController for Prci {
    fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
        let _ = &self.clk_rtc;
        let regs = self.regs.lock();
        match clock {
            Some(CLK_TLCLK) => {
                // 0 = CORE_PLL
                // 1 = HFCLK
                let coreclksel = regs.coreclksel.get() & 1 != 0;
                let glcm = if coreclksel {
                    self.clk_osc.rate()?
                } else {
                    todo!()
                };
                Ok(glcm / 2)
            }
            Some(_) => todo!(),
            None => Err(Error::InvalidArgument),
        }
    }

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

    fn enable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
        match clock {
            Some(CLK_GEMGXLPLL) | Some(CLK_DDRPLL) | Some(CLK_COREPLL) => todo!(),
            Some(CLK_TLCLK) => Ok(()),
            _ => Err(Error::InvalidArgument),
        }
    }

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

impl DeviceTreeClockController for Prci {
    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: ["sifive,fu540-c000-prci"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            let base = node.map_base(context, 0)?;
            let clk_osc = node.clock(0)?;
            let clk_rtc = node.clock(1)?;

            let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;

            let prci = Arc::new(Prci {
                regs: IrqSafeSpinlock::new(regs),
                clk_osc,
                clk_rtc
            });

            node.make_clock_controller(prci.clone());

            Some(prci)
        }
    }
}
