
use core::cell::UnsafeCell;
use core::hint::spin_loop;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, Ordering};

use crate::errors::KernelError;


pub struct SpinlockGuard<'a, T: ?Sized + 'a> {
    spinlock: &'a Spinlock<T>,
}

pub struct Spinlock<T: ?Sized> {
    lock: AtomicBool,
    data: UnsafeCell<T>
}

unsafe impl<T: ?Sized + Send> Send for Spinlock<T> {}
unsafe impl<T: ?Sized + Sync> Sync for Spinlock<T> {}

impl<T> Spinlock<T> {
    pub const fn new(t: T) -> Spinlock<T> {
        Spinlock {
            lock: AtomicBool::new(false),
            data: UnsafeCell::new(t),
        }
    }
}

impl<T: ?Sized> Spinlock<T> {
    pub fn lock(&self) -> SpinlockGuard<'_, T> {
        let mut count = 0;
        while self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
            // TODO delay
            spin_loop();
            count += 1;
            if count == 1_000_000_000 {
                panic!("Spinlock timed out");
            }
        }
        SpinlockGuard { spinlock: self }
    }

    pub fn try_lock(&self) -> Result<SpinlockGuard<'_, T>, KernelError> {
        let mut count = 0;
        while self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {
            // TODO delay
            spin_loop();
            count += 1;
            if count == 1_000_000_000 {
                return Err(KernelError::LockTimeout);
            }
        }
        Ok(SpinlockGuard { spinlock: self })
    }
}

impl<T: ?Sized> Deref for SpinlockGuard<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        unsafe {
            &*self.spinlock.data.get()
        }
    }
}

impl<T: ?Sized> DerefMut for SpinlockGuard<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe {
            &mut *self.spinlock.data.get()
        }
    }
}

impl<T: ?Sized> Drop for SpinlockGuard<'_, T> {
    #[inline]
    fn drop(&mut self) {
        self.spinlock.lock.store(false, Ordering::Release);
    }
}

