/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Different objects protected by the same lock

use crate::str::{CssString, CssStringWriter};
use crate::stylesheets::Origin;
#[cfg(feature = "gecko")]
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
#[cfg(feature = "servo")]
use parking_lot::RwLock;
use servo_arc::Arc;
use std::cell::UnsafeCell;
use std::fmt;
#[cfg(feature = "servo")]
use std::mem;
#[cfg(feature = "gecko")]
use std::ptr;
use to_shmem::{SharedMemoryBuilder, ToShmem};

/// A shared read/write lock that can protect multiple objects.
///
/// In Gecko builds, we don't need the blocking behavior, just the safety. As
/// such we implement this with an AtomicRefCell instead in Gecko builds,
/// which is ~2x as fast, and panics (rather than deadlocking) when things go
/// wrong (which is much easier to debug on CI).
///
/// Servo needs the blocking behavior for its unsynchronized animation setup,
/// but that may not be web-compatible and may need to be changed (at which
/// point Servo could use AtomicRefCell too).
///
/// Gecko also needs the ability to have "read only" SharedRwLocks, which are
/// used for objects stored in (read only) shared memory. Attempting to acquire
/// write access to objects protected by a read only SharedRwLock will panic.
#[derive(Clone)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct SharedRwLock {
    #[cfg(feature = "servo")]
    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
    arc: Arc<RwLock<()>>,

    #[cfg(feature = "gecko")]
    cell: Option<Arc<AtomicRefCell<SomethingZeroSizedButTyped>>>,
}

#[cfg(feature = "gecko")]
struct SomethingZeroSizedButTyped;

impl fmt::Debug for SharedRwLock {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("SharedRwLock")
    }
}

impl SharedRwLock {
    /// Create a new shared lock (servo).
    #[cfg(feature = "servo")]
    pub fn new() -> Self {
        SharedRwLock {
            arc: Arc::new(RwLock::new(())),
        }
    }

    /// Create a new shared lock (gecko).
    #[cfg(feature = "gecko")]
    pub fn new() -> Self {
        SharedRwLock {
            cell: Some(Arc::new(AtomicRefCell::new(SomethingZeroSizedButTyped))),
        }
    }

    /// Create a new global shared lock (servo).
    #[cfg(feature = "servo")]
    pub fn new_leaked() -> Self {
        SharedRwLock {
            arc: Arc::new_leaked(RwLock::new(())),
        }
    }

    /// Create a new global shared lock (gecko).
    #[cfg(feature = "gecko")]
    pub fn new_leaked() -> Self {
        SharedRwLock {
            cell: Some(Arc::new_leaked(AtomicRefCell::new(
                SomethingZeroSizedButTyped,
            ))),
        }
    }

    /// Create a new read-only shared lock (gecko).
    #[cfg(feature = "gecko")]
    pub fn read_only() -> Self {
        SharedRwLock { cell: None }
    }

    #[cfg(feature = "gecko")]
    #[inline]
    fn ptr(&self) -> *const SomethingZeroSizedButTyped {
        self.cell
            .as_ref()
            .map(|cell| cell.as_ptr() as *const _)
            .unwrap_or(ptr::null())
    }

    /// Wrap the given data to make its access protected by this lock.
    pub fn wrap<T>(&self, data: T) -> Locked<T> {
        Locked {
            shared_lock: self.clone(),
            data: UnsafeCell::new(data),
        }
    }

    /// Obtain the lock for reading (servo).
    #[cfg(feature = "servo")]
    pub fn read(&self) -> SharedRwLockReadGuard {
        mem::forget(self.arc.read());
        SharedRwLockReadGuard(self)
    }

    /// Obtain the lock for reading (gecko).
    #[cfg(feature = "gecko")]
    pub fn read(&self) -> SharedRwLockReadGuard {
        SharedRwLockReadGuard(self.cell.as_ref().map(|cell| cell.borrow()))
    }

    /// Obtain the lock for writing (servo).
    #[cfg(feature = "servo")]
    pub fn write(&self) -> SharedRwLockWriteGuard {
        mem::forget(self.arc.write());
        SharedRwLockWriteGuard(self)
    }

    /// Obtain the lock for writing (gecko).
    #[cfg(feature = "gecko")]
    pub fn write(&self) -> SharedRwLockWriteGuard {
        SharedRwLockWriteGuard(self.cell.as_ref().unwrap().borrow_mut())
    }
}

/// Proof that a shared lock was obtained for reading (servo).
#[cfg(feature = "servo")]
pub struct SharedRwLockReadGuard<'a>(&'a SharedRwLock);
/// Proof that a shared lock was obtained for reading (gecko).
#[cfg(feature = "gecko")]
pub struct SharedRwLockReadGuard<'a>(Option<AtomicRef<'a, SomethingZeroSizedButTyped>>);
#[cfg(feature = "servo")]
impl<'a> Drop for SharedRwLockReadGuard<'a> {
    fn drop(&mut self) {
        // Unsafe: self.lock is private to this module, only ever set after `read()`,
        // and never copied or cloned (see `compile_time_assert` below).
        unsafe { self.0.arc.force_unlock_read() }
    }
}

impl<'a> SharedRwLockReadGuard<'a> {
    #[inline]
    #[cfg(feature = "gecko")]
    fn ptr(&self) -> *const SomethingZeroSizedButTyped {
        self.0
            .as_ref()
            .map(|r| &**r as *const _)
            .unwrap_or(ptr::null())
    }
}

/// Proof that a shared lock was obtained for writing (servo).
#[cfg(feature = "servo")]
pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock);
/// Proof that a shared lock was obtained for writing (gecko).
#[cfg(feature = "gecko")]
pub struct SharedRwLockWriteGuard<'a>(AtomicRefMut<'a, SomethingZeroSizedButTyped>);
#[cfg(feature = "servo")]
impl<'a> Drop for SharedRwLockWriteGuard<'a> {
    fn drop(&mut self) {
        // Unsafe: self.lock is private to this module, only ever set after `write()`,
        // and never copied or cloned (see `compile_time_assert` below).
        unsafe { self.0.arc.force_unlock_write() }
    }
}

/// Data protect by a shared lock.
pub struct Locked<T> {
    shared_lock: SharedRwLock,
    data: UnsafeCell<T>,
}

// Unsafe: the data inside `UnsafeCell` is only accessed in `read_with` and `write_with`,
// where guards ensure synchronization.
unsafe impl<T: Send> Send for Locked<T> {}
unsafe impl<T: Send + Sync> Sync for Locked<T> {}

impl<T: fmt::Debug> fmt::Debug for Locked<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let guard = self.shared_lock.read();
        self.read_with(&guard).fmt(f)
    }
}

impl<T> Locked<T> {
    #[cfg(feature = "gecko")]
    #[inline]
    fn is_read_only_lock(&self) -> bool {
        self.shared_lock.cell.is_none()
    }

    #[cfg(feature = "servo")]
    fn same_lock_as(&self, lock: &SharedRwLock) -> bool {
        Arc::ptr_eq(&self.shared_lock.arc, &lock.arc)
    }

    #[cfg(feature = "gecko")]
    fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool {
        ptr::eq(self.shared_lock.ptr(), ptr)
    }

    /// Access the data for reading.
    pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T {
        #[cfg(feature = "gecko")]
        assert!(
            self.is_read_only_lock() || self.same_lock_as(guard.ptr()),
            "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}",
            self.shared_lock.ptr(),
            guard.ptr(),
        );
        #[cfg(not(feature = "gecko"))]
        assert!(self.same_lock_as(&guard.0));

        let ptr = self.data.get();

        // Unsafe:
        //
        // * The guard guarantees that the lock is taken for reading,
        //   and we’ve checked that it’s the correct lock.
        // * The returned reference borrows *both* the data and the guard,
        //   so that it can outlive neither.
        unsafe { &*ptr }
    }

    /// Access the data for reading without verifying the lock. Use with caution.
    #[cfg(feature = "gecko")]
    pub unsafe fn read_unchecked<'a>(&'a self) -> &'a T {
        let ptr = self.data.get();
        &*ptr
    }

    /// Access the data for writing.
    pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T {
        #[cfg(feature = "gecko")]
        assert!(
            !self.is_read_only_lock() && self.same_lock_as(&*guard.0),
            "Locked::write_with called with a guard from a read only or unrelated SharedRwLock"
        );
        #[cfg(not(feature = "gecko"))]
        assert!(self.same_lock_as(&guard.0));

        let ptr = self.data.get();

        // Unsafe:
        //
        // * The guard guarantees that the lock is taken for writing,
        //   and we’ve checked that it’s the correct lock.
        // * The returned reference borrows *both* the data and the guard,
        //   so that it can outlive neither.
        // * We require a mutable borrow of the guard,
        //   so that one write guard can only be used once at a time.
        unsafe { &mut *ptr }
    }
}

#[cfg(feature = "gecko")]
impl<T: ToShmem> ToShmem for Locked<T> {
    fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
        use std::mem::ManuallyDrop;

        let guard = self.shared_lock.read();
        Ok(ManuallyDrop::new(Locked {
            shared_lock: SharedRwLock::read_only(),
            data: UnsafeCell::new(ManuallyDrop::into_inner(
                self.read_with(&guard).to_shmem(builder)?,
            )),
        }))
    }
}

#[cfg(feature = "servo")]
impl<T: ToShmem> ToShmem for Locked<T> {
    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
        panic!("ToShmem not supported in Servo currently")
    }
}

#[allow(dead_code)]
mod compile_time_assert {
    use super::{SharedRwLockReadGuard, SharedRwLockWriteGuard};

    trait Marker1 {}
    impl<T: Clone> Marker1 for T {}
    impl<'a> Marker1 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Clone
    impl<'a> Marker1 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Clone

    trait Marker2 {}
    impl<T: Copy> Marker2 for T {}
    impl<'a> Marker2 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Copy
    impl<'a> Marker2 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Copy
}

/// Like ToCss, but with a lock guard given by the caller, and with the writer specified
/// concretely rather than with a parameter.
pub trait ToCssWithGuard {
    /// Serialize `self` in CSS syntax, writing to `dest`, using the given lock guard.
    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result;

    /// Serialize `self` in CSS syntax using the given lock guard and return a string.
    ///
    /// (This is a convenience wrapper for `to_css` and probably should not be overridden.)
    #[inline]
    fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> CssString {
        let mut s = CssString::new();
        self.to_css(guard, &mut s).unwrap();
        s
    }
}

/// Parameters needed for deep clones.
#[cfg(feature = "gecko")]
pub struct DeepCloneParams {
    /// The new sheet we're cloning rules into.
    pub reference_sheet: *const crate::gecko_bindings::structs::StyleSheet,
}

/// Parameters needed for deep clones.
#[cfg(feature = "servo")]
pub struct DeepCloneParams;

/// A trait to do a deep clone of a given CSS type. Gets a lock and a read
/// guard, in order to be able to read and clone nested structures.
pub trait DeepCloneWithLock: Sized {
    /// Deep clones this object.
    fn deep_clone_with_lock(
        &self,
        lock: &SharedRwLock,
        guard: &SharedRwLockReadGuard,
        params: &DeepCloneParams,
    ) -> Self;
}

/// Guards for a document
#[derive(Clone)]
pub struct StylesheetGuards<'a> {
    /// For author-origin stylesheets.
    pub author: &'a SharedRwLockReadGuard<'a>,

    /// For user-agent-origin and user-origin stylesheets
    pub ua_or_user: &'a SharedRwLockReadGuard<'a>,
}

impl<'a> StylesheetGuards<'a> {
    /// Get the guard for a given stylesheet origin.
    pub fn for_origin(&self, origin: Origin) -> &SharedRwLockReadGuard<'a> {
        match origin {
            Origin::Author => &self.author,
            _ => &self.ua_or_user,
        }
    }

    /// Same guard for all origins
    pub fn same(guard: &'a SharedRwLockReadGuard<'a>) -> Self {
        StylesheetGuards {
            author: guard,
            ua_or_user: guard,
        }
    }
}
