// 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 crate::buffer::traits::BufferAccess;
use crate::buffer::traits::BufferInner;
use crate::buffer::traits::TypedBufferAccess;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::device::Queue;
use crate::sync::AccessError;
use crate::DeviceSize;
use std::hash::Hash;
use std::hash::Hasher;
use std::marker::PhantomData;
use std::mem;
use std::mem::MaybeUninit;
use std::ops::Range;
use std::sync::Arc;

/// A subpart of a buffer.
///
/// This object doesn't correspond to any Vulkan object. It exists for API convenience.
///
/// # Example
///
/// 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();
/// ```
///
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> {
    #[inline]
    fn clone(&self) -> Self {
        BufferSlice {
            marker: PhantomData,
            resource: self.resource.clone(),
            offset: self.offset,
            size: self.size,
        }
    }
}

impl<T: ?Sized, B> BufferSlice<T, B> {
    #[inline]
    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: 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.
    #[inline]
    pub fn offset(&self) -> DeviceSize {
        self.offset
    }

    /// Returns the size of that slice in bytes.
    #[inline]
    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.
    ///
    /// # Example
    ///
    /// 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.
    #[inline]
    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 = mem::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.
    ///
    /// # Example
    ///
    /// ```
    /// # use std::sync::Arc;
    /// # use vulkano::buffer::BufferSlice;
    /// # use vulkano::buffer::immutable::ImmutableBuffer;
    /// # struct VertexImpl;
    /// let blob_slice: Arc<BufferSlice<[u8], Arc<ImmutableBuffer<[u8]>>>> = return;
    /// let vertex_slice: Arc<BufferSlice<[VertexImpl], Arc<ImmutableBuffer<[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.
    #[inline]
    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.
    #[inline]
    pub fn len(&self) -> DeviceSize {
        debug_assert_eq!(self.size() % mem::size_of::<T>() as DeviceSize, 0);
        self.size() / mem::size_of::<T>() as DeviceSize
    }

    /// Reduces the slice to just one element of the array.
    ///
    /// Returns `None` if out of range.
    #[inline]
    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 * mem::size_of::<T>() as DeviceSize,
            size: mem::size_of::<T>() as DeviceSize,
        }))
    }

    /// Reduces the slice to just a range of the array.
    ///
    /// Returns `None` if out of range.
    #[inline]
    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 * mem::size_of::<T>() as DeviceSize,
            size: (range.end - range.start) * mem::size_of::<T>() as DeviceSize,
        }))
    }
}

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

    #[inline]
    fn size(&self) -> DeviceSize {
        self.size
    }

    #[inline]
    fn conflict_key(&self) -> (u64, u64) {
        self.resource.conflict_key()
    }

    #[inline]
    fn try_gpu_lock(&self, exclusive_access: bool, queue: &Queue) -> Result<(), AccessError> {
        self.resource.try_gpu_lock(exclusive_access, queue)
    }

    #[inline]
    unsafe fn increase_gpu_lock(&self) {
        self.resource.increase_gpu_lock()
    }

    #[inline]
    unsafe fn unlock(&self) {
        self.resource.unlock()
    }
}

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

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

impl<T, B> From<BufferSlice<T, B>> for BufferSlice<[T], B> {
    #[inline]
    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,
{
    #[inline]
    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,
{
    #[inline]
    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) }
    };
}
