use std::cell::UnsafeCell;
use std::hint;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
use std::sync::atomic::{AtomicBool, Ordering};

use crate::sync::{DynSend, DynSync, ReadGuard, RwLock, WriteGuard};

/// A type which allows mutation using a lock until
/// the value is frozen and can be accessed lock-free.
///
/// Unlike `RwLock`, it can be used to prevent mutation past a point.
#[derive(Default)]
pub struct FreezeLock<T> {
    data: UnsafeCell<T>,
    frozen: AtomicBool,

    /// This lock protects writes to the `data` and `frozen` fields.
    lock: RwLock<()>,
}

unsafe impl<T: DynSync + DynSend> DynSync for FreezeLock<T> {}

impl<T> FreezeLock<T> {
    #[inline]
    pub fn new(value: T) -> Self {
        Self::with(value, false)
    }

    #[inline]
    pub fn frozen(value: T) -> Self {
        Self::with(value, true)
    }

    #[inline]
    pub fn with(value: T, frozen: bool) -> Self {
        Self {
            data: UnsafeCell::new(value),
            frozen: AtomicBool::new(frozen),
            lock: RwLock::new(()),
        }
    }

    /// Clones the inner value along with the frozen state.
    #[inline]
    pub fn clone(&self) -> Self
    where
        T: Clone,
    {
        let lock = self.read();
        Self::with(lock.clone(), self.is_frozen())
    }

    #[inline]
    pub fn is_frozen(&self) -> bool {
        self.frozen.load(Ordering::Acquire)
    }

    /// Get the inner value if frozen.
    #[inline]
    pub fn get(&self) -> Option<&T> {
        if self.frozen.load(Ordering::Acquire) {
            // SAFETY: This is frozen so the data cannot be modified.
            unsafe { Some(&*self.data.get()) }
        } else {
            hint::cold_path();
            None
        }
    }

    #[inline]
    pub fn read(&self) -> FreezeReadGuard<'_, T> {
        FreezeReadGuard {
            _lock_guard: if self.frozen.load(Ordering::Acquire) {
                None
            } else {
                Some(self.lock.read())
            },
            data: unsafe { NonNull::new_unchecked(self.data.get()) },
        }
    }

    #[inline]
    pub fn borrow(&self) -> FreezeReadGuard<'_, T> {
        self.read()
    }

    #[inline]
    #[track_caller]
    pub fn write(&self) -> FreezeWriteGuard<'_, T> {
        self.try_write().expect("data should not be frozen if we're still attempting to mutate it")
    }

    #[inline]
    pub fn try_write(&self) -> Option<FreezeWriteGuard<'_, T>> {
        let _lock_guard = self.lock.write();
        // Use relaxed ordering since we're in the write lock.
        if self.frozen.load(Ordering::Relaxed) {
            None
        } else {
            Some(FreezeWriteGuard {
                _lock_guard,
                data: unsafe { NonNull::new_unchecked(self.data.get()) },
                frozen: &self.frozen,
                marker: PhantomData,
            })
        }
    }

    #[inline]
    pub fn freeze(&self) -> &T {
        if !self.frozen.load(Ordering::Acquire) {
            // Get the lock to ensure no concurrent writes and that we release the latest write.
            let _lock = self.lock.write();
            self.frozen.store(true, Ordering::Release);
        }

        // SAFETY: This is frozen so the data cannot be modified and shared access is sound.
        unsafe { &*self.data.get() }
    }
}

/// A guard holding shared access to a `FreezeLock` which is in a locked state or frozen.
#[must_use = "if unused the FreezeLock may immediately unlock"]
pub struct FreezeReadGuard<'a, T: ?Sized> {
    _lock_guard: Option<ReadGuard<'a, ()>>,
    data: NonNull<T>,
}

impl<'a, T: ?Sized + 'a> Deref for FreezeReadGuard<'a, T> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &T {
        // SAFETY: If the lock is not frozen, `_lock_guard` holds the lock to the `UnsafeCell` so
        // this has shared access until the `FreezeReadGuard` is dropped. If the lock is frozen,
        // the data cannot be modified and shared access is sound.
        unsafe { &*self.data.as_ptr() }
    }
}

impl<'a, T: ?Sized> FreezeReadGuard<'a, T> {
    #[inline]
    pub fn map<U: ?Sized>(this: Self, f: impl FnOnce(&T) -> &U) -> FreezeReadGuard<'a, U> {
        FreezeReadGuard { data: NonNull::from(f(&*this)), _lock_guard: this._lock_guard }
    }
}

/// A guard holding mutable access to a `FreezeLock` which is in a locked state or frozen.
#[must_use = "if unused the FreezeLock may immediately unlock"]
pub struct FreezeWriteGuard<'a, T: ?Sized> {
    _lock_guard: WriteGuard<'a, ()>,
    frozen: &'a AtomicBool,
    data: NonNull<T>,
    marker: PhantomData<&'a mut T>,
}

impl<'a, T> FreezeWriteGuard<'a, T> {
    pub fn freeze(self) -> &'a T {
        self.frozen.store(true, Ordering::Release);

        // SAFETY: This is frozen so the data cannot be modified and shared access is sound.
        unsafe { &*self.data.as_ptr() }
    }
}

impl<'a, T: ?Sized> FreezeWriteGuard<'a, T> {
    #[inline]
    pub fn map<U: ?Sized>(
        mut this: Self,
        f: impl FnOnce(&mut T) -> &mut U,
    ) -> FreezeWriteGuard<'a, U> {
        FreezeWriteGuard {
            data: NonNull::from(f(&mut *this)),
            _lock_guard: this._lock_guard,
            frozen: this.frozen,
            marker: PhantomData,
        }
    }
}

impl<'a, T: ?Sized + 'a> Deref for FreezeWriteGuard<'a, T> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &T {
        // SAFETY: `self._lock_guard` holds the lock to the `UnsafeCell` so this has shared access.
        unsafe { &*self.data.as_ptr() }
    }
}

impl<'a, T: ?Sized + 'a> DerefMut for FreezeWriteGuard<'a, T> {
    #[inline]
    fn deref_mut(&mut self) -> &mut T {
        // SAFETY: `self._lock_guard` holds the lock to the `UnsafeCell` so this has mutable access.
        unsafe { &mut *self.data.as_ptr() }
    }
}
