/* 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/. */

//! A thin atomically-reference-counted slice.

use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use servo_arc::ThinArc;
use std::ops::Deref;
use std::ptr::NonNull;
use std::sync::LazyLock;
use std::{
    hash::{Hash, Hasher},
    iter, mem,
};

use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf};

/// A canary that we stash in ArcSlices.
///
/// Given we cannot use a zero-sized-type for the header, since well, C++
/// doesn't have zsts, and we want to use cbindgen for this type, we may as well
/// assert some sanity at runtime.
///
/// We use an u64, to guarantee that we can use a single singleton for every
/// empty slice, even if the types they hold are aligned differently.
const ARC_SLICE_CANARY: u64 = 0xf3f3f3f3f3f3f3f3;

/// A wrapper type for a refcounted slice using ThinArc.
#[repr(C)]
#[derive(Debug, Eq, PartialEq, ToShmem)]
pub struct ArcSlice<T>(#[shmem(field_bound)] ThinArc<u64, T>);

impl<T> Deref for ArcSlice<T> {
    type Target = [T];

    #[inline]
    fn deref(&self) -> &Self::Target {
        debug_assert_eq!(self.0.header, ARC_SLICE_CANARY);
        self.0.slice()
    }
}

impl<T> Clone for ArcSlice<T> {
    fn clone(&self) -> Self {
        ArcSlice(self.0.clone())
    }
}

// ThinArc doesn't support alignments greater than align_of::<u64>.
static EMPTY_ARC_SLICE: LazyLock<ArcSlice<u64>> =
    LazyLock::new(|| ArcSlice::from_iter_leaked(iter::empty()));

impl<T> Default for ArcSlice<T> {
    #[allow(unsafe_code)]
    fn default() -> Self {
        debug_assert!(
            mem::align_of::<T>() <= mem::align_of::<u64>(),
            "Need to increase the alignment of EMPTY_ARC_SLICE"
        );
        unsafe {
            let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone();
            let empty: Self = mem::transmute(empty);
            debug_assert_eq!(empty.len(), 0);
            empty
        }
    }
}

impl<T: Serialize> Serialize for ArcSlice<T> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.deref().serialize(serializer)
    }
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcSlice<T> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let r = Vec::deserialize(deserializer)?;
        Ok(ArcSlice::from_iter(r.into_iter()))
    }
}

impl<T> ArcSlice<T> {
    /// Creates an Arc for a slice using the given iterator to generate the
    /// slice.
    #[inline]
    pub fn from_iter<I>(items: I) -> Self
    where
        I: Iterator<Item = T> + ExactSizeIterator,
    {
        if items.len() == 0 {
            return Self::default();
        }
        ArcSlice(ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items))
    }

    /// Creates an Arc for a slice using the given iterator to generate the
    /// slice, and marks the arc as intentionally leaked from the refcount
    /// logging point of view.
    #[inline]
    pub fn from_iter_leaked<I>(items: I) -> Self
    where
        I: Iterator<Item = T> + ExactSizeIterator,
    {
        let arc = ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items);
        arc.mark_as_intentionally_leaked();
        ArcSlice(arc)
    }

    /// Creates a value that can be passed via FFI, and forgets this value
    /// altogether.
    #[inline]
    pub fn forget(self) -> ForgottenArcSlicePtr<T> {
        let ret = ForgottenArcSlicePtr(self.0.raw_ptr().cast());
        mem::forget(self);
        ret
    }

    /// Leaks an empty arc slice pointer, and returns it. Only to be used to
    /// construct ArcSlices from FFI.
    #[inline]
    pub fn leaked_empty_ptr() -> *mut std::os::raw::c_void {
        let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone();
        let ptr = empty.0.raw_ptr();
        std::mem::forget(empty);
        ptr.cast().as_ptr()
    }

    /// Returns whether there's only one reference to this ArcSlice.
    pub fn is_unique(&self) -> bool {
        self.0.is_unique()
    }
}

impl<T: MallocSizeOf> MallocUnconditionalSizeOf for ArcSlice<T> {
    #[allow(unsafe_code)]
    fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        let mut size = unsafe { ops.malloc_size_of(self.0.heap_ptr()) };
        for el in self.iter() {
            size += el.size_of(ops);
        }
        size
    }
}

impl<T: Hash> Hash for ArcSlice<T> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        T::hash_slice(&**self, state)
    }
}

/// The inner pointer of an ArcSlice<T>, to be sent via FFI.
/// The type of the pointer is a bit of a lie, we just want to preserve the type
/// but these pointers cannot be constructed outside of this crate, so we're
/// good.
#[repr(C)]
pub struct ForgottenArcSlicePtr<T>(NonNull<T>);
