use crate::{critical, task};
use core::{
    cell::UnsafeCell,
    ops::{Deref, DerefMut},
    sync::atomic::{compiler_fence, Ordering},
};
use gba::sync::Static;

pub struct BlockingMutex<T> {
    acquired: Static<bool>,
    data: UnsafeCell<T>,
}

impl<T> BlockingMutex<T> {
    pub const fn new(t: T) -> Self {
        Self {
            acquired: Static::new(false),
            data: UnsafeCell::new(t),
        }
    }

    pub fn lock(&self) -> BlockingMutexGuard<'_, T> {
        loop {
            let res = self.try_lock();
            match res {
                Some(lock) => break lock,
                None => task::block_on(task::BlockingOn::Mutex),
            }
        }
    }

    pub fn try_lock(&self) -> Option<BlockingMutexGuard<'_, T>> {
        if self.acquired.replace(true) {
            // already acquired.
            None
        } else {
            compiler_fence(Ordering::Acquire);
            Some(BlockingMutexGuard {
                underlying: self,
                ptr: self.data.get(),
            })
        }
    }

    fn unlock(&self) {
        compiler_fence(Ordering::Release);
        if !self.acquired.replace(false) {
            panic!("Internal error: attempt to unlock a mutex which is not locked.");
        }
        task::unblock_on(task::BlockingOn::Mutex);
    }
}

unsafe impl<T> Send for BlockingMutex<T> {}

unsafe impl<T> Sync for BlockingMutex<T> {}

pub struct BlockingMutexGuard<'a, T> {
    underlying: &'a BlockingMutex<T>,
    ptr: *mut T,
}

impl<'a, T> Drop for BlockingMutexGuard<'a, T> {
    fn drop(&mut self) {
        self.underlying.unlock();
    }
}

impl<'a, T> Deref for BlockingMutexGuard<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.ptr }
    }
}

impl<'a, T> DerefMut for BlockingMutexGuard<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.ptr }
    }
}
