use ash::vk::{self};

use crate::{Game, InWorld, RenderCtx, Window};

use super::{
    SwapchainCtx,
    loading_world::{Component, WorldComponent as _},
    swapchain::{
        make_depth_img, make_depth_view, make_framebufs, make_swap_images, make_swap_views,
        setup_swapchain,
    },
};

impl Game<InWorld> {
    pub fn window_resize(&mut self, size: (u32, u32)) {
        let Self {
            state:
                InWorld {
                    sdl_context,
                    window,
                    ctx,
                    skybox,
                    components,
                    sem_avail,
                    sem_finish,
                    fence_flight,
                    swapchain:
                        SwapchainCtx {
                            surface_loader,
                            swapchain_loader,
                            swap_images,
                            swap_views,
                            depth_mem,
                            depth_image,
                            depth_view,
                            framebufs,
                            swapchain,
                        },
                    pass,
                    camera,
                },
        } = self;

        window.width = size.0;
        window.height = size.1;

        unsafe {
            ctx.dev
                .device_wait_idle()
                .expect("Failed to wait for device idle");
            swapchain_loader.destroy_swapchain(*swapchain, None);
        }
        (*swapchain, _) = setup_swapchain(
            &ctx,
            &surface_loader,
            &swapchain_loader,
            window.width,
            window.height,
        );
        *swap_images = make_swap_images(*swapchain, swapchain_loader);
        *swap_views = make_swap_views(&ctx, &swap_images, vk::Format::B8G8R8A8_UNORM);
        *depth_image = make_depth_img(&ctx, window.width, window.height, *depth_mem);
        *depth_view = make_depth_view(&ctx, *depth_image, vk::Format::D32_SFLOAT);
        *framebufs = make_framebufs(
            &ctx,
            &swap_views,
            &depth_view,
            &pass,
            window.width,
            window.height,
        );
    }

    pub fn render_frame(&self) {
        let Self {
            state:
                InWorld {
                    sdl_context,
                    window,
                    ctx,
                    skybox,
                    components,
                    sem_avail,
                    sem_finish,
                    fence_flight,
                    swapchain,
                    pass,
                    camera,
                },
        } = self;

        ctx.wait_for_fence(*fence_flight);
        ctx.reset_fence(*fence_flight);

        let img_idx = ack_next_img(swapchain, sem_avail);

        ctx.start_cmd_buf();

        self.record_commands(img_idx as usize);

        ctx.end_cmd_buf();

        ctx.submit_queue(*sem_avail, *sem_finish, *fence_flight);

        ctx.queue_present(swapchain, img_idx, *sem_finish);
    }

    fn record_commands(&self, img_idx: usize) {
        let Self {
            state:
                InWorld {
                    sdl_context,
                    window: Window { width, height, .. },
                    ctx: RenderCtx { dev, cmd_buf, .. },
                    skybox,
                    components,
                    swapchain,
                    pass,
                    camera,
                    ..
                },
        } = self;

        let clear_vals = [
            vk::ClearValue {
                color: vk::ClearColorValue {
                    float32: [0.0, 0.0, 0.0, 1.0],
                },
            },
            vk::ClearValue {
                depth_stencil: vk::ClearDepthStencilValue {
                    depth: 1.0,
                    stencil: 0,
                },
            },
        ];

        let render_pass_info = vk::RenderPassBeginInfo::default()
            .render_pass(*pass)
            .framebuffer(swapchain.framebufs[img_idx])
            .render_area(
                vk::Rect2D::default()
                    .offset(vk::Offset2D::default())
                    .extent(vk::Extent2D {
                        width: *width,
                        height: *height,
                    }),
            )
            .clear_values(&clear_vals);

        unsafe {
            let regions = (0..6)
                .map(|i| {
                    vk::BufferImageCopy::default()
                        .buffer_offset(3 * 2048 * 2048 * i)
                        .buffer_row_length(0)
                        .buffer_image_height(0)
                        .image_offset(vk::Offset3D::default())
                        .image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1))
                        .image_subresource(
                            vk::ImageSubresourceLayers::default()
                                .aspect_mask(vk::ImageAspectFlags::COLOR)
                                .mip_level(0)
                                .layer_count(1)
                                .base_array_layer(i as u32),
                        )
                })
                .collect::<Vec<_>>();
            dev.cmd_copy_buffer_to_image(
                *cmd_buf,
                skybox.0.buf,
                skybox.0.image,
                vk::ImageLayout::GENERAL,
                &regions,
            );
            dev.cmd_begin_render_pass(*cmd_buf, &render_pass_info, vk::SubpassContents::INLINE);

            let base_color = [0.0, 1.0, 1.0, 1.0];

            for Component { inner, desc_set } in components {
                let (pipe, layout) = inner.pipeline();
                dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe);

                let desc_sets = [*desc_set];

                dev.cmd_bind_descriptor_sets(
                    *cmd_buf,
                    vk::PipelineBindPoint::GRAPHICS,
                    layout,
                    0,
                    desc_sets.as_ref(),
                    &[],
                );

                dev.cmd_set_viewport_with_count(
                    *cmd_buf,
                    &[vk::Viewport::default()
                        .width(*width as f32)
                        .height(*height as f32)],
                );
                dev.cmd_set_scissor_with_count(
                    *cmd_buf,
                    &[vk::Rect2D::default().extent(vk::Extent2D {
                        width: *width,
                        height: *height,
                    })],
                );

                inner
                    .push_constants(camera, [*width, *height], base_color)
                    .into_iter()
                    .for_each(|(f, o, d)| {
                        dev.cmd_push_constants(*cmd_buf, layout, f, o, d.as_ref());
                    });

                dev.cmd_draw(*cmd_buf, 36, 1, 0, 0);
            }

            {
                let (pipe, layout) = skybox.0.pipeline();
                dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe);

                let desc_sets = [skybox.1];

                dev.cmd_bind_descriptor_sets(
                    *cmd_buf,
                    vk::PipelineBindPoint::GRAPHICS,
                    layout,
                    0,
                    desc_sets.as_ref(),
                    &[],
                );

                dev.cmd_set_viewport_with_count(
                    *cmd_buf,
                    &[vk::Viewport::default()
                        .width(*width as f32)
                        .height(*height as f32)],
                );
                dev.cmd_set_scissor_with_count(
                    *cmd_buf,
                    &[vk::Rect2D::default().extent(vk::Extent2D {
                        width: *width,
                        height: *height,
                    })],
                );

                skybox
                    .0
                    .push_constants(camera, [*width, *height], base_color)
                    .into_iter()
                    .for_each(|(f, o, d)| {
                        dev.cmd_push_constants(
                            *cmd_buf,
                            layout,
                            vk::ShaderStageFlags::VERTEX,
                            0,
                            d.as_ref(),
                        );
                    });

                dev.cmd_draw(*cmd_buf, 36, 1, 0, 0);
            }

            dev.cmd_end_render_pass(*cmd_buf);
        }
    }
}

fn ack_next_img(swapchain: &SwapchainCtx, sem_avail: &vk::Semaphore) -> u32 {
    unsafe {
        let (img_inx, _) = swapchain
            .swapchain_loader
            .acquire_next_image(swapchain.swapchain, u64::MAX, *sem_avail, vk::Fence::null())
            .expect("Failed to acquire next image");
        img_inx
    }
}
