// 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::{
    allocator::{
        CommandBufferAlloc, CommandBufferAllocator, CommandBufferBuilderAlloc,
        StandardCommandBufferAlloc, StandardCommandBufferAllocator,
    },
    synced::{CommandBufferBuilderState, SyncCommandBuffer, SyncCommandBufferBuilder},
    sys::CommandBufferBeginInfo,
    CommandBufferExecError, CommandBufferInheritanceInfo, CommandBufferInheritanceRenderPassInfo,
    CommandBufferInheritanceRenderPassType, CommandBufferLevel, CommandBufferResourcesUsage,
    CommandBufferState, CommandBufferUsage, PrimaryCommandBufferAbstract, RenderingAttachmentInfo,
    SecondaryCommandBufferAbstract, SecondaryCommandBufferResourcesUsage, SubpassContents,
};
use crate::{
    command_buffer::CommandBufferInheritanceRenderingInfo,
    device::{Device, DeviceOwned, QueueFamilyProperties},
    format::{Format, FormatFeatures},
    image::ImageAspects,
    query::{QueryControlFlags, QueryType},
    render_pass::{Framebuffer, Subpass},
    OomError, RequirementNotMet, RequiresOneOf, VulkanObject,
};
use ahash::HashMap;
use parking_lot::{Mutex, MutexGuard};
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    marker::PhantomData,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
};

/// Note that command buffers allocated from `StandardCommandBufferAllocator` don't implement
/// the `Send` and `Sync` traits. If you use this allocator, then the `AutoCommandBufferBuilder`
/// will not implement `Send` and `Sync` either. Once a command buffer is built, however, it *does*
/// implement `Send` and `Sync`.
pub struct AutoCommandBufferBuilder<L, A = StandardCommandBufferAllocator>
where
    A: CommandBufferAllocator,
{
    pub(super) inner: SyncCommandBufferBuilder,
    builder_alloc: A::Builder, // Safety: must be dropped after `inner`

    // The index of the queue family that this command buffer is being created for.
    queue_family_index: u32,

    // The inheritance for secondary command buffers.
    // Must be `None` in a primary command buffer and `Some` in a secondary command buffer.
    pub(super) inheritance_info: Option<CommandBufferInheritanceInfo>,

    // Usage flags passed when creating the command buffer.
    pub(super) usage: CommandBufferUsage,

    // If we're inside a render pass, contains the render pass state.
    pub(super) render_pass_state: Option<RenderPassState>,

    // If any queries are active, this hashmap contains their state.
    pub(super) query_state: HashMap<ash::vk::QueryType, QueryState>,

    _data: PhantomData<L>,
}

// The state of the current render pass.
pub(super) struct RenderPassState {
    pub(super) contents: SubpassContents,
    pub(super) render_area_offset: [u32; 2],
    pub(super) render_area_extent: [u32; 2],
    pub(super) render_pass: RenderPassStateType,
    pub(super) view_mask: u32,
}

pub(super) enum RenderPassStateType {
    BeginRenderPass(BeginRenderPassState),
    BeginRendering(BeginRenderingState),
}

impl From<BeginRenderPassState> for RenderPassStateType {
    fn from(val: BeginRenderPassState) -> Self {
        Self::BeginRenderPass(val)
    }
}

impl From<BeginRenderingState> for RenderPassStateType {
    fn from(val: BeginRenderingState) -> Self {
        Self::BeginRendering(val)
    }
}

pub(super) struct BeginRenderPassState {
    pub(super) subpass: Subpass,
    pub(super) framebuffer: Option<Arc<Framebuffer>>,
}

pub(super) struct BeginRenderingState {
    pub(super) attachments: Option<BeginRenderingAttachments>,
    pub(super) color_attachment_formats: Vec<Option<Format>>,
    pub(super) depth_attachment_format: Option<Format>,
    pub(super) stencil_attachment_format: Option<Format>,
    pub(super) pipeline_used: bool,
}

pub(super) struct BeginRenderingAttachments {
    pub(super) color_attachments: Vec<Option<RenderingAttachmentInfo>>,
    pub(super) depth_attachment: Option<RenderingAttachmentInfo>,
    pub(super) stencil_attachment: Option<RenderingAttachmentInfo>,
}

// The state of an active query.
pub(super) struct QueryState {
    pub(super) query_pool: ash::vk::QueryPool,
    pub(super) query: u32,
    pub(super) ty: QueryType,
    pub(super) flags: QueryControlFlags,
    pub(super) in_subpass: bool,
}

impl<A> AutoCommandBufferBuilder<PrimaryAutoCommandBuffer, A>
where
    A: CommandBufferAllocator,
{
    /// Starts recording a primary command buffer.
    #[inline]
    pub fn primary(
        allocator: &A,
        queue_family_index: u32,
        usage: CommandBufferUsage,
    ) -> Result<
        AutoCommandBufferBuilder<PrimaryAutoCommandBuffer<A::Alloc>, A>,
        CommandBufferBeginError,
    > {
        unsafe {
            AutoCommandBufferBuilder::begin(
                allocator,
                queue_family_index,
                CommandBufferLevel::Primary,
                CommandBufferBeginInfo {
                    usage,
                    inheritance_info: None,
                    _ne: crate::NonExhaustive(()),
                },
            )
        }
    }
}

impl<A> AutoCommandBufferBuilder<SecondaryAutoCommandBuffer, A>
where
    A: CommandBufferAllocator,
{
    /// Starts recording a secondary command buffer.
    #[inline]
    pub fn secondary(
        allocator: &A,
        queue_family_index: u32,
        usage: CommandBufferUsage,
        inheritance_info: CommandBufferInheritanceInfo,
    ) -> Result<
        AutoCommandBufferBuilder<SecondaryAutoCommandBuffer<A::Alloc>, A>,
        CommandBufferBeginError,
    > {
        unsafe {
            AutoCommandBufferBuilder::begin(
                allocator,
                queue_family_index,
                CommandBufferLevel::Secondary,
                CommandBufferBeginInfo {
                    usage,
                    inheritance_info: Some(inheritance_info),
                    _ne: crate::NonExhaustive(()),
                },
            )
        }
    }
}

impl<L, A> AutoCommandBufferBuilder<L, A>
where
    A: CommandBufferAllocator,
{
    // Actual constructor. Private.
    //
    // `begin_info.inheritance_info` must match `level`.
    unsafe fn begin(
        allocator: &A,
        queue_family_index: u32,
        level: CommandBufferLevel,
        begin_info: CommandBufferBeginInfo,
    ) -> Result<AutoCommandBufferBuilder<L, A>, CommandBufferBeginError> {
        Self::validate_begin(allocator.device(), queue_family_index, level, &begin_info)?;

        let &CommandBufferBeginInfo {
            usage,
            ref inheritance_info,
            _ne: _,
        } = &begin_info;

        let inheritance_info = inheritance_info.clone();
        let mut render_pass_state = None;

        if let Some(inheritance_info) = &inheritance_info {
            let &CommandBufferInheritanceInfo {
                ref render_pass,
                occlusion_query: _,
                query_statistics_flags: _,
                _ne: _,
            } = inheritance_info;

            if let Some(render_pass) = render_pass {
                // In a secondary command buffer, we don't know the render area yet, so use a
                // dummy value.
                let render_area_offset = [0, 0];
                let mut render_area_extent = [u32::MAX, u32::MAX];

                match render_pass {
                    CommandBufferInheritanceRenderPassType::BeginRenderPass(info) => {
                        if let Some(framebuffer) = &info.framebuffer {
                            // Still not exact, but it's a better upper bound.
                            render_area_extent = framebuffer.extent();
                        }

                        render_pass_state = Some(RenderPassState {
                            contents: SubpassContents::Inline,
                            render_area_offset,
                            render_area_extent,
                            render_pass: BeginRenderPassState {
                                subpass: info.subpass.clone(),
                                framebuffer: info.framebuffer.clone(),
                            }
                            .into(),
                            view_mask: info.subpass.subpass_desc().view_mask,
                        });
                    }
                    CommandBufferInheritanceRenderPassType::BeginRendering(info) => {
                        render_pass_state = Some(RenderPassState {
                            contents: SubpassContents::Inline,
                            render_area_offset,
                            render_area_extent,
                            render_pass: BeginRenderingState {
                                attachments: None,
                                color_attachment_formats: info.color_attachment_formats.clone(),
                                depth_attachment_format: info.depth_attachment_format,
                                stencil_attachment_format: info.stencil_attachment_format,
                                pipeline_used: false,
                            }
                            .into(),
                            view_mask: info.view_mask,
                        });
                    }
                }
            }
        }

        let builder_alloc = allocator
            .allocate(queue_family_index, level, 1)?
            .next()
            .expect("requested one command buffer from the command pool, but got zero");

        let inner = SyncCommandBufferBuilder::new(builder_alloc.inner(), begin_info)?;

        Ok(AutoCommandBufferBuilder {
            inner,
            builder_alloc,
            queue_family_index,
            render_pass_state,
            query_state: HashMap::default(),
            inheritance_info,
            usage,
            _data: PhantomData,
        })
    }

    fn validate_begin(
        device: &Device,
        _queue_family_index: u32,
        level: CommandBufferLevel,
        begin_info: &CommandBufferBeginInfo,
    ) -> Result<(), CommandBufferBeginError> {
        let physical_device = device.physical_device();
        let properties = physical_device.properties();

        let &CommandBufferBeginInfo {
            usage: _,
            ref inheritance_info,
            _ne: _,
        } = &begin_info;

        if let Some(inheritance_info) = &inheritance_info {
            debug_assert!(level == CommandBufferLevel::Secondary);

            let &CommandBufferInheritanceInfo {
                ref render_pass,
                occlusion_query,
                query_statistics_flags,
                _ne: _,
            } = inheritance_info;

            if let Some(render_pass) = render_pass {
                // VUID-VkCommandBufferBeginInfo-flags-06000
                // VUID-VkCommandBufferBeginInfo-flags-06002
                // Ensured by the definition of the `CommandBufferInheritanceRenderPassType` enum.

                match render_pass {
                    CommandBufferInheritanceRenderPassType::BeginRenderPass(render_pass_info) => {
                        let &CommandBufferInheritanceRenderPassInfo {
                            ref subpass,
                            ref framebuffer,
                        } = render_pass_info;

                        // VUID-VkCommandBufferInheritanceInfo-commonparent
                        assert_eq!(device, subpass.render_pass().device().as_ref());

                        // VUID-VkCommandBufferBeginInfo-flags-06001
                        // Ensured by how the `Subpass` type is constructed.

                        if let Some(framebuffer) = framebuffer {
                            // VUID-VkCommandBufferInheritanceInfo-commonparent
                            assert_eq!(device, framebuffer.device().as_ref());

                            // VUID-VkCommandBufferBeginInfo-flags-00055
                            if !framebuffer
                                .render_pass()
                                .is_compatible_with(subpass.render_pass())
                            {
                                return Err(CommandBufferBeginError::FramebufferNotCompatible);
                            }
                        }
                    }
                    CommandBufferInheritanceRenderPassType::BeginRendering(rendering_info) => {
                        let &CommandBufferInheritanceRenderingInfo {
                            view_mask,
                            ref color_attachment_formats,
                            depth_attachment_format,
                            stencil_attachment_format,
                            rasterization_samples,
                        } = rendering_info;

                        // VUID-VkCommandBufferInheritanceRenderingInfo-multiview-06008
                        if view_mask != 0 && !device.enabled_features().multiview {
                            return Err(CommandBufferBeginError::RequirementNotMet {
                                required_for: "`inheritance_info.render_pass` is \
                                    `CommandBufferInheritanceRenderPassType::BeginRendering`, \
                                    where `view_mask` is not `0`",
                                requires_one_of: RequiresOneOf {
                                    features: &["multiview"],
                                    ..Default::default()
                                },
                            });
                        }

                        let view_count = u32::BITS - view_mask.leading_zeros();

                        // VUID-VkCommandBufferInheritanceRenderingInfo-viewMask-06009
                        if view_count > properties.max_multiview_view_count.unwrap_or(0) {
                            return Err(CommandBufferBeginError::MaxMultiviewViewCountExceeded {
                                view_count,
                                max: properties.max_multiview_view_count.unwrap_or(0),
                            });
                        }

                        for (attachment_index, format) in color_attachment_formats
                            .iter()
                            .enumerate()
                            .flat_map(|(i, f)| f.map(|f| (i, f)))
                        {
                            let attachment_index = attachment_index as u32;

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

                            // VUID-VkCommandBufferInheritanceRenderingInfo-pColorAttachmentFormats-06006
                            // Use unchecked, because all validation has been done above.
                            if !unsafe { physical_device.format_properties_unchecked(format) }
                                .potential_format_features()
                                .intersects(FormatFeatures::COLOR_ATTACHMENT)
                            {
                                return Err(
                                    CommandBufferBeginError::ColorAttachmentFormatUsageNotSupported {
                                        attachment_index,
                                    },
                                );
                            }
                        }

                        if let Some(format) = depth_attachment_format {
                            // VUID-VkCommandBufferInheritanceRenderingInfo-depthAttachmentFormat-parameter
                            format.validate_device(device)?;

                            // VUID-VkCommandBufferInheritanceRenderingInfo-depthAttachmentFormat-06540
                            if !format.aspects().intersects(ImageAspects::DEPTH) {
                                return Err(
                                    CommandBufferBeginError::DepthAttachmentFormatUsageNotSupported,
                                );
                            }

                            // VUID-VkCommandBufferInheritanceRenderingInfo-depthAttachmentFormat-06007
                            // Use unchecked, because all validation has been done above.
                            if !unsafe { physical_device.format_properties_unchecked(format) }
                                .potential_format_features()
                                .intersects(FormatFeatures::DEPTH_STENCIL_ATTACHMENT)
                            {
                                return Err(
                                    CommandBufferBeginError::DepthAttachmentFormatUsageNotSupported,
                                );
                            }
                        }

                        if let Some(format) = stencil_attachment_format {
                            // VUID-VkCommandBufferInheritanceRenderingInfo-stencilAttachmentFormat-parameter
                            format.validate_device(device)?;

                            // VUID-VkCommandBufferInheritanceRenderingInfo-stencilAttachmentFormat-06541
                            if !format.aspects().intersects(ImageAspects::STENCIL) {
                                return Err(
                                    CommandBufferBeginError::StencilAttachmentFormatUsageNotSupported,
                                );
                            }

                            // VUID-VkCommandBufferInheritanceRenderingInfo-stencilAttachmentFormat-06199
                            // Use unchecked, because all validation has been done above.
                            if !unsafe { physical_device.format_properties_unchecked(format) }
                                .potential_format_features()
                                .intersects(FormatFeatures::DEPTH_STENCIL_ATTACHMENT)
                            {
                                return Err(
                                    CommandBufferBeginError::StencilAttachmentFormatUsageNotSupported,
                                );
                            }
                        }

                        if let (Some(depth_format), Some(stencil_format)) =
                            (depth_attachment_format, stencil_attachment_format)
                        {
                            // VUID-VkCommandBufferInheritanceRenderingInfo-depthAttachmentFormat-06200
                            if depth_format != stencil_format {
                                return Err(
                                    CommandBufferBeginError::DepthStencilAttachmentFormatMismatch,
                                );
                            }
                        }

                        // VUID-VkCommandBufferInheritanceRenderingInfo-rasterizationSamples-parameter
                        rasterization_samples.validate_device(device)?;
                    }
                }
            }

            if let Some(control_flags) = occlusion_query {
                // VUID-VkCommandBufferInheritanceInfo-queryFlags-00057
                control_flags.validate_device(device)?;

                // VUID-VkCommandBufferInheritanceInfo-occlusionQueryEnable-00056
                // VUID-VkCommandBufferInheritanceInfo-queryFlags-02788
                if !device.enabled_features().inherited_queries {
                    return Err(CommandBufferBeginError::RequirementNotMet {
                        required_for: "`inheritance_info.occlusion_query` is `Some`",
                        requires_one_of: RequiresOneOf {
                            features: &["inherited_queries"],
                            ..Default::default()
                        },
                    });
                }

                // VUID-vkBeginCommandBuffer-commandBuffer-00052
                if control_flags.intersects(QueryControlFlags::PRECISE)
                    && !device.enabled_features().occlusion_query_precise
                {
                    return Err(CommandBufferBeginError::RequirementNotMet {
                        required_for: "`inheritance_info.occlusion_query` is \
                            `Some(control_flags)`, where `control_flags` contains \
                            `QueryControlFlags::PRECISE`",
                        requires_one_of: RequiresOneOf {
                            features: &["occlusion_query_precise"],
                            ..Default::default()
                        },
                    });
                }
            }

            // VUID-VkCommandBufferInheritanceInfo-pipelineStatistics-02789
            query_statistics_flags.validate_device(device)?;

            // VUID-VkCommandBufferInheritanceInfo-pipelineStatistics-00058
            if query_statistics_flags.count() > 0
                && !device.enabled_features().pipeline_statistics_query
            {
                return Err(CommandBufferBeginError::RequirementNotMet {
                    required_for: "`inheritance_info.query_statistics_flags` is not empty",
                    requires_one_of: RequiresOneOf {
                        features: &["pipeline_statistics_query"],
                        ..Default::default()
                    },
                });
            }
        } else {
            debug_assert!(level == CommandBufferLevel::Primary);

            // VUID-vkBeginCommandBuffer-commandBuffer-02840
            // Ensured by the definition of the `CommandBufferUsage` enum.
        }

        Ok(())
    }
}

/// Error that can happen when beginning recording of a command buffer.
#[derive(Clone, Copy, Debug)]
pub enum CommandBufferBeginError {
    /// Not enough memory.
    OomError(OomError),

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

    /// A color attachment has a format that does not support that usage.
    ColorAttachmentFormatUsageNotSupported { attachment_index: u32 },

    /// The depth attachment has a format that does not support that usage.
    DepthAttachmentFormatUsageNotSupported,

    /// The depth and stencil attachments have different formats.
    DepthStencilAttachmentFormatMismatch,

    /// The framebuffer is not compatible with the render pass.
    FramebufferNotCompatible,

    /// The `max_multiview_view_count` limit has been exceeded.
    MaxMultiviewViewCountExceeded { view_count: u32, max: u32 },

    /// The stencil attachment has a format that does not support that usage.
    StencilAttachmentFormatUsageNotSupported,
}

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

impl Display for CommandBufferBeginError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "not enough memory available"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),
            Self::ColorAttachmentFormatUsageNotSupported { attachment_index } => write!(
                f,
                "color attachment {} has a format that does not support that usage",
                attachment_index,
            ),
            Self::DepthAttachmentFormatUsageNotSupported => write!(
                f,
                "the depth attachment has a format that does not support that usage",
            ),
            Self::DepthStencilAttachmentFormatMismatch => write!(
                f,
                "the depth and stencil attachments have different formats",
            ),
            Self::FramebufferNotCompatible => {
                write!(f, "the framebuffer is not compatible with the render pass")
            }
            Self::MaxMultiviewViewCountExceeded { .. } => {
                write!(f, "the `max_multiview_view_count` limit has been exceeded")
            }
            Self::StencilAttachmentFormatUsageNotSupported => write!(
                f,
                "the stencil attachment has a format that does not support that usage",
            ),
        }
    }
}

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

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

impl<A> AutoCommandBufferBuilder<PrimaryAutoCommandBuffer<A::Alloc>, A>
where
    A: CommandBufferAllocator,
{
    /// Builds the command buffer.
    pub fn build(self) -> Result<PrimaryAutoCommandBuffer<A::Alloc>, BuildError> {
        if self.render_pass_state.is_some() {
            return Err(BuildError::RenderPassActive);
        }

        if !self.query_state.is_empty() {
            return Err(BuildError::QueryActive);
        }

        Ok(PrimaryAutoCommandBuffer {
            inner: self.inner.build()?,
            _alloc: self.builder_alloc.into_alloc(),
            usage: self.usage,

            state: Mutex::new(Default::default()),
        })
    }
}

impl<A> AutoCommandBufferBuilder<SecondaryAutoCommandBuffer<A::Alloc>, A>
where
    A: CommandBufferAllocator,
{
    /// Builds the command buffer.
    pub fn build(self) -> Result<SecondaryAutoCommandBuffer<A::Alloc>, BuildError> {
        if !self.query_state.is_empty() {
            return Err(BuildError::QueryActive);
        }

        let submit_state = match self.usage {
            CommandBufferUsage::MultipleSubmit => SubmitState::ExclusiveUse {
                in_use: AtomicBool::new(false),
            },
            CommandBufferUsage::SimultaneousUse => SubmitState::Concurrent,
            CommandBufferUsage::OneTimeSubmit => SubmitState::OneTime {
                already_submitted: AtomicBool::new(false),
            },
        };

        Ok(SecondaryAutoCommandBuffer {
            inner: self.inner.build()?,
            _alloc: self.builder_alloc.into_alloc(),
            usage: self.usage,
            inheritance_info: self.inheritance_info.unwrap(),
            submit_state,
        })
    }
}

/// Error that can happen when building a command buffer.
#[derive(Clone, Debug)]
pub enum BuildError {
    OomError(OomError),

    /// A render pass is still active on the command buffer.
    RenderPassActive,

    /// A query is still active on the command buffer.
    QueryActive,
}

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

impl Display for BuildError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "out of memory"),
            Self::RenderPassActive => {
                write!(f, "a render pass is still active on the command buffer")
            }
            Self::QueryActive => write!(f, "a query is still active on the command buffer"),
        }
    }
}

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

impl<L, A> AutoCommandBufferBuilder<L, A>
where
    A: CommandBufferAllocator,
{
    pub(super) fn queue_family_properties(&self) -> &QueueFamilyProperties {
        &self.device().physical_device().queue_family_properties()[self.queue_family_index as usize]
    }

    /// Returns the binding/setting state.
    pub fn state(&self) -> CommandBufferBuilderState<'_> {
        self.inner.state()
    }
}

unsafe impl<L, A> DeviceOwned for AutoCommandBufferBuilder<L, A>
where
    A: CommandBufferAllocator,
{
    fn device(&self) -> &Arc<Device> {
        self.inner.device()
    }
}

pub struct PrimaryAutoCommandBuffer<A = StandardCommandBufferAlloc> {
    inner: SyncCommandBuffer,
    _alloc: A, // Safety: must be dropped after `inner`
    usage: CommandBufferUsage,

    state: Mutex<CommandBufferState>,
}

unsafe impl<A> DeviceOwned for PrimaryAutoCommandBuffer<A> {
    fn device(&self) -> &Arc<Device> {
        self.inner.device()
    }
}

unsafe impl<A> VulkanObject for PrimaryAutoCommandBuffer<A> {
    type Handle = ash::vk::CommandBuffer;

    fn handle(&self) -> Self::Handle {
        self.inner.as_ref().handle()
    }
}

unsafe impl<A> PrimaryCommandBufferAbstract for PrimaryAutoCommandBuffer<A>
where
    A: CommandBufferAlloc,
{
    fn usage(&self) -> CommandBufferUsage {
        self.usage
    }

    fn state(&self) -> MutexGuard<'_, CommandBufferState> {
        self.state.lock()
    }

    fn resources_usage(&self) -> &CommandBufferResourcesUsage {
        self.inner.resources_usage()
    }
}

pub struct SecondaryAutoCommandBuffer<A = StandardCommandBufferAlloc> {
    inner: SyncCommandBuffer,
    _alloc: A, // Safety: must be dropped after `inner`
    usage: CommandBufferUsage,
    inheritance_info: CommandBufferInheritanceInfo,

    // Tracks usage of the command buffer on the GPU.
    submit_state: SubmitState,
}

unsafe impl<A> VulkanObject for SecondaryAutoCommandBuffer<A> {
    type Handle = ash::vk::CommandBuffer;

    fn handle(&self) -> Self::Handle {
        self.inner.as_ref().handle()
    }
}

unsafe impl<A> DeviceOwned for SecondaryAutoCommandBuffer<A> {
    fn device(&self) -> &Arc<Device> {
        self.inner.device()
    }
}

unsafe impl<A> SecondaryCommandBufferAbstract for SecondaryAutoCommandBuffer<A>
where
    A: CommandBufferAlloc,
{
    fn usage(&self) -> CommandBufferUsage {
        self.usage
    }

    fn inheritance_info(&self) -> &CommandBufferInheritanceInfo {
        &self.inheritance_info
    }

    fn lock_record(&self) -> Result<(), CommandBufferExecError> {
        match self.submit_state {
            SubmitState::OneTime {
                ref already_submitted,
            } => {
                let was_already_submitted = already_submitted.swap(true, Ordering::SeqCst);
                if was_already_submitted {
                    return Err(CommandBufferExecError::OneTimeSubmitAlreadySubmitted);
                }
            }
            SubmitState::ExclusiveUse { ref in_use } => {
                let already_in_use = in_use.swap(true, Ordering::SeqCst);
                if already_in_use {
                    return Err(CommandBufferExecError::ExclusiveAlreadyInUse);
                }
            }
            SubmitState::Concurrent => (),
        };

        Ok(())
    }

    unsafe fn unlock(&self) {
        match self.submit_state {
            SubmitState::OneTime {
                ref already_submitted,
            } => {
                debug_assert!(already_submitted.load(Ordering::SeqCst));
            }
            SubmitState::ExclusiveUse { ref in_use } => {
                let old_val = in_use.swap(false, Ordering::SeqCst);
                debug_assert!(old_val);
            }
            SubmitState::Concurrent => (),
        };
    }

    fn resources_usage(&self) -> &SecondaryCommandBufferResourcesUsage {
        self.inner.secondary_resources_usage()
    }
}

// Whether the command buffer can be submitted.
#[derive(Debug)]
enum SubmitState {
    // The command buffer was created with the "SimultaneousUse" flag. Can always be submitted at
    // any time.
    Concurrent,

    // The command buffer can only be submitted once simultaneously.
    ExclusiveUse {
        // True if the command buffer is current in use by the GPU.
        in_use: AtomicBool,
    },

    // The command buffer can only ever be submitted once.
    OneTime {
        // True if the command buffer has already been submitted once and can be no longer be
        // submitted.
        already_submitted: AtomicBool,
    },
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        buffer::{Buffer, BufferAllocateInfo, BufferUsage},
        command_buffer::{
            synced::SyncCommandBufferBuilderError, BufferCopy, CopyBufferInfoTyped, CopyError,
            ExecuteCommandsError,
        },
        device::{DeviceCreateInfo, QueueCreateInfo},
        memory::allocator::StandardMemoryAllocator,
        sync::GpuFuture,
    };

    #[test]
    fn copy_buffer_dimensions() {
        let instance = instance!();

        let physical_device = match instance.enumerate_physical_devices().unwrap().next() {
            Some(p) => p,
            None => return,
        };

        let (device, mut queues) = Device::new(
            physical_device,
            DeviceCreateInfo {
                queue_create_infos: vec![QueueCreateInfo {
                    queue_family_index: 0,
                    ..Default::default()
                }],
                ..Default::default()
            },
        )
        .unwrap();

        let queue = queues.next().unwrap();
        let memory_allocator = StandardMemoryAllocator::new_default(device.clone());

        let source = Buffer::from_iter(
            &memory_allocator,
            BufferAllocateInfo {
                buffer_usage: BufferUsage::TRANSFER_SRC,
                ..Default::default()
            },
            [1_u32, 2].iter().copied(),
        )
        .unwrap();

        let destination = Buffer::from_iter(
            &memory_allocator,
            BufferAllocateInfo {
                buffer_usage: BufferUsage::TRANSFER_DST,
                ..Default::default()
            },
            [0_u32, 10, 20, 3, 4].iter().copied(),
        )
        .unwrap();

        let cb_allocator = StandardCommandBufferAllocator::new(device, Default::default());
        let mut cbb = AutoCommandBufferBuilder::primary(
            &cb_allocator,
            queue.queue_family_index(),
            CommandBufferUsage::OneTimeSubmit,
        )
        .unwrap();

        cbb.copy_buffer(CopyBufferInfoTyped {
            regions: [BufferCopy {
                src_offset: 0,
                dst_offset: 1,
                size: 2,
                ..Default::default()
            }]
            .into(),
            ..CopyBufferInfoTyped::buffers(source, destination.clone())
        })
        .unwrap();

        let cb = cbb.build().unwrap();

        let future = cb
            .execute(queue)
            .unwrap()
            .then_signal_fence_and_flush()
            .unwrap();
        future.wait(None).unwrap();

        let result = destination.read().unwrap();

        assert_eq!(*result, [0_u32, 1, 2, 3, 4]);
    }

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

        let cb_allocator = StandardCommandBufferAllocator::new(device, Default::default());

        // Make a secondary CB that doesn't support simultaneous use.
        let builder = AutoCommandBufferBuilder::secondary(
            &cb_allocator,
            queue.queue_family_index(),
            CommandBufferUsage::MultipleSubmit,
            Default::default(),
        )
        .unwrap();
        let secondary = Arc::new(builder.build().unwrap());

        {
            let mut builder = AutoCommandBufferBuilder::primary(
                &cb_allocator,
                queue.queue_family_index(),
                CommandBufferUsage::SimultaneousUse,
            )
            .unwrap();

            // Add the secondary a first time
            builder.execute_commands(secondary.clone()).unwrap();

            // Recording the same non-concurrent secondary command buffer twice into the same
            // primary is an error.
            assert!(matches!(
                builder.execute_commands(secondary.clone()),
                Err(ExecuteCommandsError::SyncCommandBufferBuilderError(
                    SyncCommandBufferBuilderError::ExecError(
                        CommandBufferExecError::ExclusiveAlreadyInUse
                    )
                ))
            ));
        }

        {
            let mut builder = AutoCommandBufferBuilder::primary(
                &cb_allocator,
                queue.queue_family_index(),
                CommandBufferUsage::SimultaneousUse,
            )
            .unwrap();
            builder.execute_commands(secondary.clone()).unwrap();
            let cb1 = builder.build().unwrap();

            let mut builder = AutoCommandBufferBuilder::primary(
                &cb_allocator,
                queue.queue_family_index(),
                CommandBufferUsage::SimultaneousUse,
            )
            .unwrap();

            // Recording the same non-concurrent secondary command buffer into multiple
            // primaries is an error.
            assert!(matches!(
                builder.execute_commands(secondary.clone()),
                Err(ExecuteCommandsError::SyncCommandBufferBuilderError(
                    SyncCommandBufferBuilderError::ExecError(
                        CommandBufferExecError::ExclusiveAlreadyInUse
                    )
                ))
            ));

            std::mem::drop(cb1);

            // Now that the first cb is dropped, we should be able to record.
            builder.execute_commands(secondary).unwrap();
        }
    }

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

        let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
        let source = Buffer::from_iter(
            &memory_allocator,
            BufferAllocateInfo {
                buffer_usage: BufferUsage::TRANSFER_SRC | BufferUsage::TRANSFER_DST,
                ..Default::default()
            },
            [0_u32, 1, 2, 3].iter().copied(),
        )
        .unwrap();

        let cb_allocator = StandardCommandBufferAllocator::new(device, Default::default());
        let mut builder = AutoCommandBufferBuilder::primary(
            &cb_allocator,
            queue.queue_family_index(),
            CommandBufferUsage::OneTimeSubmit,
        )
        .unwrap();

        builder
            .copy_buffer(CopyBufferInfoTyped {
                regions: [BufferCopy {
                    src_offset: 0,
                    dst_offset: 2,
                    size: 2,
                    ..Default::default()
                }]
                .into(),
                ..CopyBufferInfoTyped::buffers(source.clone(), source.clone())
            })
            .unwrap();

        let cb = builder.build().unwrap();

        let future = cb
            .execute(queue)
            .unwrap()
            .then_signal_fence_and_flush()
            .unwrap();
        future.wait(None).unwrap();

        let result = source.read().unwrap();

        assert_eq!(*result, [0_u32, 1, 0, 1]);
    }

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

        let memory_allocator = StandardMemoryAllocator::new_default(device.clone());
        let source = Buffer::from_iter(
            &memory_allocator,
            BufferAllocateInfo {
                buffer_usage: BufferUsage::TRANSFER_SRC | BufferUsage::TRANSFER_DST,
                ..Default::default()
            },
            [0_u32, 1, 2, 3].iter().copied(),
        )
        .unwrap();

        let cb_allocator = StandardCommandBufferAllocator::new(device, Default::default());
        let mut builder = AutoCommandBufferBuilder::primary(
            &cb_allocator,
            queue.queue_family_index(),
            CommandBufferUsage::OneTimeSubmit,
        )
        .unwrap();

        assert!(matches!(
            builder.copy_buffer(CopyBufferInfoTyped {
                regions: [BufferCopy {
                    src_offset: 0,
                    dst_offset: 1,
                    size: 2,
                    ..Default::default()
                }]
                .into(),
                ..CopyBufferInfoTyped::buffers(source.clone(), source)
            }),
            Err(CopyError::OverlappingRegions {
                src_region_index: 0,
                dst_region_index: 0,
            })
        ));
    }
}
