//! i386 architecture helper functions

use core::marker::PhantomData;

/// Wrapper struct providing access to an x86 I/O port
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct IoPort<T> {
    address: u16,
    _pd: PhantomData<T>,
}

/// Common interface for reading and writing I/O ports
pub trait IoPortAccess<T> {
    /// Reads a value from the port
    fn read(&self) -> T;
    /// Writes a value to the port
    fn write(&self, value: T);

    fn modify<F: FnOnce(T) -> T>(&self, f: F) {
        self.write(f(self.read()));
    }
}

impl<T> IoPort<T> {
    /// Constructs a new I/O port interface
    pub const fn new(address: u16) -> Self {
        Self {
            address,
            _pd: PhantomData,
        }
    }
}

impl IoPortAccess<u8> for IoPort<u8> {
    fn write(&self, value: u8) {
        unsafe {
            outb(self.address, value);
        }
    }

    fn read(&self) -> u8 {
        unsafe { inb(self.address) }
    }
}

impl IoPortAccess<u16> for IoPort<u16> {
    fn write(&self, value: u16) {
        unsafe {
            outw(self.address, value);
        }
    }

    fn read(&self) -> u16 {
        unsafe { inw(self.address) }
    }
}

impl IoPortAccess<u32> for IoPort<u32> {
    fn write(&self, value: u32) {
        unsafe {
            outl(self.address, value);
        }
    }

    fn read(&self) -> u32 {
        unsafe { inl(self.address) }
    }
}

/// Reads a byte from the I/O port
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn inb(port: u16) -> u8 {
    let value: u8;
    core::arch::asm!("inb %dx, %al", in("dx") port, out("al") value, options(att_syntax));
    value
}

/// Reads a 16-bit value from the I/O port
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn inw(port: u16) -> u16 {
    let value: u16;
    core::arch::asm!("inw %dx, %ax", in("dx") port, out("ax") value, options(att_syntax));
    value
}

/// Reads a 32-bit value from the I/O port
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn inl(port: u16) -> u32 {
    let value: u32;
    core::arch::asm!("inl %dx, %eax", in("dx") port, out("eax") value, options(att_syntax));
    value
}

/// Writes a byte to the I/O port.
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn outb(port: u16, value: u8) {
    core::arch::asm!("outb %al, %dx", in("dx") port, in("al") value, options(att_syntax));
}

/// Writes a 16-bit value to the I/O port.
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn outw(port: u16, value: u16) {
    core::arch::asm!("outw %ax, %dx", in("dx") port, in("ax") value, options(att_syntax));
}

/// Writes a 32-bit value to the I/O port.
///
/// # Safety
///
/// Provides direct access to port I/O, unsafe.
#[inline]
pub unsafe fn outl(port: u16, value: u32) {
    core::arch::asm!("outl %eax, %dx", in("dx") port, in("eax") value, options(att_syntax));
}

#[inline]
pub fn flush_cpu_cache() {
    unsafe {
        core::arch::asm!("wbinvd");
    }
}

#[inline]
pub fn io_wait() {
    WAIT_PORT.write(0);
}

static WAIT_PORT: IoPort<u8> = IoPort::new(0x80);
