use core::ops::{Deref, DerefMut};

use device_api::interrupt::IpiMessage;
use kernel_arch::{Architecture, ArchitectureImpl, CpuImpl, LocalCpuImpl};
use libk_util::sync::IrqGuard;

use crate::task::sched::CpuQueue;

/// Kernel wrapper for local CPU info structure. See [kernel_arch::LocalCpuImpl].
#[repr(transparent)]
pub struct LocalCpu<'a>(LocalCpuImpl<'a, CpuQueue>);
/// Kernel wrapper for per-CPU info structure. See [kernel_arch::LocalCpuImpl].
#[repr(transparent)]
pub struct Cpu(CpuImpl<CpuQueue>);

impl Cpu {
    /// # Safety
    ///
    /// Precondition: this function has not yet been called on the local CPU.
    pub unsafe fn init_local(
        id: Option<u32>,
        data: <ArchitectureImpl as Architecture>::PerCpuData,
    ) {
        ArchitectureImpl::init_local_cpu::<CpuQueue>(id, data)
    }

    /// Returns local CPU reference
    #[inline]
    pub fn local<'a>() -> LocalCpu<'a> {
        LocalCpu(CpuImpl::local())
    }

    /// Returns local CPU reference or None if it hasn't yet been initialized
    #[inline]
    pub fn try_local<'a>() -> Option<LocalCpu<'a>> {
        CpuImpl::try_local().map(LocalCpu)
    }

    /// Pushes a message to the IPI queue
    #[inline]
    pub fn push_ipi_queue(cpu_id: u32, msg: IpiMessage) {
        CpuImpl::<CpuQueue>::push_ipi_queue(cpu_id, msg)
    }

    /// Initialize the IPI queues for all present CPUs
    #[inline]
    pub fn init_ipi_queues(cpu_count: usize) {
        CpuImpl::<CpuQueue>::init_ipi_queues(cpu_count)
    }
}

impl Deref for Cpu {
    type Target = CpuImpl<CpuQueue>;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for Cpu {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl LocalCpu<'_> {
    /// Converts the local CPU handle into its IRQ guard
    pub fn into_guard(self) -> IrqGuard {
        self.0.into_guard()
    }
}

impl Deref for LocalCpu<'_> {
    type Target = CpuImpl<CpuQueue>;

    #[inline]
    fn deref(&self) -> &Self::Target {
        self.0.deref()
    }
}

impl DerefMut for LocalCpu<'_> {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.0.deref_mut()
    }
}
