Documentation
// dacho/src/renderer/images/image.rs

// crates
use {
    anyhow::Result,
    ash::vk
};

// crate
use crate::{
    renderer::{
        commands::CommandPool,
        devices::{Device, PhysicalDevice},
        setup::Instance,
        VulkanDrop
    },
    fatal
};

pub struct Image {
    pub raw:    vk::Image,
        memory: vk::DeviceMemory
}

impl Image {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        device:          &Device,
        instance:        &Instance,
        physical_device: &PhysicalDevice,
        extent_2d:        vk::Extent2D,
        format:           vk::Format,
        usage:            vk::ImageUsageFlags,
        properties:       vk::MemoryPropertyFlags,
        samples:          vk::SampleCountFlags
    ) -> Result<Self> {
        let extent = vk::Extent3D::builder()
            .width(extent_2d.width)
            .height(extent_2d.height)
            .depth(1)
            .build();

        let create_info = vk::ImageCreateInfo::builder()
            .extent(extent)
            .format(format)
            .usage(usage)
            .image_type(vk::ImageType::TYPE_2D)
            .mip_levels(1)
            .array_layers(1)
            .tiling(vk::ImageTiling::OPTIMAL)
            .initial_layout(vk::ImageLayout::UNDEFINED)
            .samples(samples)
            .sharing_mode(vk::SharingMode::EXCLUSIVE);

        let raw = unsafe { device.raw.create_image(&create_info, None) }?;

        let memory_requirements = unsafe { device.raw.get_image_memory_requirements(raw) };
        let memory_properties   = unsafe { instance.raw.get_physical_device_memory_properties(physical_device.raw) };

        let memory_type_index = {
            let mut found  = false;
            let mut result = 0;

            for i in 0..memory_properties.memory_type_count {
                let req = (memory_requirements.memory_type_bits & (1 << i)) != 0;
                let mem = (memory_properties.memory_types[i as usize].property_flags & properties) == properties;

                if req && mem {
                    found  = true;
                    result = i;
                    break;
                }
            }
            
            if !found {
                fatal!("Failed to find a suitable memory type");
            }

            result
        };

        let allocate_info = vk::MemoryAllocateInfo::builder()
            .allocation_size(memory_requirements.size)
            .memory_type_index(memory_type_index);

        let memory = unsafe { device.raw.allocate_memory(&allocate_info, None) }?;

        unsafe { device.raw.bind_image_memory(raw, memory, 0) }?;

        Ok(Self { raw, memory })
    }

    #[allow(dead_code)]
    pub fn transition_layout(
        &self,
        device:       &Device,
        command_pool: &CommandPool,
        old_layout:    vk::ImageLayout,
        new_layout:    vk::ImageLayout
    ) -> Result<()> {
        let command_buffer = command_pool.begin_single_time_commands(device)?;

        let (src_am, dst_am, src_stage, dst_stage) = match (old_layout, new_layout) {
            (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => {
                (
                    vk::AccessFlags::empty(),
                    vk::AccessFlags::TRANSFER_WRITE,
                    vk::PipelineStageFlags::TOP_OF_PIPE,
                    vk::PipelineStageFlags::TRANSFER
                )
            },
            (vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) => {
                (
                    vk::AccessFlags::TRANSFER_WRITE,
                    vk::AccessFlags::SHADER_READ,
                    vk::PipelineStageFlags::TRANSFER,
                    vk::PipelineStageFlags::FRAGMENT_SHADER
                )
            },
            _ => { fatal!("Invalid vk::ImageLayout transition"); }
        };

        let subresource_range = vk::ImageSubresourceRange::builder()
            .aspect_mask(vk::ImageAspectFlags::COLOR)
            .base_mip_level(0)
            .level_count(1)
            .base_array_layer(0)
            .layer_count(1)
            .build();

        let barrier = vk::ImageMemoryBarrier::builder()
            .old_layout(old_layout)
            .new_layout(new_layout)
            .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
            .image(self.raw)
            .subresource_range(subresource_range)
            .src_access_mask(src_am)
            .dst_access_mask(dst_am);

        unsafe {
            device.raw.cmd_pipeline_barrier(
                command_buffer,
                src_stage,
                dst_stage,
                vk::DependencyFlags::empty(),
                &[],
                &[],
                &[*barrier]
            );
        }

        command_pool.end_single_time_commands(device, command_buffer)?;

        Ok(())
    }
}

impl VulkanDrop for Image {
    fn drop(&self, device: &Device) {
        unsafe {
            device.raw.destroy_image(self.raw, None);
            device.raw.free_memory(self.memory, None);
        }
    }
}