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

//! View of a buffer, in order to use it as a uniform texel buffer or storage texel buffer.
//!
//! In order to use a buffer as a uniform texel buffer or a storage texel buffer, you have to
//! create a `BufferView`, which indicates which format the data is in.
//!
//! In order to create a view from a buffer, the buffer must have been created with either the
//! `uniform_texel_buffer` or the `storage_texel_buffer` usage.
//!
//! # Example
//!
//! ```
//! # use std::sync::Arc;
//! use vulkano::buffer::immutable::ImmutableBuffer;
//! use vulkano::buffer::BufferUsage;
//! use vulkano::buffer::BufferView;
//! use vulkano::format::Format;
//!
//! # let device: Arc<vulkano::device::Device> = return;
//! # let queue: Arc<vulkano::device::Queue> = return;
//! let usage = BufferUsage {
//!     storage_texel_buffer: true,
//!     .. BufferUsage::none()
//! };
//!
//! let (buffer, _future) = ImmutableBuffer::<[u32]>::from_iter((0..128).map(|n| n), usage,
//!                                                             queue.clone()).unwrap();
//! let _view = BufferView::new(buffer, Format::R32Uint).unwrap();
//! ```

use crate::buffer::BufferAccess;
use crate::buffer::BufferInner;
use crate::buffer::TypedBufferAccess;
use crate::check_errors;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::format::Format;
use crate::format::Pixel;
use crate::vk;
use crate::Error;
use crate::OomError;
use crate::SafeDeref;
use crate::VulkanObject;
use std::error;
use std::fmt;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;

/// Represents a way for the GPU to interpret buffer data. See the documentation of the
/// `view` module.
pub struct BufferView<B>
where
    B: BufferAccess,
{
    view: vk::BufferView,
    buffer: B,
    atomic_accesses: bool,
}

impl<B> BufferView<B>
where
    B: BufferAccess,
{
    /// Builds a new buffer view.
    #[inline]
    pub fn new<Px>(buffer: B, format: Format) -> Result<BufferView<B>, BufferViewCreationError>
    where
        B: TypedBufferAccess<Content = [Px]>,
        Px: Pixel,
    {
        unsafe { BufferView::unchecked(buffer, format) }
    }

    /// Builds a new buffer view without checking that the format is correct.
    pub unsafe fn unchecked(
        org_buffer: B,
        format: Format,
    ) -> Result<BufferView<B>, BufferViewCreationError>
    where
        B: BufferAccess,
    {
        let (view, format_props) = {
            let size = org_buffer.size();
            let BufferInner { buffer, offset } = org_buffer.inner();

            let device = buffer.device();

            if (offset
                % device
                    .physical_device()
                    .limits()
                    .min_texel_buffer_offset_alignment() as usize)
                != 0
            {
                return Err(BufferViewCreationError::WrongBufferAlignment);
            }

            if !buffer.usage().uniform_texel_buffer && !buffer.usage().storage_texel_buffer {
                return Err(BufferViewCreationError::WrongBufferUsage);
            }

            {
                let nb = size
                    / format
                        .size()
                        .expect("Can't use a compressed format for buffer views");
                let l = device
                    .physical_device()
                    .limits()
                    .max_texel_buffer_elements();
                if nb > l as usize {
                    return Err(BufferViewCreationError::MaxTexelBufferElementsExceeded);
                }
            }

            let format_props = {
                let vk_i = device.instance().pointers();
                let mut output = MaybeUninit::uninit();
                vk_i.GetPhysicalDeviceFormatProperties(
                    device.physical_device().internal_object(),
                    format as u32,
                    output.as_mut_ptr(),
                );
                output.assume_init().bufferFeatures
            };

            if buffer.usage().uniform_texel_buffer {
                if (format_props & vk::FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) == 0 {
                    return Err(BufferViewCreationError::UnsupportedFormat);
                }
            }

            if buffer.usage().storage_texel_buffer {
                if (format_props & vk::FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT) == 0 {
                    return Err(BufferViewCreationError::UnsupportedFormat);
                }
            }

            let infos = vk::BufferViewCreateInfo {
                sType: vk::STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO,
                pNext: ptr::null(),
                flags: 0, // reserved,
                buffer: buffer.internal_object(),
                format: format as u32,
                offset: offset as u64,
                range: size as u64,
            };

            let vk = device.pointers();
            let mut output = MaybeUninit::uninit();
            check_errors(vk.CreateBufferView(
                device.internal_object(),
                &infos,
                ptr::null(),
                output.as_mut_ptr(),
            ))?;
            (output.assume_init(), format_props)
        };

        Ok(BufferView {
            view,
            buffer: org_buffer,
            atomic_accesses: (format_props & vk::FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_ATOMIC_BIT)
                != 0,
        })
    }

    /// Returns the buffer associated to this view.
    #[inline]
    pub fn buffer(&self) -> &B {
        &self.buffer
    }

    /// Returns true if the buffer view can be used as a uniform texel buffer.
    #[inline]
    pub fn uniform_texel_buffer(&self) -> bool {
        self.buffer.inner().buffer.usage().uniform_texel_buffer
    }

    /// Returns true if the buffer view can be used as a storage texel buffer.
    #[inline]
    pub fn storage_texel_buffer(&self) -> bool {
        self.buffer.inner().buffer.usage().storage_texel_buffer
    }

    /// Returns true if the buffer view can be used as a storage texel buffer with atomic accesses.
    #[inline]
    pub fn storage_texel_buffer_atomic(&self) -> bool {
        self.atomic_accesses && self.storage_texel_buffer()
    }
}

unsafe impl<B> VulkanObject for BufferView<B>
where
    B: BufferAccess,
{
    type Object = vk::BufferView;

    const TYPE: vk::ObjectType = vk::OBJECT_TYPE_BUFFER_VIEW;

    #[inline]
    fn internal_object(&self) -> vk::BufferView {
        self.view
    }
}

unsafe impl<B> DeviceOwned for BufferView<B>
where
    B: BufferAccess,
{
    #[inline]
    fn device(&self) -> &Arc<Device> {
        self.buffer.device()
    }
}

impl<B> fmt::Debug for BufferView<B>
where
    B: BufferAccess + fmt::Debug,
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        fmt.debug_struct("BufferView")
            .field("raw", &self.view)
            .field("buffer", &self.buffer)
            .finish()
    }
}

impl<B> Drop for BufferView<B>
where
    B: BufferAccess,
{
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let vk = self.buffer.inner().buffer.device().pointers();
            vk.DestroyBufferView(
                self.buffer.inner().buffer.device().internal_object(),
                self.view,
                ptr::null(),
            );
        }
    }
}

pub unsafe trait BufferViewRef {
    type BufferAccess: BufferAccess;

    fn view(&self) -> &BufferView<Self::BufferAccess>;
}

unsafe impl<B> BufferViewRef for BufferView<B>
where
    B: BufferAccess,
{
    type BufferAccess = B;

    #[inline]
    fn view(&self) -> &BufferView<B> {
        self
    }
}

unsafe impl<T, B> BufferViewRef for T
where
    T: SafeDeref<Target = BufferView<B>>,
    B: BufferAccess,
{
    type BufferAccess = B;

    #[inline]
    fn view(&self) -> &BufferView<B> {
        &**self
    }
}

/// Error that can happen when creating a buffer view.
#[derive(Debug, Copy, Clone)]
pub enum BufferViewCreationError {
    /// Out of memory.
    OomError(OomError),

    /// The buffer was not creating with one of the `storage_texel_buffer` or
    /// `uniform_texel_buffer` usages.
    WrongBufferUsage,

    /// The offset within the buffer is not a multiple of the `min_texel_buffer_offset_alignment`
    /// limit.
    WrongBufferAlignment,

    /// The requested format is not supported for this usage.
    UnsupportedFormat,

    /// The maximum number of elements in the buffer view has been exceeded.
    MaxTexelBufferElementsExceeded,
}

impl error::Error for BufferViewCreationError {
    #[inline]
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            BufferViewCreationError::OomError(ref err) => Some(err),
            _ => None,
        }
    }
}

impl fmt::Display for BufferViewCreationError {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(
            fmt,
            "{}",
            match *self {
                BufferViewCreationError::OomError(_) => "out of memory when creating buffer view",
                BufferViewCreationError::WrongBufferUsage => {
                    "the buffer is missing correct usage flags"
                }
                BufferViewCreationError::WrongBufferAlignment => {
                    "the offset within the buffer is not a multiple of the
                 `min_texel_buffer_offset_alignment` limit"
                }
                BufferViewCreationError::UnsupportedFormat => {
                    "the requested format is not supported for this usage"
                }
                BufferViewCreationError::MaxTexelBufferElementsExceeded => {
                    "the maximum number of texel elements is exceeded"
                }
            }
        )
    }
}

impl From<OomError> for BufferViewCreationError {
    #[inline]
    fn from(err: OomError) -> BufferViewCreationError {
        BufferViewCreationError::OomError(err)
    }
}

impl From<Error> for BufferViewCreationError {
    #[inline]
    fn from(err: Error) -> BufferViewCreationError {
        OomError::from(err).into()
    }
}

#[cfg(test)]
mod tests {
    use crate::buffer::immutable::ImmutableBuffer;
    use crate::buffer::view::BufferViewCreationError;
    use crate::buffer::BufferUsage;
    use crate::buffer::BufferView;
    use crate::format::Format;

    #[test]
    fn create_uniform() {
        // `VK_FORMAT_R8G8B8A8_UNORM` guaranteed to be a supported format
        let (device, queue) = gfx_dev_and_queue!();

        let usage = BufferUsage {
            uniform_texel_buffer: true,
            ..BufferUsage::none()
        };

        let (buffer, _) =
            ImmutableBuffer::<[[u8; 4]]>::from_iter((0..128).map(|_| [0; 4]), usage, queue.clone())
                .unwrap();
        let view = BufferView::new(buffer, Format::R8G8B8A8Unorm).unwrap();

        assert!(view.uniform_texel_buffer());
    }

    #[test]
    fn create_storage() {
        // `VK_FORMAT_R8G8B8A8_UNORM` guaranteed to be a supported format
        let (device, queue) = gfx_dev_and_queue!();

        let usage = BufferUsage {
            storage_texel_buffer: true,
            ..BufferUsage::none()
        };

        let (buffer, _) =
            ImmutableBuffer::<[[u8; 4]]>::from_iter((0..128).map(|_| [0; 4]), usage, queue.clone())
                .unwrap();
        let view = BufferView::new(buffer, Format::R8G8B8A8Unorm).unwrap();

        assert!(view.storage_texel_buffer());
    }

    #[test]
    fn create_storage_atomic() {
        // `VK_FORMAT_R32_UINT` guaranteed to be a supported format for atomics
        let (device, queue) = gfx_dev_and_queue!();

        let usage = BufferUsage {
            storage_texel_buffer: true,
            ..BufferUsage::none()
        };

        let (buffer, _) =
            ImmutableBuffer::<[u32]>::from_iter((0..128).map(|_| 0), usage, queue.clone()).unwrap();
        let view = BufferView::new(buffer, Format::R32Uint).unwrap();

        assert!(view.storage_texel_buffer());
        assert!(view.storage_texel_buffer_atomic());
    }

    #[test]
    fn wrong_usage() {
        // `VK_FORMAT_R8G8B8A8_UNORM` guaranteed to be a supported format
        let (device, queue) = gfx_dev_and_queue!();

        let (buffer, _) = ImmutableBuffer::<[[u8; 4]]>::from_iter(
            (0..128).map(|_| [0; 4]),
            BufferUsage::none(),
            queue.clone(),
        )
        .unwrap();

        match BufferView::new(buffer, Format::R8G8B8A8Unorm) {
            Err(BufferViewCreationError::WrongBufferUsage) => (),
            _ => panic!(),
        }
    }

    #[test]
    fn unsupported_format() {
        let (device, queue) = gfx_dev_and_queue!();

        let usage = BufferUsage {
            uniform_texel_buffer: true,
            storage_texel_buffer: true,
            ..BufferUsage::none()
        };

        let (buffer, _) = ImmutableBuffer::<[[f64; 4]]>::from_iter(
            (0..128).map(|_| [0.0; 4]),
            usage,
            queue.clone(),
        )
        .unwrap();

        // TODO: what if R64G64B64A64Sfloat is supported?
        match BufferView::new(buffer, Format::R64G64B64A64Sfloat) {
            Err(BufferViewCreationError::UnsupportedFormat) => (),
            _ => panic!(),
        }
    }
}
