use crate::os::xous::ffi::{blocking_scalar, scalar};
use crate::os::xous::services::{TicktimerScalar, ticktimer_server};
use crate::pin::Pin;
use crate::ptr;
use crate::sync::atomic::Ordering::{Acquire, Release};
use crate::sync::atomic::{Atomic, AtomicI8};
use crate::time::Duration;

const NOTIFIED: i8 = 1;
const EMPTY: i8 = 0;
const PARKED: i8 = -1;

pub struct Parker {
    state: Atomic<i8>,
}

impl Parker {
    pub unsafe fn new_in_place(parker: *mut Parker) {
        unsafe { parker.write(Parker { state: AtomicI8::new(EMPTY) }) }
    }

    fn index(&self) -> usize {
        ptr::from_ref(self).addr()
    }

    pub unsafe fn park(self: Pin<&Self>) {
        // Change NOTIFIED to EMPTY and EMPTY to PARKED.
        let state = self.state.fetch_sub(1, Acquire);
        if state == NOTIFIED {
            // The state has gone from NOTIFIED (1) to EMPTY (0)
            return;
        }
        // The state has gone from EMPTY (0) to PARKED (-1)
        assert!(state == EMPTY);

        // The state is now PARKED (-1). Wait until the `unpark` wakes us up.
        blocking_scalar(
            ticktimer_server(),
            TicktimerScalar::WaitForCondition(self.index(), 0).into(),
        )
        .expect("failed to send WaitForCondition command");

        let state = self.state.swap(EMPTY, Acquire);
        assert!(state == NOTIFIED || state == PARKED);
    }

    pub unsafe fn park_timeout(self: Pin<&Self>, timeout: Duration) {
        // Change NOTIFIED to EMPTY and EMPTY to PARKED.
        let state = self.state.fetch_sub(1, Acquire);
        if state == NOTIFIED {
            // The state has gone from NOTIFIED (1) to EMPTY (0)
            return;
        }
        // The state has gone from EMPTY (0) to PARKED (-1)
        assert!(state == EMPTY);

        // A value of zero indicates an indefinite wait. Clamp the number of
        // milliseconds to the allowed range.
        let millis = usize::max(timeout.as_millis().try_into().unwrap_or(usize::MAX), 1);

        // The state is now PARKED (-1). Wait until the `unpark` wakes us up,
        // or things time out.
        let _was_timeout = blocking_scalar(
            ticktimer_server(),
            TicktimerScalar::WaitForCondition(self.index(), millis).into(),
        )
        .expect("failed to send WaitForCondition command")[0]
            != 0;

        let state = self.state.swap(EMPTY, Acquire);
        assert!(state == PARKED || state == NOTIFIED);
    }

    pub fn unpark(self: Pin<&Self>) {
        // If the state is already `NOTIFIED`, then another thread has
        // indicated it wants to wake up the target thread.
        //
        // If the state is `EMPTY` then there is nothing to wake up, and
        // the target thread will immediately exit from `park()` the
        // next time that function is called.
        if self.state.swap(NOTIFIED, Release) != PARKED {
            return;
        }

        // The thread is parked, wake it up. Keep trying until we wake something up.
        // This will happen when the `NotifyCondition` call returns the fact that
        // 1 condition was notified.
        // Alternately, keep going until the state is seen as `EMPTY`, indicating
        // the thread woke up and kept going. This can happen when the Park
        // times out before we can send the NotifyCondition message.
        while blocking_scalar(
            ticktimer_server(),
            TicktimerScalar::NotifyCondition(self.index(), 1).into(),
        )
        .expect("failed to send NotifyCondition command")[0]
            == 0
            && self.state.load(Acquire) != EMPTY
        {
            // The target thread hasn't yet hit the `WaitForCondition` call.
            // Yield to let the target thread run some more.
            crate::thread::yield_now();
        }
    }
}

impl Drop for Parker {
    fn drop(&mut self) {
        scalar(ticktimer_server(), TicktimerScalar::FreeCondition(self.index()).into()).ok();
    }
}
