extern crate alloc;

use alloc::format;

use pcie::CommandRegister;
use rdrive::{
    PlatformDevice,
    probe::{
        OnProbeError,
        pci::{EndpointRc, FnOnProbe},
    },
};
use simple_ahci::{AhciDriver as SimpleAhciDriver, Hal as AhciHal};

use super::{SyncBlockOps, register_sync_block};

pub const DEVICE_NAME: &str = "ahci";

crate::model_register!(
    name: "AHCI",
    level: ProbeLevel::PostKernel,
    priority: ProbePriority::DEFAULT,
    probe_kinds: &[ProbeKind::Pci {
        on_probe: probe_pci as FnOnProbe,
    }],
);

struct AxAhciHal;

impl AhciHal for AxAhciHal {
    fn virt_to_phys(va: usize) -> usize {
        axklib::mem::virt_to_phys(va.into()).as_usize()
    }

    fn current_ms() -> u64 {
        0
    }

    fn flush_dcache() {}
}

fn probe_pci(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> {
    let class = endpoint.revision_and_class();
    if (class.base_class, class.sub_class) != (0x01, 0x06) {
        return Err(OnProbeError::NotMatch);
    }

    let Some(bar) = endpoint.bar_mmio(5).or_else(|| endpoint.bar_mmio(0)) else {
        return Err(OnProbeError::other("AHCI MMIO BAR missing"));
    };

    endpoint.update_command(|mut cmd| {
        cmd.insert(CommandRegister::MEMORY_ENABLE | CommandRegister::BUS_MASTER_ENABLE);
        cmd
    });

    let mmio = axklib::mmio::ioremap_raw(bar.start.into(), bar.count().max(1))
        .map_err(|err| OnProbeError::other(format!("failed to map AHCI BAR: {err:?}")))?;
    let Some(driver) = (unsafe { SimpleAhciDriver::<AxAhciHal>::try_new(mmio.as_ptr() as usize) })
    else {
        return Err(OnProbeError::other("failed to initialize AHCI controller"));
    };
    register_sync_block(plat_dev, AhciBlock(driver));
    Ok(())
}

struct AhciBlock(SimpleAhciDriver<AxAhciHal>);

impl SyncBlockOps for AhciBlock {
    fn name(&self) -> &'static str {
        DEVICE_NAME
    }

    fn num_blocks(&self) -> u64 {
        self.0.capacity()
    }

    fn block_size(&self) -> usize {
        self.0.block_size()
    }

    fn read_blocks(&mut self, block_id: u64, buf: &mut [u8]) -> Result<(), rd_block::BlkError> {
        if !buf.len().is_multiple_of(self.block_size())
            || !(buf.as_ptr() as usize).is_multiple_of(4)
        {
            return Err(rd_block::BlkError::NotSupported);
        }
        if self.0.read(block_id, buf) {
            Ok(())
        } else {
            Err(rd_block::BlkError::Other("AHCI read failed".into()))
        }
    }

    fn write_blocks(&mut self, block_id: u64, buf: &[u8]) -> Result<(), rd_block::BlkError> {
        if !buf.len().is_multiple_of(self.block_size())
            || !(buf.as_ptr() as usize).is_multiple_of(4)
        {
            return Err(rd_block::BlkError::NotSupported);
        }
        if self.0.write(block_id, buf) {
            Ok(())
        } else {
            Err(rd_block::BlkError::Other("AHCI write failed".into()))
        }
    }
}
