use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};

use super::raw_sys;
use super::sync::{futex_wait_for_value, futex_wake_all};

const INSTALL_UNINITIALIZED: u32 = 0;
const INSTALL_READY: u32 = 1;
const INSTALL_FAILED: u32 = 2;

static INSTALL_STATE: AtomicU32 = AtomicU32::new(INSTALL_UNINITIALIZED);
static ACTIVE_MESH_SEQ: AtomicU32 = AtomicU32::new(0);
static ACTIVE_MESH_START: AtomicUsize = AtomicUsize::new(0);
static ACTIVE_MESH_LEN: AtomicUsize = AtomicUsize::new(0);

#[derive(Debug)]
pub struct ActiveMeshGuard {
    active: bool,
}

impl ActiveMeshGuard {
    pub fn begin(start: *mut u8, len: usize) -> raw_sys::Result<Self> {
        ensure_fault_mediation_installed()?;
        loop {
            let seq = ACTIVE_MESH_SEQ.load(Ordering::Acquire);
            if seq & 1 == 0 {
                break;
            }
            futex_wait_for_value(&ACTIVE_MESH_SEQ, seq);
        }
        ACTIVE_MESH_START.store(start as usize, Ordering::Release);
        ACTIVE_MESH_LEN.store(len, Ordering::Release);
        let previous = ACTIVE_MESH_SEQ.fetch_add(1, Ordering::AcqRel);
        debug_assert_eq!(previous & 1, 0);
        Ok(Self { active: true })
    }

    pub fn finish(mut self) {
        self.release();
    }

    fn release(&mut self) {
        if !self.active {
            return;
        }

        ACTIVE_MESH_START.store(0, Ordering::Release);
        ACTIVE_MESH_LEN.store(0, Ordering::Release);
        let previous = ACTIVE_MESH_SEQ.fetch_add(1, Ordering::AcqRel);
        debug_assert_eq!(previous & 1, 1);
        futex_wake_all(&ACTIVE_MESH_SEQ);
        self.active = false;
    }
}

impl Drop for ActiveMeshGuard {
    fn drop(&mut self) {
        self.release();
    }
}

pub fn ensure_fault_mediation_installed() -> raw_sys::Result<()> {
    match INSTALL_STATE.compare_exchange(
        INSTALL_UNINITIALIZED,
        INSTALL_READY,
        Ordering::AcqRel,
        Ordering::Acquire,
    ) {
        Ok(_) | Err(INSTALL_READY) => Ok(()),
        Err(_) => {
            INSTALL_STATE.store(INSTALL_FAILED, Ordering::Release);
            Err(raw_sys::Error(raw_sys::EAGAIN))
        }
    }
}

pub fn ok_to_proceed(ptr: *const u8) -> bool {
    let address = ptr as usize;
    let mut waited = false;
    loop {
        let seq = ACTIVE_MESH_SEQ.load(Ordering::Acquire);
        if seq & 1 == 0 {
            return waited;
        }

        let start = ACTIVE_MESH_START.load(Ordering::Acquire);
        let len = ACTIVE_MESH_LEN.load(Ordering::Acquire);
        let end = start.saturating_add(len);
        if address < start || address >= end {
            return waited;
        }

        waited = true;
        futex_wait_for_value(&ACTIVE_MESH_SEQ, seq);
    }
}

pub fn retry_on_efault<T, F>(ptr: *const u8, mut op: F) -> raw_sys::Result<T>
where
    F: FnMut() -> raw_sys::Result<T>,
{
    loop {
        match op() {
            Ok(value) => return Ok(value),
            Err(error) if error.errno() == raw_sys::EFAULT && ok_to_proceed(ptr) => continue,
            Err(error) => return Err(error),
        }
    }
}

pub fn retry_on_efault_ptrs<T, F>(ptrs: &[*const u8], mut op: F) -> raw_sys::Result<T>
where
    F: FnMut() -> raw_sys::Result<T>,
{
    loop {
        match op() {
            Ok(value) => return Ok(value),
            Err(error) if error.errno() == raw_sys::EFAULT => {
                let mut waited = false;
                for &ptr in ptrs {
                    if !ptr.is_null() {
                        waited |= ok_to_proceed(ptr);
                    }
                }
                if waited {
                    continue;
                }
                return Err(error);
            }
            Err(error) => return Err(error),
        }
    }
}
