use std::collections::HashMap;

use ash::vk;

use crate::{Game, LoadingWorld, RenderCtx, render::MemType};

use super::loading_world::WorldComponent;

pub struct Cube {
    mesh_buf: vk::Buffer,
    pipeline: vk::Pipeline,
    pipe_layout: vk::PipelineLayout,
}

const MESH_SIZE: u64 = 36 * 2 * 4 * 4;

#[rustfmt::skip]
const POSITIONS: [f32; 36 * 2 * 4] = [
    // BOTTOM
    -0.5, 0.5, 1.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    0.5, 0.5, 2.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    -0.5, 0.5, 2.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    0.5, 0.5, 1.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    0.5, 0.5, 2.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    -0.5, 0.5, 1.0, 1.0,
    0.0, 1.0, 0.0, 0.0,

    // TOP
    0.5, -0.5, 2.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    -0.5, -0.5, 1.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    -0.5, -0.5, 2.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    0.5, -0.5, 2.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    0.5, -0.5, 1.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    -0.5, -0.5, 1.0, 1.0,
    0.0, -1.0, 0.0, 0.0,

    // FRONT
    -0.5, -0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    0.5, 0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    -0.5, 0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    0.5, -0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    0.5, 0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    -0.5, -0.5, 1.0, 1.0,
    0.0, 0.0, -1.0, 0.0,

    // BACK
    0.5, 0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    -0.5, -0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    -0.5, 0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    0.5, 0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    0.5, -0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    -0.5, -0.5, 2.0, 1.0,
    0.0, 0.0, 1.0, 0.0,

    // LEFT
    -0.5, -0.5, 1.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    -0.5, 0.5, 2.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    -0.5, -0.5, 2.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    -0.5, 0.5, 1.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    -0.5, 0.5, 2.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    -0.5, -0.5, 1.0, 1.0,
    -1.0, 0.0, 0.0, 0.0,

    // RIGHT
    0.5, 0.5, 2.0, 1.0,
    1.0, 0.0, 0.0, 0.0,

    0.5, -0.5, 1.0, 1.0,
    1.0, 0.0, 0.0, 0.0,

    0.5, -0.5, 2.0, 1.0,
    1.0, 0.0, 0.0, 0.0,

    0.5, 0.5, 2.0, 1.0,
    1.0, 0.0, 0.0, 0.0,

    0.5, 0.5, 1.0, 1.0,
    1.0, 0.0, 0.0, 0.0,

    0.5, -0.5, 1.0, 1.0,
    1.0, 0.0, 0.0, 0.0,
];

impl WorldComponent for Cube {
    fn descriptors(&self) -> std::collections::HashMap<vk::DescriptorType, u32> {
        let mut map = HashMap::new();
        map.insert(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1);
        map.insert(vk::DescriptorType::STORAGE_BUFFER, 1);
        map
    }

    fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout {
        let storage_binding = vk::DescriptorSetLayoutBinding::default()
            .binding(0)
            .descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
            .descriptor_count(1)
            .stage_flags(vk::ShaderStageFlags::VERTEX);
        let image_binding = vk::DescriptorSetLayoutBinding::default()
            .binding(1)
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
            .descriptor_count(1)
            .stage_flags(vk::ShaderStageFlags::FRAGMENT);
        let layouts = [storage_binding, image_binding];

        let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
        let layout = unsafe {
            ctx.dev
                .create_descriptor_set_layout(&layout_info, None)
                .expect("Failed to create descriptor set layout")
        };
        layout
    }

    fn write_desc_set(
        &self,
        ctx: &RenderCtx,
        desc_set: vk::DescriptorSet,
        skybox: (vk::ImageView, vk::Sampler),
    ) {
        let buf_info = vk::DescriptorBufferInfo::default()
            .buffer(self.mesh_buf)
            .offset(0)
            .range(vk::WHOLE_SIZE);
        let buf_infos = [buf_info];
        let buf_desc = vk::WriteDescriptorSet::default()
            .dst_set(desc_set)
            .dst_binding(0)
            .dst_array_element(0)
            .descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
            .buffer_info(&buf_infos);

        let img_info = vk::DescriptorImageInfo::default()
            .image_layout(vk::ImageLayout::GENERAL)
            .image_view(skybox.0)
            .sampler(skybox.1);
        let img_infos = &[img_info];
        let img_desc = vk::WriteDescriptorSet::default()
            .dst_set(desc_set)
            .dst_binding(1)
            .dst_array_element(0)
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
            .image_info(img_infos);

        unsafe {
            ctx.dev.update_descriptor_sets(&[buf_desc, img_desc], &[]);
        }
    }

    fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout) {
        (self.pipeline, self.pipe_layout)
    }

    fn push_constants(
        &self,
        camera: &crate::game::Camera,
        res: [u32; 2],
        base_color: [f32; 4],
    ) -> Vec<(vk::ShaderStageFlags, u32, Vec<u8>)> {
        let mut constants = Vec::new();
        let cam_data: Vec<u8> = (Vec::<f32>::from([
            camera.origin.x,
            camera.origin.y,
            camera.origin.z,
            0.0,
            camera.rotation.x,
            camera.rotation.y,
            camera.rotation.z,
        ]))
        .iter()
        .map(|f| f.to_ne_bytes())
        .collect::<Vec<_>>()
        .into_flattened();
        constants.push((vk::ShaderStageFlags::VERTEX, 0, cam_data));

        let screen_res: Vec<u8> = res
            .iter()
            .map(|f| f.to_ne_bytes())
            .collect::<Vec<_>>()
            .into_flattened();
        constants.push((vk::ShaderStageFlags::VERTEX, 32, screen_res));

        let base_color: Vec<u8> = (Vec::<f32>::from([0.0, 1.0, 1.0, 1.0]))
            .iter()
            .map(|f| f.to_ne_bytes())
            .collect::<Vec<_>>()
            .into_flattened();
        constants.push((vk::ShaderStageFlags::FRAGMENT, 48, base_color));

        constants
    }
}

impl RenderCtx {
    fn create_cube(&self, pass: vk::RenderPass) -> Cube {
        let mesh_mem = self.mem_alloc(MemType::HostVisibile, MESH_SIZE);

        let mesh_buf = self.alloc_buf(
            mesh_mem,
            MESH_SIZE,
            vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::STORAGE_BUFFER,
        );

        let mesh_mem = self.buf_to_ptr(mesh_mem, mesh_buf);
        unsafe {
            std::ptr::copy(POSITIONS.as_ptr(), mesh_mem as *mut f32, POSITIONS.len());
        }

        let pipe_layout = setup_pipe_layout(self);
        let pipeline = setup_pipeline(self, &pass, pipe_layout);

        Cube {
            mesh_buf,
            pipeline,
            pipe_layout,
        }
    }
}

impl Game<LoadingWorld> {
    pub fn create_cube(&self) -> Cube {
        let Self {
            state: LoadingWorld { ctx, pass, .. },
        } = self;
        ctx.create_cube(*pass)
    }
}

fn load_cube_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) {
    let shader_vert_shader = ctx.shader_mod_from_file(
        "shaders/cube_vert.spv",
        vk::ShaderModuleCreateFlags::empty(),
    );
    let shader_frag_shader = ctx.shader_mod_from_file(
        "shaders/cube_frag.spv",
        vk::ShaderModuleCreateFlags::empty(),
    );

    (shader_vert_shader, shader_frag_shader)
}

fn setup_desc_layout(ctx: &RenderCtx) -> vk::DescriptorSetLayout {
    let storage_binding = vk::DescriptorSetLayoutBinding::default()
        .binding(0)
        .descriptor_type(vk::DescriptorType::STORAGE_BUFFER)
        .descriptor_count(1)
        .stage_flags(vk::ShaderStageFlags::VERTEX);
    let image_binding = vk::DescriptorSetLayoutBinding::default()
        .binding(1)
        .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
        .descriptor_count(1)
        .stage_flags(vk::ShaderStageFlags::FRAGMENT);
    let layouts = [storage_binding, image_binding];

    let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts);
    let layout = unsafe {
        ctx.dev
            .create_descriptor_set_layout(&layout_info, None)
            .expect("Failed to create descriptor set layout")
    };
    layout
}

fn setup_pipe_layout(ctx: &RenderCtx) -> vk::PipelineLayout {
    let layout = setup_desc_layout(ctx);
    let push_range: [vk::PushConstantRange; 2] = [
        vk::PushConstantRange::default()
            .stage_flags(vk::ShaderStageFlags::VERTEX)
            .offset(0)
            .size(48), // vec4 camera_orig, camera_rot; uvec2 screen_res
        vk::PushConstantRange::default()
            .stage_flags(vk::ShaderStageFlags::FRAGMENT)
            .offset(48)
            .size(16), // vec4 base_color
    ];

    let layouts = &[layout];

    let pipe_layout = vk::PipelineLayoutCreateInfo::default()
        .set_layouts(layouts.as_ref())
        .push_constant_ranges(&push_range);
    let pipe_layout = unsafe {
        ctx.dev
            .create_pipeline_layout(&pipe_layout, None)
            .expect("Failed to create pipeline layout")
    };
    pipe_layout
}

fn setup_pipeline(
    ctx: &RenderCtx,
    pass: &vk::RenderPass,
    layout: vk::PipelineLayout,
) -> vk::Pipeline {
    let (vert_shader, frag_shader) = load_cube_shaders(ctx);
    let shader_stages = ctx.setup_simple_shader_stage(vert_shader, frag_shader);
    let vert_input = vk::PipelineVertexInputStateCreateInfo::default();
    let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
        .topology(vk::PrimitiveTopology::TRIANGLE_LIST);
    let rasterization = vk::PipelineRasterizationStateCreateInfo::default()
        .polygon_mode(vk::PolygonMode::FILL)
        .cull_mode(vk::CullModeFlags::BACK)
        .front_face(vk::FrontFace::COUNTER_CLOCKWISE)
        .line_width(1.0);
    let multisample = vk::PipelineMultisampleStateCreateInfo::default()
        .rasterization_samples(vk::SampleCountFlags::TYPE_1);
    let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
        .depth_test_enable(true)
        .depth_write_enable(true)
        .depth_compare_op(vk::CompareOp::LESS)
        .depth_bounds_test_enable(true)
        .stencil_test_enable(false)
        .min_depth_bounds(0.0)
        .max_depth_bounds(1.0);

    let blend = vk::PipelineColorBlendAttachmentState::default()
        .src_color_blend_factor(vk::BlendFactor::SRC_COLOR)
        .dst_color_blend_factor(vk::BlendFactor::SRC_COLOR)
        .color_blend_op(vk::BlendOp::ADD)
        .src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
        .dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR)
        .alpha_blend_op(vk::BlendOp::ADD)
        .color_write_mask(
            vk::ColorComponentFlags::R
                | vk::ColorComponentFlags::G
                | vk::ColorComponentFlags::B
                | vk::ColorComponentFlags::A,
        );

    let blend_attachments = [blend];

    let color_blend = vk::PipelineColorBlendStateCreateInfo::default()
        .attachments(blend_attachments.as_ref())
        .logic_op_enable(false)
        .logic_op(vk::LogicOp::COPY)
        .blend_constants([1.0, 1.0, 1.0, 1.0]);

    let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[
        vk::DynamicState::VIEWPORT_WITH_COUNT,
        vk::DynamicState::SCISSOR_WITH_COUNT,
    ]);

    let pipe_info = vk::GraphicsPipelineCreateInfo::default()
        .stages(&shader_stages)
        .vertex_input_state(&vert_input)
        .input_assembly_state(&input_assembly)
        .rasterization_state(&rasterization)
        .multisample_state(&multisample)
        .depth_stencil_state(&depth_stencil)
        .color_blend_state(&color_blend)
        .layout(layout)
        .dynamic_state(&dynamic)
        .render_pass(*pass)
        .subpass(0);

    let pipe = unsafe {
        ctx.dev
            .create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None)
            .expect("Failed to create graphics pipeline")
            .remove(0)
    };
    println!("Created pipeline");
    pipe
}
