use alloc::format;
use core::sync::atomic::{AtomicBool, Ordering};

use arm_scmi_rs::{Scmi, Shmem, Smc};
use ax_kspin::SpinNoIrq as Mutex;
use fdt_edit::Phandle;
use log::{info, warn};

use crate::{DriverGeneric, PlatformDevice, mmio::iomap, probe::OnProbeError, register::FdtInfo};

const SCMI_SHMEM_SIZE: usize = 0x100;
const RK3588_SCMI_SHMEM_BASE: usize = 0x10f000;

static SCMI: Mutex<Option<Scmi<Smc>>> = Mutex::new(None);
static SCMI_REGISTERED: AtomicBool = AtomicBool::new(false);

crate::model_register!(
    name: "ARM SCMI SMC",
    level: ProbeLevel::PostKernel,
    priority: ProbePriority::CLK,
    probe_kinds: &[
        ProbeKind::Fdt {
            compatibles: &["arm,scmi-smc"],
            on_probe: probe
        }
    ],
);

fn probe(info: FdtInfo<'_>, plat_dev: PlatformDevice) -> Result<(), OnProbeError> {
    let smc_id = info
        .node
        .as_node()
        .get_property("arm,smc-id")
        .and_then(|prop| prop.get_u32())
        .ok_or_else(|| OnProbeError::other(format!("[{}] has no arm,smc-id", info.node.name())))?;
    let shmem_phandle = info
        .node
        .as_node()
        .get_property("shmem")
        .and_then(|prop| prop.get_u32_iter().next())
        .ok_or_else(|| OnProbeError::other(format!("[{}] has no shmem", info.node.name())))?;
    let (shmem_addr, shmem_size) = info
        .node
        .regs()
        .into_iter()
        .next()
        .map(|reg| {
            (
                reg.address as usize,
                reg.size.unwrap_or(SCMI_SHMEM_SIZE as u64) as usize,
            )
        })
        .unwrap_or_else(|| {
            warn!(
                "[{}] SCMI shmem phandle {} cannot be resolved by rdrive; using RK3588 shmem \
                 fallback {:#x}+{:#x}",
                info.node.name(),
                shmem_phandle,
                RK3588_SCMI_SHMEM_BASE,
                SCMI_SHMEM_SIZE
            );
            (RK3588_SCMI_SHMEM_BASE, SCMI_SHMEM_SIZE)
        });
    let shmem_base = iomap(shmem_addr, shmem_size)?;

    let shmem = Shmem {
        address: shmem_base,
        bus_address: shmem_addr,
        size: shmem_size,
    };
    let scmi = Scmi::new(Smc::new(smc_id, None), shmem);
    *SCMI.lock() = Some(scmi);
    SCMI_REGISTERED.store(true, Ordering::Release);
    plat_dev.register(ScmiDevice);
    info!(
        "SCMI SMC registered: smc_id={:#x}, shmem_phandle={}, shmem={:#x}+{:#x}",
        smc_id, shmem_phandle, shmem_addr, shmem_size
    );
    Ok(())
}

pub fn clock_rate(_phandle: Phandle, clock_id: u32) -> Option<u64> {
    if !SCMI_REGISTERED.load(Ordering::Acquire) {
        warn!(
            "SCMI clock rate requested before SCMI registration: clock_id={:#x}",
            clock_id
        );
        return None;
    }
    let mut guard = SCMI.lock();
    let scmi = guard.as_mut()?;
    match scmi.clock_rate_get_direct(clock_id) {
        Ok(rate) => {
            info!(
                "SCMI clock rate get: clock_id={:#x}, rate={} Hz",
                clock_id, rate
            );
            Some(rate)
        }
        Err(err) => {
            warn!(
                "SCMI clock rate get failed: clock_id={:#x}, {:?}",
                clock_id, err
            );
            None
        }
    }
}

pub fn set_clock_rate(_phandle: Phandle, clock_id: u32, rate: u64) -> Option<()> {
    if !SCMI_REGISTERED.load(Ordering::Acquire) {
        warn!(
            "SCMI clock rate set requested before SCMI registration: clock_id={:#x}, rate={} Hz",
            clock_id, rate
        );
        return None;
    }
    let mut guard = SCMI.lock();
    let scmi = guard.as_mut()?;
    match scmi.clock_rate_set_direct(clock_id, rate) {
        Ok(()) => {
            info!(
                "SCMI clock rate set: clock_id={:#x}, rate={} Hz",
                clock_id, rate
            );
            Some(())
        }
        Err(err) => {
            warn!(
                "SCMI clock rate set failed: clock_id={:#x}, rate={} Hz, {:?}",
                clock_id, rate, err
            );
            None
        }
    }
}

struct ScmiDevice;

impl DriverGeneric for ScmiDevice {
    fn name(&self) -> &str {
        "arm-scmi-smc"
    }
}
