use yggdrasil_abi::{error::Error, primitive_enum};

const EXT_HSM: u64 = 0x48534D;
const EXT_TIME: u64 = 0x54494D45;
const EXT_DBCN: u64 = 0x4442434E;
const EXT_SPI: u64 = 0x735049;
const EXT_SYSTEM_SHUTDOWN: u64 = 0x53525354;
const EXT_SYSTEM_SHUTDOWN_LEGACY: u64 = 0x08;

primitive_enum! {
    pub enum Status: i64 {
        Failed = -1,
        NotSupported = -2,
        InvalidParam = -3,
        Denied = -4,
        InvalidAddress = -5,
        AlreadyAvailable = -6,
        AlreadyStarted = -7,
        AlreadyStopped = -8,
        NoShmem = -9,
        InvalidState = -10,
        BadRange = -11,
        Timeout = -12,
        Io = -13,
    }
}

primitive_enum! {
    pub enum HartState: u64 {
        Started = 0,
        Stopped = 1,
        StartPending = 2,
        StopPending = 3,
        Suspended = 4,
        SuspendPending = 5,
        ResumePending = 6,
    }
}

pub enum SbiError {
    Status(Status),
    Other(i64),
}

impl From<i64> for SbiError {
    #[inline]
    fn from(value: i64) -> Self {
        match Status::try_from(value) {
            Ok(value) => Self::Status(value),
            Err(_) => Self::Other(value),
        }
    }
}

#[allow(clippy::too_many_arguments)]
#[inline(always)]
unsafe fn sbi_do_call(
    extension: u64,
    function: u64,
    mut a0: u64,
    mut a1: u64,
    a2: u64,
    a3: u64,
    a4: u64,
    a5: u64,
) -> Result<u64, SbiError> {
    unsafe {
        core::arch::asm!(
            "ecall",
            inlateout("a0") a0,
            inlateout("a1") a1,
            in("a2") a2,
            in("a3") a3,
            in("a4") a4,
            in("a5") a5,
            in("a6") function,
            in("a7") extension,
        );
    }
    let a0 = a0 as i64;
    if a0 == 0 {
        Ok(a1)
    } else {
        Err(a0.into())
    }
}

pub fn sbi_hart_start(hart_id: u64, start_addr: u64, opaque: u64) -> Result<(), Error> {
    match unsafe { sbi_do_call(EXT_HSM, 0x00, hart_id, start_addr, opaque, 0, 0, 0) } {
        Ok(_) => Ok(()),
        Err(SbiError::Status(Status::AlreadyAvailable)) => Err(Error::AlreadyExists),
        Err(SbiError::Status(Status::InvalidParam)) => Err(Error::DoesNotExist),
        Err(SbiError::Status(Status::InvalidAddress)) => Err(Error::InvalidArgument),
        Err(_) => Err(Error::InvalidOperation),
    }
}

pub fn sbi_send_ipi(hart_mask: u64, hart_mask_base: u64) -> Result<(), Error> {
    match unsafe { sbi_do_call(EXT_SPI, 0x00, hart_mask, hart_mask_base, 0, 0, 0, 0) } {
        Ok(_) => Ok(()),
        Err(SbiError::Status(Status::InvalidParam)) => Err(Error::DoesNotExist),
        Err(_) => Err(Error::InvalidOperation),
    }
}

pub fn sbi_debug_console_write_byte(byte: u8) {
    unsafe { sbi_do_call(EXT_DBCN, 0x02, byte as u64, 0, 0, 0, 0, 0) }.ok();
}

pub fn sbi_set_timer(next_event: u64) {
    unsafe { sbi_do_call(EXT_TIME, 0x00, next_event, 0, 0, 0, 0, 0) }.ok();
}

pub fn sbi_system_shutdown() -> ! {
    unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
    unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN_LEGACY, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
    unreachable!()
}
