use std::cmp;

use ash::{
    khr::{surface, swapchain as khr_swapchain},
    vk,
};

use crate::{
    RenderCtx,
    render::{MAX_HEIGHT, MAX_WIDTH},
};

pub fn setup_swapchain(
    ctx: &RenderCtx,
    surface_loader: &surface::Instance,
    swapchain_loader: &khr_swapchain::Device,
    width: u32,
    height: u32,
) -> (vk::SwapchainKHR, vk::Extent2D) {
    let caps = unsafe {
        surface_loader
            .get_physical_device_surface_capabilities(ctx.pdev, ctx.surface)
            .expect("Failed to get surface capabilities")
    };

    let formats = unsafe {
        surface_loader
            .get_physical_device_surface_formats(ctx.pdev, ctx.surface)
            .expect("Failed to get surface formats")
    };

    let format = (formats)
        .iter()
        .filter(|f| {
            f.format == vk::Format::B8G8R8A8_UNORM
                && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
        })
        .next()
        .expect("No suitable format found");

    // When the window is resized fast, then the caps goes out of sync of the target extents
    // Thus we are using the passed-in (target) extents instead of the cap extents.
    // assert_eq!(caps.current_extent.width, width);
    // assert_eq!(caps.current_extent.height, height);

    let swap_create = vk::SwapchainCreateInfoKHR::default()
        .surface(ctx.surface)
        .min_image_count(caps.min_image_count)
        .image_format(format.format)
        .image_color_space(format.color_space)
        .image_extent(vk::Extent2D {
            width: cmp::min(cmp::max(caps.current_extent.width, width), MAX_WIDTH),
            height: cmp::min(cmp::max(caps.current_extent.height, height), MAX_HEIGHT),
        })
        .image_array_layers(1)
        .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
        .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
        .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
        .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
        .present_mode(vk::PresentModeKHR::MAILBOX)
        .clipped(true);

    let swapchain = unsafe {
        swapchain_loader
            .create_swapchain(&swap_create, None)
            .expect("Failed to create swapchain")
    };
    println!("Created swapchain");
    (swapchain, caps.current_extent)
}

pub fn make_swap_images(
    swapchain: vk::SwapchainKHR,
    swapchain_loader: &khr_swapchain::Device,
) -> Vec<vk::Image> {
    unsafe {
        let images = swapchain_loader
            .get_swapchain_images(swapchain)
            .expect("Failed to get swapchain images");
        println!("Fetched swapchain images");
        images
    }
}

pub fn make_swap_views(
    ctx: &RenderCtx,
    swap_images: &[vk::Image],
    format: vk::Format,
) -> Vec<vk::ImageView> {
    let mut views = Vec::new();
    for img in swap_images.iter() {
        let view_info = vk::ImageViewCreateInfo::default()
            .image(*img)
            .view_type(vk::ImageViewType::TYPE_2D)
            .format(format)
            .components(vk::ComponentMapping::default())
            .subresource_range(
                vk::ImageSubresourceRange::default()
                    .aspect_mask(vk::ImageAspectFlags::COLOR)
                    .base_mip_level(0)
                    .level_count(1)
                    .base_array_layer(0)
                    .layer_count(1),
            );
        let view = unsafe {
            ctx.dev
                .create_image_view(&view_info, None)
                .expect("Failed to create image view")
        };
        views.push(view);
    }
    views
}

pub fn make_depth_img(
    ctx: &RenderCtx,
    width: u32,
    height: u32,
    mem: vk::DeviceMemory,
) -> vk::Image {
    let queue_fams = [ctx.queue_idx];
    let img_info = vk::ImageCreateInfo::default()
        .flags(vk::ImageCreateFlags::empty())
        .image_type(vk::ImageType::TYPE_2D)
        .format(vk::Format::D32_SFLOAT)
        .extent(vk::Extent3D {
            width,
            height,
            depth: 1,
        })
        .mip_levels(1)
        .array_layers(1)
        .samples(vk::SampleCountFlags::TYPE_1)
        .tiling(vk::ImageTiling::OPTIMAL)
        .usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
        .sharing_mode(vk::SharingMode::EXCLUSIVE)
        .queue_family_indices(&queue_fams)
        .initial_layout(vk::ImageLayout::UNDEFINED);

    let img = unsafe {
        ctx.dev
            .create_image(&img_info, None)
            .expect("Failed to create image")
    };
    println!("Created image");

    unsafe {
        ctx.dev
            .bind_image_memory(img, mem, 0)
            .expect("Failed to bind image memory")
    };

    img
}

pub fn make_depth_view(ctx: &RenderCtx, depth_img: vk::Image, format: vk::Format) -> vk::ImageView {
    let view_info = vk::ImageViewCreateInfo::default()
        .image(depth_img)
        .view_type(vk::ImageViewType::TYPE_2D)
        .format(format)
        .components(vk::ComponentMapping::default())
        .subresource_range(
            vk::ImageSubresourceRange::default()
                .aspect_mask(vk::ImageAspectFlags::DEPTH)
                .base_mip_level(0)
                .level_count(1)
                .base_array_layer(0)
                .layer_count(1),
        );
    let view = unsafe {
        ctx.dev
            .create_image_view(&view_info, None)
            .expect("Failed to create image view")
    };
    view
}

pub fn make_framebufs(
    ctx: &RenderCtx,
    swap_views: &Vec<vk::ImageView>,
    depth_view: &vk::ImageView,
    pass: &vk::RenderPass,
    width: u32,
    height: u32,
) -> Vec<vk::Framebuffer> {
    let mut framebufs = Vec::new();
    for view in swap_views.iter() {
        let attachments = [*view, *depth_view];
        let framebuf_info = vk::FramebufferCreateInfo::default()
            .render_pass(*pass)
            .attachments(attachments.as_ref())
            .width(width)
            .height(height)
            .layers(1);
        let framebuf = unsafe {
            ctx.dev
                .create_framebuffer(&framebuf_info, None)
                .expect("Failed to create framebuffer")
        };
        framebufs.push(framebuf);
    }
    framebufs
}
