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

use crate::RenderCtx;

mod cube;
pub mod in_world;
mod init;
pub mod loading_world;
pub mod skybox;
mod swapchain;

const MAX_WIDTH: u32 = 3440;
const MAX_HEIGHT: u32 = 1440;

pub struct SwapchainCtx {
    surface_loader: surface::Instance,
    swapchain_loader: khr_swapchain::Device,
    swap_images: Vec<vk::Image>,
    swap_views: Vec<vk::ImageView>,
    depth_mem: vk::DeviceMemory,
    depth_image: vk::Image,
    depth_view: vk::ImageView,
    framebufs: Vec<vk::Framebuffer>,
    swapchain: vk::SwapchainKHR,
}

pub enum MemType {
    HostVisibile,
    HostInvisible,
}

impl RenderCtx {
    pub fn mem_alloc(&self, typ: MemType, size: u64) -> vk::DeviceMemory {
        let mem_info = vk::MemoryAllocateInfo::default()
            .allocation_size(size)
            .memory_type_index(match typ {
                MemType::HostVisibile => self.host_vis_idx,
                MemType::HostInvisible => self.host_invis_idx,
            });
        let mem = unsafe { self.dev.allocate_memory(&mem_info, None) }
            .expect("Failed to allocate memory");
        println!("Allocated memory");
        mem
    }

    pub fn alloc_buf(
        &self,
        mem: vk::DeviceMemory,
        size: vk::DeviceSize,
        usage: vk::BufferUsageFlags,
    ) -> vk::Buffer {
        unsafe {
            let buf_info = vk::BufferCreateInfo::default()
                .size(size)
                .usage(usage)
                .sharing_mode(vk::SharingMode::EXCLUSIVE);
            let buf = self
                .dev
                .create_buffer(&buf_info, None)
                .expect("Failed to create buffer");

            self.dev
                .bind_buffer_memory(buf, mem, 0)
                .expect("Failed to bind buffer memory");
            println!("Created buffer");
            buf
        }
    }

    pub fn buf_to_ptr(&self, mem: vk::DeviceMemory, buf: vk::Buffer) -> *mut std::ffi::c_void {
        let ptr = unsafe {
            self.dev
                .map_memory(mem, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
        }
        .expect("Failed to map memory");
        println!("Mapped memory");
        ptr
    }

    pub fn shader_mod_from_file(
        &self,
        file: &str,
        flags: vk::ShaderModuleCreateFlags,
    ) -> vk::ShaderModule {
        let shader_bin = std::fs::read(file).unwrap();

        unsafe {
            self.dev.create_shader_module(
                &vk::ShaderModuleCreateInfo {
                    code_size: shader_bin.len(),
                    p_code: shader_bin.as_ptr() as *const u32,
                    flags,
                    ..Default::default()
                },
                None,
            )
        }
        .expect(&format!("Failed to create shader module form file {file}"))
    }

    fn setup_simple_shader_stage(
        &self,
        vert_shader: vk::ShaderModule,
        frag_shader: vk::ShaderModule,
    ) -> [vk::PipelineShaderStageCreateInfo; 2] {
        let vert_stage = vk::PipelineShaderStageCreateInfo::default()
            .stage(vk::ShaderStageFlags::VERTEX)
            .module(vert_shader)
            .name(c"main");
        let frag_stage = vk::PipelineShaderStageCreateInfo::default()
            .stage(vk::ShaderStageFlags::FRAGMENT)
            .module(frag_shader)
            .name(c"main");
        [vert_stage, frag_stage]
    }

    fn make_sem(&self) -> vk::Semaphore {
        let sem_info = vk::SemaphoreCreateInfo::default();
        let sem = unsafe {
            self.dev
                .create_semaphore(&sem_info, None)
                .expect("Failed to create semaphore")
        };
        sem
    }

    fn make_fence(&self) -> vk::Fence {
        let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
        let fence = unsafe {
            self.dev
                .create_fence(&fence_info, None)
                .expect("Failed to create fence")
        };
        fence
    }

    fn wait_for_fence(&self, fence: vk::Fence) {
        unsafe {
            let fence = [fence];
            self.dev
                .wait_for_fences(&fence, true, u64::MAX)
                .expect("Failed to wait for fence");
        }
    }

    fn reset_fence(&self, fence: vk::Fence) {
        unsafe {
            let fence = [fence];
            self.dev
                .reset_fences(&fence)
                .expect("Failed to reset fence");
        }
    }

    fn start_cmd_buf(&self) {
        unsafe {
            self.dev
                .reset_command_buffer(self.cmd_buf, vk::CommandBufferResetFlags::empty())
                .expect("Failed to reset command buffer");
            let begin_info =
                vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::empty());
            self.dev
                .begin_command_buffer(self.cmd_buf, &begin_info)
                .expect("Failed to begin command buffer");
        }
    }

    fn end_cmd_buf(&self) {
        unsafe {
            self.dev
                .end_command_buffer(self.cmd_buf)
                .expect("Failed to end command buffer");
        }
    }

    fn submit_queue(&self, sem_avail: vk::Semaphore, sem_finish: vk::Semaphore, fence: vk::Fence) {
        let cmd_bufs = [self.cmd_buf];
        let wait_sems = [sem_avail];
        let signal_sems = [sem_finish];
        let submit_info = vk::SubmitInfo::default()
            .command_buffers(&cmd_bufs)
            .wait_semaphores(&wait_sems)
            .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT])
            .signal_semaphores(&signal_sems);
        unsafe {
            self.dev
                .queue_submit(self.queue, &[submit_info], fence)
                .expect("Failed to submit queue");
        }
    }

    fn queue_present(&self, swapchain: &SwapchainCtx, img_inx: u32, sem_finish: vk::Semaphore) {
        let swapchains = [swapchain.swapchain];
        let img_inxs = [img_inx];
        let wait_sems = [sem_finish];
        let present_info = vk::PresentInfoKHR::default()
            .swapchains(&swapchains)
            .image_indices(&img_inxs)
            .wait_semaphores(&wait_sems);
        unsafe {
            swapchain
                .swapchain_loader
                .queue_present(self.queue, &present_info)
                .expect("Failed to present queue");
        }
    }
}
