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::{ReadOnly, ReadWrite},
};

use crate::pll::{PllCfg, WrpllData};

// const CLK_COREPLL: u32 = 0;
// const CLK_DDRPLL: u32 = 1;
// const CLK_GEMGXLPLL: u32 = 2;
// const CLK_DVFSCOREPLL: u32 = 3;
// const CLK_HFPCLKPLL: u32 = 4;
// const CLK_CLTXPLL: u32 = 5;
// const CLK_TLCLK: u32 = 6;
const CLK_PCLK: u32 = 7;
// const CLK_PCIE_AUX: u32 = 8;

register_structs! {
    Regs {
        (0x00 => hfxosccfg: ReadWrite<u32>),
        (0x04 => core_pllcfg: ReadWrite<u32>),
        (0x08 => core_plloutdiv: ReadWrite<u32>),
        (0x0C => ddr_pllcfg: ReadWrite<u32>),
        (0x10 => ddr_plloutdiv: ReadWrite<u32>),
        (0x14 => _0),
        (0x1C => gemgxl_pllcfg: ReadWrite<u32, PllCfg::Register>),
        (0x20 => gemgxl_plloutdiv: ReadWrite<u32>),
        (0x24 => core_clk_sel_reg: ReadWrite<u32>),
        (0x28 => devices_reset_n: ReadWrite<u32>),
        (0x2C => clk_mux_status: ReadOnly<u32>),
        (0x30 => _1),
        (0x38 => dvfs_core_pllcfg: ReadWrite<u32>),
        (0x3C => dvfs_core_plloutdiv: ReadWrite<u32>),
        (0x40 => corepllsel: ReadWrite<u32>),
        (0x44 => _2),
        (0x50 => hfpclk_pllcfg: ReadWrite<u32, PllCfg::Register>),
        (0x54 => hfpclk_plloutdiv: ReadWrite<u32>),
        (0x58 => hfpclkpllsel: ReadWrite<u32>),
        (0x5C => hfpclk_div_reg: ReadWrite<u32>),
        (0x60 => _3),
        (0xE0 => prci_plls: ReadOnly<u32>),
        (0xE4 => _4),
        (0x100 => @END),
    }
}

struct Prci {
    clk_hfclk: ClockHandle,
    clk_rtcclk: ClockHandle,
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}

impl Regs {
    fn read_hfpclk_pll(&self) -> WrpllData {
        let reg = self.hfpclk_pllcfg.extract();
        let divr = reg.read(PllCfg::PLLR);
        let divq = reg.read(PllCfg::PLLQ);
        let divf = reg.read(PllCfg::PLLF);
        WrpllData {
            divq,
            divr,
            divf,
            int_feedback: true,
        }
    }
}

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

impl ClockController for Prci {
    fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
        // hart frequency: coreclk, supplied either by corepll or dvfscorepll, selected by
        // corepllsel
        // hfpclkpll: clock for SPI, UART, GPIO, I2C, PWM
        //
        let _ = &self.clk_rtcclk;
        let regs = self.regs.lock();
        match clock {
            Some(CLK_PCLK) => {
                // PCLK calculation:
                // pclk <- X / hfpclkdiv
                // case [hfpclkpllsel]:
                //  1: X <- (hfclk)
                //  0: X <- hfpclkpllcfg/out & hfpclkpll
                // hfpclkdiv <- [hfpclk_div_reg] + 2

                let hfpclkpllsel = regs.hfpclkpllsel.get() & 1 != 0;
                let hfpclk_div_reg = regs.hfpclk_div_reg.get() + 2;
                let x = match hfpclkpllsel {
                    true => self.clk_hfclk.rate()?,
                    false => {
                        let pclk_pll = regs.read_hfpclk_pll();
                        pclk_pll.output_rate(self.clk_hfclk.rate()?)
                    }
                };
                Ok(x / hfpclk_div_reg)
            }
            Some(_) => {
                todo!();
            }
            None => {
                todo!();
            }
        }
    }

    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> {
        Ok(())
    }

    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,fu740-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_hfclk = node.clock(0)?;
            let clk_rtcclk = node.clock(1)?;

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

            let prci = Arc::new(Prci {
                regs: IrqSafeSpinlock::new(regs),
                clk_hfclk,
                clk_rtcclk
            });

            node.make_clock_controller(prci.clone());

            Some(prci)
        }
    }
}
