// 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.
//!
//! # Examples
//!
//! ```
//! # use std::sync::Arc;
//! use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage};
//! use vulkano::buffer::view::{BufferView, BufferViewCreateInfo};
//! use vulkano::format::Format;
//! use vulkano::memory::allocator::AllocationCreateInfo;
//!
//! # let queue: Arc<vulkano::device::Queue> = return;
//! # let memory_allocator: vulkano::memory::allocator::StandardMemoryAllocator = return;
//! let buffer = Buffer::new_slice::<u32>(
//!     &memory_allocator,
//!     BufferCreateInfo {
//!         usage: BufferUsage::STORAGE_TEXEL_BUFFER,
//!         ..Default::default()
//!     },
//!     AllocationCreateInfo::default(),
//!     128,
//! )
//! .unwrap();
//!
//! let view = BufferView::new(
//!     buffer,
//!     BufferViewCreateInfo {
//!         format: Some(Format::R32_UINT),
//!         ..Default::default()
//!     },
//! )
//! .unwrap();
//! ```

use super::{BufferUsage, Subbuffer};
use crate::{
    device::{Device, DeviceOwned},
    format::{Format, FormatFeatures},
    macros::impl_id_counter,
    memory::{is_aligned, DeviceAlignment},
    DeviceSize, OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    mem::MaybeUninit,
    num::NonZeroU64,
    ops::Range,
    ptr,
    sync::Arc,
};

/// Represents a way for the GPU to interpret buffer data. See the documentation of the
/// `view` module.
#[derive(Debug)]
pub struct BufferView {
    handle: ash::vk::BufferView,
    subbuffer: Subbuffer<[u8]>,
    id: NonZeroU64,

    format: Option<Format>,
    format_features: FormatFeatures,
    range: Range<DeviceSize>,
}

impl BufferView {
    /// Creates a new `BufferView`.
    #[inline]
    pub fn new(
        subbuffer: Subbuffer<impl ?Sized>,
        create_info: BufferViewCreateInfo,
    ) -> Result<Arc<BufferView>, BufferViewCreationError> {
        Self::new_inner(subbuffer.into_bytes(), create_info)
    }

    fn new_inner(
        subbuffer: Subbuffer<[u8]>,
        create_info: BufferViewCreateInfo,
    ) -> Result<Arc<BufferView>, BufferViewCreationError> {
        let BufferViewCreateInfo { format, _ne: _ } = create_info;

        let buffer = subbuffer.buffer();
        let device = buffer.device();
        let properties = device.physical_device().properties();
        let size = subbuffer.size();
        let offset = subbuffer.offset();

        // No VUID, but seems sensible?
        let format = format.unwrap();

        // VUID-VkBufferViewCreateInfo-format-parameter
        format.validate_device(device)?;

        // VUID-VkBufferViewCreateInfo-buffer-00932
        if !buffer
            .usage()
            .intersects(BufferUsage::UNIFORM_TEXEL_BUFFER | BufferUsage::STORAGE_TEXEL_BUFFER)
        {
            return Err(BufferViewCreationError::BufferMissingUsage);
        }

        // Use unchecked, because all validation has been done above.
        let format_features = unsafe {
            device
                .physical_device()
                .format_properties_unchecked(format)
                .buffer_features
        };

        // VUID-VkBufferViewCreateInfo-buffer-00933
        if buffer.usage().intersects(BufferUsage::UNIFORM_TEXEL_BUFFER)
            && !format_features.intersects(FormatFeatures::UNIFORM_TEXEL_BUFFER)
        {
            return Err(BufferViewCreationError::UnsupportedFormat);
        }

        // VUID-VkBufferViewCreateInfo-buffer-00934
        if buffer.usage().intersects(BufferUsage::STORAGE_TEXEL_BUFFER)
            && !format_features.intersects(FormatFeatures::STORAGE_TEXEL_BUFFER)
        {
            return Err(BufferViewCreationError::UnsupportedFormat);
        }

        let block_size = format.block_size().unwrap();
        let texels_per_block = format.texels_per_block();

        // VUID-VkBufferViewCreateInfo-range-00929
        if size % block_size != 0 {
            return Err(BufferViewCreationError::RangeNotAligned {
                range: size,
                required_alignment: block_size,
            });
        }

        // VUID-VkBufferViewCreateInfo-range-00930
        if ((size / block_size) * texels_per_block as DeviceSize) as u32
            > properties.max_texel_buffer_elements
        {
            return Err(BufferViewCreationError::MaxTexelBufferElementsExceeded);
        }

        if device.api_version() >= Version::V1_3 || device.enabled_features().texel_buffer_alignment
        {
            let element_size = DeviceAlignment::new(if block_size % 3 == 0 {
                block_size / 3
            } else {
                block_size
            })
            .unwrap();

            if buffer.usage().intersects(BufferUsage::STORAGE_TEXEL_BUFFER) {
                let mut required_alignment = properties
                    .storage_texel_buffer_offset_alignment_bytes
                    .unwrap();

                if properties
                    .storage_texel_buffer_offset_single_texel_alignment
                    .unwrap()
                {
                    required_alignment = required_alignment.min(element_size);
                }

                // VUID-VkBufferViewCreateInfo-buffer-02750
                if !is_aligned(offset, required_alignment) {
                    return Err(BufferViewCreationError::OffsetNotAligned {
                        offset,
                        required_alignment,
                    });
                }
            }

            if buffer.usage().intersects(BufferUsage::UNIFORM_TEXEL_BUFFER) {
                let mut required_alignment = properties
                    .uniform_texel_buffer_offset_alignment_bytes
                    .unwrap();

                if properties
                    .uniform_texel_buffer_offset_single_texel_alignment
                    .unwrap()
                {
                    required_alignment = required_alignment.min(element_size);
                }

                // VUID-VkBufferViewCreateInfo-buffer-02751
                if !is_aligned(offset, required_alignment) {
                    return Err(BufferViewCreationError::OffsetNotAligned {
                        offset,
                        required_alignment,
                    });
                }
            }
        } else {
            let required_alignment = properties.min_texel_buffer_offset_alignment;

            // VUID-VkBufferViewCreateInfo-offset-02749
            if !is_aligned(offset, required_alignment) {
                return Err(BufferViewCreationError::OffsetNotAligned {
                    offset,
                    required_alignment,
                });
            }
        }

        let create_info = ash::vk::BufferViewCreateInfo {
            flags: ash::vk::BufferViewCreateFlags::empty(),
            buffer: buffer.handle(),
            format: format.into(),
            offset,
            range: size,
            ..Default::default()
        };

        let handle = unsafe {
            let fns = device.fns();
            let mut output = MaybeUninit::uninit();
            (fns.v1_0.create_buffer_view)(
                device.handle(),
                &create_info,
                ptr::null(),
                output.as_mut_ptr(),
            )
            .result()
            .map_err(VulkanError::from)?;
            output.assume_init()
        };

        Ok(Arc::new(BufferView {
            handle,
            subbuffer,
            id: Self::next_id(),
            format: Some(format),
            format_features,
            range: 0..size,
        }))
    }

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

    /// Returns the format of this view.
    #[inline]
    pub fn format(&self) -> Option<Format> {
        self.format
    }

    /// Returns the features supported by this view’s format.
    #[inline]
    pub fn format_features(&self) -> FormatFeatures {
        self.format_features
    }

    /// Returns the byte range of the wrapped buffer that this view exposes.
    #[inline]
    pub fn range(&self) -> Range<DeviceSize> {
        self.range.clone()
    }
}

impl Drop for BufferView {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let fns = self.subbuffer.device().fns();
            (fns.v1_0.destroy_buffer_view)(
                self.subbuffer.device().handle(),
                self.handle,
                ptr::null(),
            );
        }
    }
}

unsafe impl VulkanObject for BufferView {
    type Handle = ash::vk::BufferView;

    #[inline]
    fn handle(&self) -> Self::Handle {
        self.handle
    }
}

unsafe impl DeviceOwned for BufferView {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        self.subbuffer.device()
    }
}

impl_id_counter!(BufferView);

/// Parameters to create a new `BufferView`.
#[derive(Clone, Debug)]
pub struct BufferViewCreateInfo {
    /// The format of the buffer view.
    ///
    /// The default value is `None`, which must be overridden.
    pub format: Option<Format>,

    pub _ne: crate::NonExhaustive,
}

impl Default for BufferViewCreateInfo {
    #[inline]
    fn default() -> Self {
        Self {
            format: None,
            _ne: crate::NonExhaustive(()),
        }
    }
}

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

    RequirementNotMet {
        required_for: &'static str,
        requires_one_of: RequiresOneOf,
    },

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

    /// The offset within the buffer is not a multiple of the required alignment.
    OffsetNotAligned {
        offset: DeviceSize,
        required_alignment: DeviceAlignment,
    },

    /// The range within the buffer is not a multiple of the required alignment.
    RangeNotAligned {
        range: DeviceSize,
        required_alignment: DeviceSize,
    },

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

    /// The `max_texel_buffer_elements` limit has been exceeded.
    MaxTexelBufferElementsExceeded,
}

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

impl Display for BufferViewCreationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "out of memory when creating buffer view"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),
            Self::BufferMissingUsage => write!(
                f,
                "the buffer was not created with one of the `storage_texel_buffer` or \
                `uniform_texel_buffer` usages",
            ),
            Self::OffsetNotAligned { .. } => write!(
                f,
                "the offset within the buffer is not a multiple of the required alignment",
            ),
            Self::RangeNotAligned { .. } => write!(
                f,
                "the range within the buffer is not a multiple of the required alignment",
            ),
            Self::UnsupportedFormat => {
                write!(f, "the requested format is not supported for this usage")
            }
            Self::MaxTexelBufferElementsExceeded => {
                write!(f, "the `max_texel_buffer_elements` limit has been exceeded")
            }
        }
    }
}

impl From<OomError> for BufferViewCreationError {
    fn from(err: OomError) -> Self {
        Self::OomError(err)
    }
}

impl From<VulkanError> for BufferViewCreationError {
    fn from(err: VulkanError) -> Self {
        OomError::from(err).into()
    }
}

impl From<RequirementNotMet> for BufferViewCreationError {
    fn from(err: RequirementNotMet) -> Self {
        Self::RequirementNotMet {
            required_for: err.required_for,
            requires_one_of: err.requires_one_of,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{BufferView, BufferViewCreateInfo, BufferViewCreationError};
    use crate::{
        buffer::{Buffer, BufferCreateInfo, BufferUsage},
        format::Format,
        memory::allocator::{AllocationCreateInfo, MemoryUsage, StandardMemoryAllocator},
    };

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

        let buffer = Buffer::new_slice::<[u8; 4]>(
            &memory_allocator,
            BufferCreateInfo {
                usage: BufferUsage::UNIFORM_TEXEL_BUFFER,
                ..Default::default()
            },
            AllocationCreateInfo {
                usage: MemoryUsage::Upload,
                ..Default::default()
            },
            128,
        )
        .unwrap();
        BufferView::new(
            buffer,
            BufferViewCreateInfo {
                format: Some(Format::R8G8B8A8_UNORM),
                ..Default::default()
            },
        )
        .unwrap();
    }

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

        let buffer = Buffer::new_slice::<[u8; 4]>(
            &memory_allocator,
            BufferCreateInfo {
                usage: BufferUsage::STORAGE_TEXEL_BUFFER,
                ..Default::default()
            },
            AllocationCreateInfo::default(),
            128,
        )
        .unwrap();
        BufferView::new(
            buffer,
            BufferViewCreateInfo {
                format: Some(Format::R8G8B8A8_UNORM),
                ..Default::default()
            },
        )
        .unwrap();
    }

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

        let buffer = Buffer::new_slice::<u32>(
            &memory_allocator,
            BufferCreateInfo {
                usage: BufferUsage::STORAGE_TEXEL_BUFFER,
                ..Default::default()
            },
            AllocationCreateInfo::default(),
            128,
        )
        .unwrap();
        BufferView::new(
            buffer,
            BufferViewCreateInfo {
                format: Some(Format::R32_UINT),
                ..Default::default()
            },
        )
        .unwrap();
    }

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

        let buffer = Buffer::new_slice::<[u8; 4]>(
            &memory_allocator,
            BufferCreateInfo {
                usage: BufferUsage::TRANSFER_DST, // Dummy value
                ..Default::default()
            },
            AllocationCreateInfo::default(),
            128,
        )
        .unwrap();

        match BufferView::new(
            buffer,
            BufferViewCreateInfo {
                format: Some(Format::R8G8B8A8_UNORM),
                ..Default::default()
            },
        ) {
            Err(BufferViewCreationError::BufferMissingUsage) => (),
            _ => panic!(),
        }
    }

    #[test]
    fn unsupported_format() {
        let (device, _) = gfx_dev_and_queue!();
        let memory_allocator = StandardMemoryAllocator::new_default(device);

        let buffer = Buffer::new_slice::<[f64; 4]>(
            &memory_allocator,
            BufferCreateInfo {
                usage: BufferUsage::UNIFORM_TEXEL_BUFFER | BufferUsage::STORAGE_TEXEL_BUFFER,
                ..Default::default()
            },
            AllocationCreateInfo::default(),
            128,
        )
        .unwrap();

        // TODO: what if R64G64B64A64_SFLOAT is supported?
        match BufferView::new(
            buffer,
            BufferViewCreateInfo {
                format: Some(Format::R64G64B64A64_SFLOAT),
                ..Default::default()
            },
        ) {
            Err(BufferViewCreationError::UnsupportedFormat) => (),
            _ => panic!(),
        }
    }
}
