//! Various kernel utility functions

use crate::device::MACHINE_NAME;

/// Extension trait for [Iterator]s of [Result]s
pub trait ResultIterator<T, E> {
    /// Drops entries from the iterator until the first error
    fn collect_error(self) -> Option<E>;
}

/// Extension trait for [Iterator]s with extra functionality
pub trait IteratorExt<T> {
    /// Like find_map(), but with [Result] handling
    fn try_find_map<U, E, F: Fn(T) -> Result<Option<U>, E>>(
        self,
        mapper: F,
    ) -> Result<Option<U>, E>;
}

impl<T, E, I: Iterator<Item = Result<T, E>>> ResultIterator<T, E> for I {
    fn collect_error(self) -> Option<E> {
        for item in self {
            if let Err(e) = item {
                return Some(e);
            }
        }
        None
    }
}

impl<T, I: Iterator<Item = T>> IteratorExt<T> for I {
    fn try_find_map<U, E, F: Fn(T) -> Result<Option<U>, E>>(
        self,
        mapper: F,
    ) -> Result<Option<U>, E> {
        for item in self {
            if let Some(entry) = mapper(item)? {
                return Ok(Some(entry));
            }
        }
        Ok(None)
    }
}

/// Returns the architecture name string
pub const fn arch_str() -> &'static str {
    #[cfg(target_arch = "aarch64")]
    {
        "aarch64"
    }
    #[cfg(target_arch = "x86_64")]
    {
        "x86_64"
    }
    #[cfg(target_arch = "riscv64")]
    {
        "riscv64"
    }
}

/// Returns a generic machine description string
pub fn machine_name() -> &'static str {
    if let Some(machine) = MACHINE_NAME.try_get() {
        machine.as_str()
    } else {
        "unknown"
    }
}

/// Invokes the functions defined in .init_array
///
/// # Safety
///
/// Must only be called once after basic memory management was initialized, but before
/// platform device enumeration.
pub unsafe fn call_init_array() {
    type InitFn = extern "C" fn();

    extern "C" {
        static __init_array_start: u8;
        static __init_array_end: u8;
    }

    // TODO provide some warning if .init_array size is misaligned

    let base = &raw const __init_array_start;
    let end = (&raw const __init_array_end).addr();
    if end <= base.addr() {
        return;
    }
    let size = (end - base.addr()) / size_of::<InitFn>();

    let init_array: &[InitFn] = core::slice::from_raw_parts(base.cast(), size);

    for function in init_array {
        function();
    }
}
