// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use super::{BufferAccess, BufferAccessObject, BufferContents, BufferInner, TypedBufferAccess};
use crate::{
    device::{Device, DeviceOwned},
    DeviceSize,
};
use std::{
    hash::{Hash, Hasher},
    marker::PhantomData,
    mem::{size_of, size_of_val, MaybeUninit},
    ops::Range,
    sync::Arc,
};

/// A subpart of a buffer.
///
/// This object doesn't correspond to any Vulkan object. It exists for API convenience.
///
/// # Examples
///
/// Creating a slice:
///
/// ```ignore       // FIXME: unignore
/// use vulkano::buffer::BufferSlice;
/// # let buffer: std::sync::Arc<vulkano::buffer::DeviceLocalBuffer<[u8]>> = return;
/// let _slice = BufferSlice::from(&buffer);
/// ```
///
/// Selecting a slice of a buffer that contains `[T]`:
///
/// ```ignore       // FIXME: unignore
/// use vulkano::buffer::BufferSlice;
/// # let buffer: std::sync::Arc<vulkano::buffer::DeviceLocalBuffer<[u8]>> = return;
/// let _slice = BufferSlice::from(&buffer).slice(12 .. 14).unwrap();
/// ```
#[derive(Debug)]
pub struct BufferSlice<T: ?Sized, B> {
    marker: PhantomData<T>,
    resource: Arc<B>,
    offset: DeviceSize,
    size: DeviceSize,
}

// We need to implement `Clone` manually, otherwise the derive adds a `T: Clone` requirement.
impl<T: ?Sized, B> Clone for BufferSlice<T, B> {
    fn clone(&self) -> Self {
        BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset,
            size: self.size,
        }
    }
}

impl<T: ?Sized, B> BufferSlice<T, B> {
    pub fn from_typed_buffer_access(r: Arc<B>) -> Arc<BufferSlice<T, B>>
    where
        B: TypedBufferAccess<Content = T>,
    {
        let size = r.size();

        Arc::new(BufferSlice {
            marker: PhantomData,
            resource: r,
            offset: 0,
            size,
        })
    }

    /// Returns the buffer that this slice belongs to.
    pub fn buffer(&self) -> &Arc<B> {
        &self.resource
    }

    /// Returns the offset of that slice within the buffer.
    pub fn offset(&self) -> DeviceSize {
        self.offset
    }

    /// Returns the size of that slice in bytes.
    pub fn size(&self) -> DeviceSize {
        self.size
    }

    /// Builds a slice that contains an element from inside the buffer.
    ///
    /// This method builds an object that represents a slice of the buffer. No actual operation
    /// is performed.
    ///
    /// # Examples
    ///
    /// TODO
    ///
    /// # Safety
    ///
    /// The object whose reference is passed to the closure is uninitialized. Therefore you
    /// **must not** access the content of the object.
    ///
    /// You **must** return a reference to an element from the parameter. The closure **must not**
    /// panic.
    pub unsafe fn slice_custom<F, R: ?Sized>(&self, f: F) -> Arc<BufferSlice<R, B>>
    where
        F: for<'r> FnOnce(&'r T) -> &'r R, // TODO: bounds on R
    {
        let data: MaybeUninit<&T> = MaybeUninit::zeroed();
        let result = f(data.assume_init());
        let size = size_of_val(result) as DeviceSize;
        let result = result as *const R as *const () as DeviceSize;

        assert!(result <= self.size());
        assert!(result + size <= self.size());

        Arc::new(BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset + result,
            size,
        })
    }

    /// Changes the `T` generic parameter of the `BufferSlice` to the desired type. This can be
    /// useful when you have a buffer with various types of data and want to create a typed slice
    /// of a region that contains a single type of data.
    ///
    /// # Examples
    ///
    /// ```
    /// # use std::sync::Arc;
    /// # use vulkano::buffer::BufferSlice;
    /// # use vulkano::buffer::DeviceLocalBuffer;
    /// # struct VertexImpl;
    /// let blob_slice: Arc<BufferSlice<[u8], Arc<DeviceLocalBuffer<[u8]>>>> = return;
    /// let vertex_slice: Arc<BufferSlice<[VertexImpl], Arc<DeviceLocalBuffer<[u8]>>>> = unsafe {
    ///     blob_slice.reinterpret::<[VertexImpl]>()
    /// };
    /// ```
    ///
    /// # Safety
    ///
    /// Correct `offset` and `size` must be ensured before using this `BufferSlice` on the device.
    /// See `BufferSlice::slice` for adjusting these properties.
    pub unsafe fn reinterpret<R: ?Sized>(&self) -> Arc<BufferSlice<R, B>> {
        Arc::new(BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset,
            size: self.size,
        })
    }
}

impl<T, B> BufferSlice<[T], B> {
    /// Returns the number of elements in this slice.
    pub fn len(&self) -> DeviceSize {
        debug_assert_eq!(self.size() % size_of::<T>() as DeviceSize, 0);
        self.size() / size_of::<T>() as DeviceSize
    }

    /// Reduces the slice to just one element of the array.
    ///
    /// Returns `None` if out of range.
    pub fn index(&self, index: DeviceSize) -> Option<Arc<BufferSlice<T, B>>> {
        if index >= self.len() {
            return None;
        }

        Some(Arc::new(BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset + index * size_of::<T>() as DeviceSize,
            size: size_of::<T>() as DeviceSize,
        }))
    }

    /// Reduces the slice to just a range of the array.
    ///
    /// Returns `None` if out of range.
    pub fn slice(&self, range: Range<DeviceSize>) -> Option<Arc<BufferSlice<[T], B>>> {
        if range.end > self.len() {
            return None;
        }

        Some(Arc::new(BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset + range.start * size_of::<T>() as DeviceSize,
            size: (range.end - range.start) * size_of::<T>() as DeviceSize,
        }))
    }
}

unsafe impl<T, B> BufferAccess for BufferSlice<T, B>
where
    B: BufferAccess,
    T: Send + Sync + ?Sized,
{
    fn inner(&self) -> BufferInner<'_> {
        let inner = self.resource.inner();
        BufferInner {
            buffer: inner.buffer,
            offset: inner.offset + self.offset,
        }
    }

    fn size(&self) -> DeviceSize {
        self.size
    }
}

impl<T, B> BufferAccessObject for Arc<BufferSlice<T, B>>
where
    B: BufferAccess + 'static,
    T: Send + Sync + ?Sized + 'static,
{
    fn as_buffer_access_object(&self) -> Arc<dyn BufferAccess> {
        self.clone()
    }
}

unsafe impl<T, B> TypedBufferAccess for BufferSlice<T, B>
where
    T: BufferContents + ?Sized,
    B: BufferAccess,
{
    type Content = T;
}

unsafe impl<T: ?Sized, B> DeviceOwned for BufferSlice<T, B>
where
    B: DeviceOwned,
{
    fn device(&self) -> &Arc<Device> {
        self.resource.device()
    }
}

impl<T, B> From<BufferSlice<T, B>> for BufferSlice<[T], B> {
    fn from(r: BufferSlice<T, B>) -> BufferSlice<[T], B> {
        BufferSlice {
            marker: PhantomData,
            resource: r.resource,
            offset: r.offset,
            size: r.size,
        }
    }
}

impl<T: ?Sized, B> PartialEq for BufferSlice<T, B>
where
    T: Send + Sync,
    B: BufferAccess,
{
    fn eq(&self, other: &Self) -> bool {
        self.inner() == other.inner() && self.size() == other.size()
    }
}

impl<T: ?Sized, B> Eq for BufferSlice<T, B>
where
    B: BufferAccess,
    T: Send + Sync,
{
}

impl<T: ?Sized, B> Hash for BufferSlice<T, B>
where
    T: Send + Sync,
    B: BufferAccess,
{
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.inner().hash(state);
        self.size().hash(state);
    }
}

/// Takes a `BufferSlice` that points to a struct, and returns a `BufferSlice` that points to
/// a specific field of that struct.
#[macro_export]
macro_rules! buffer_slice_field {
    ($slice:expr, $field:ident) => {
        // TODO: add #[allow(unsafe_code)] when that's allowed
        unsafe { $slice.slice_custom(|s| &s.$field) }
    };
}
