use egui::Widget;
use egui_winit_vulkano::{Gui, GuiConfig};
use ray_tracing_core::prelude::*;
use ray_tracing_scene::examples::example_scenes;
use render::{render_thread, RENDERER};
use setup::{get_compute_pipeline, get_framebuffers, SetupResult};
use std::{collections::HashSet, sync::Arc};
use vulkano::{
    buffer::{Buffer, BufferCreateInfo, BufferUsage},
    command_buffer::{allocator::StandardCommandBufferAllocator, AutoCommandBufferBuilder},
    descriptor_set::{
        allocator::StandardDescriptorSetAllocator, PersistentDescriptorSet, WriteDescriptorSet,
    },
    memory::allocator::{AllocationCreateInfo, MemoryTypeFilter, StandardMemoryAllocator},
    pipeline::Pipeline,
    swapchain::{self, SwapchainCreateInfo, SwapchainPresentInfo},
    sync::{self, future::FenceSignalFuture, GpuFuture},
    Validated, VulkanError,
};
use winit::{
    event::{Event, WindowEvent},
    event_loop::ControlFlow,
};

mod render;
mod setup;

mod cs {
    vulkano_shaders::shader! {
        ty: "compute",
        src: "
            #version 460
            layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
            layout(set = 0, binding = 0) uniform writeonly image2D outputImage;

            layout(set = 0, binding = 1) uniform GPUSettings {
                uint samples;
            } settings;

            layout(set = 0, binding = 2) buffer Data {
                float data[];
            } buf;


            void main() {
                uvec2 size = imageSize(outputImage);
                uvec2 pos = gl_GlobalInvocationID.xy;
                if (pos.x < size.x && pos.y < size.y) {
                    uint index = (pos.y * size.x + pos.x) * 3;
                    vec3 color = vec3(buf.data[index], buf.data[index + 1], buf.data[index + 2]);

                    vec4 to_write = vec4(color / settings.samples, 1.0);
                    imageStore(outputImage, ivec2(pos), to_write);
                }
            }
        "
    }
}

fn main() {
    let SetupResult(event_loop, physical_device, device, queue, window, surface) = setup::setup();

    let (mut swapchain, images) = setup::create_swapchain(
        physical_device.clone(),
        device.clone(),
        surface.clone(),
        window.inner_size(),
    );

    let mut gui = Gui::new(
        &event_loop,
        surface.clone(),
        queue.clone(),
        swapchain.image_format(),
        GuiConfig {
            is_overlay: true,
            ..Default::default()
        },
    );

    let memory_allocator = Arc::new(StandardMemoryAllocator::new_default(device.clone()));

    let command_buffer_allocator =
        StandardCommandBufferAllocator::new(device.clone(), Default::default());
    let descriptor_set_allocator =
        StandardDescriptorSetAllocator::new(device.clone(), Default::default());

    let (control_tx, control_rx) = std::sync::mpsc::channel();
    let (data_tx, data_rx) = std::sync::mpsc::channel();

    let scenes = example_scenes::<rand::rngs::SmallRng>();
    let mut settings = {
        let scene = scenes.iter().next().unwrap();
        let e = scene.1;
        render::RenderSettings {
            width: window.inner_size().width,
            height: window.inner_size().height,
            scene: scene.0,
            renderer_id: 0,
            camera_pos: e.get_camera_pos(),
            camera_look_at: e.get_camera_look_at(),
            camera_up: e.get_camera_up(),
            camera_horizontal_fov: e.get_horizontal_fov(),
        }
    };

    let send_settings = settings.clone();
    let send_memory_allocator = memory_allocator.clone();

    std::thread::spawn(move || {
        render_thread(send_settings, control_rx, data_tx, send_memory_allocator)
    });

    let render_pass = setup::get_render_pass(device.clone(), &swapchain);
    let mut framebuffers = setup::get_framebuffers(&images, &render_pass);

    let mut recreate_swapchain = false;

    let mut fences: Vec<Option<Arc<FenceSignalFuture<_>>>> = vec![None; images.len()];
    let mut previous_fence_i = 0;

    let mut buffer = None;

    let cs = cs::load(device.clone()).unwrap();
    let mut keypressed = HashSet::new();

    let cpu_buffers: Vec<_> = framebuffers
        .iter()
        .map(|_| {
            Buffer::from_data(
                memory_allocator.clone(),
                BufferCreateInfo {
                    usage: BufferUsage::UNIFORM_BUFFER,
                    ..Default::default()
                },
                AllocationCreateInfo {
                    memory_type_filter: MemoryTypeFilter::PREFER_DEVICE
                        | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
                    ..Default::default()
                },
                cs::GPUSettings { samples: 1 },
            )
            .unwrap()
        })
        .collect();

    let mut pipeline = get_compute_pipeline(device.clone(), cs.clone());

    event_loop.run(move |event, _, control_flow| match event {
        Event::WindowEvent {
            window_id: _,
            event,
        } => {
            if !gui.update(&event) {
                if let WindowEvent::KeyboardInput { input, .. } = event {
                    if let Some(virt) = input.virtual_keycode {
                        match input.state {
                            winit::event::ElementState::Pressed => keypressed.insert(virt),
                            winit::event::ElementState::Released => keypressed.remove(&virt),
                        };
                    };
                };
            }
            match event {
                WindowEvent::Resized(_) => {
                    recreate_swapchain = true;
                    window.request_redraw();
                    settings.width = window.inner_size().width;
                    settings.height = window.inner_size().height;
                    let _ = control_tx.send(settings.clone());
                }
                WindowEvent::CloseRequested => {
                    *control_flow = ControlFlow::Exit;
                }
                _ => (),
            }
        }
        Event::MainEventsCleared => {
            if recreate_swapchain {
                let (new_swapchain, new_images) = swapchain
                    .recreate(SwapchainCreateInfo {
                        image_extent: window.inner_size().into(),
                        ..swapchain.create_info()
                    })
                    .expect("failed to recreate swapchain: {e}");
                swapchain = new_swapchain;
                framebuffers = get_framebuffers(&new_images, &render_pass);
                pipeline = get_compute_pipeline(device.clone(), cs.clone());
            }

            let mut settings_changed = false;
            if keypressed.contains(&winit::event::VirtualKeyCode::Right) {
                let translation = Dir3::cross(
                    settings.camera_look_at - settings.camera_pos,
                    settings.camera_up,
                )
                .normalize();
                settings.camera_pos = settings.camera_pos + translation * 0.1;
                settings.camera_look_at = settings.camera_look_at + translation * 0.1;
                settings_changed = true;
            }
            if keypressed.contains(&winit::event::VirtualKeyCode::Left) {
                let translation = Dir3::cross(
                    settings.camera_look_at - settings.camera_pos,
                    settings.camera_up,
                )
                .normalize();
                settings.camera_pos = settings.camera_pos - translation * 0.1;
                settings.camera_look_at = settings.camera_look_at - translation * 0.1;
                settings_changed = true;
            }

            gui.immediate_ui(|gui| {
                let ctx = gui.context();
                egui::Window::new("panel").show(&ctx, |ui| {
                    ui.collapsing("Scene Settings:", |ui| {
                        egui::ComboBox::from_label("Scene")
                            .selected_text(settings.scene)
                            .show_ui(ui, |ui| {
                                let mut scene_changed = false;
                                for &label in scenes.keys() {
                                    scene_changed |= ui
                                        .selectable_value(&mut settings.scene, label, label)
                                        .changed();
                                }
                                if scene_changed {
                                    settings_changed = true;
                                    settings.camera_pos = scenes[settings.scene].get_camera_pos();
                                    settings.camera_look_at =
                                        scenes[settings.scene].get_camera_look_at();
                                    settings.camera_up = scenes[settings.scene].get_camera_up();
                                    settings.camera_horizontal_fov =
                                        scenes[settings.scene].get_horizontal_fov();
                                }
                            });
                    });

                    settings_changed |= egui::ComboBox::from_label("Renderer")
                        .selected_text(RENDERER[settings.renderer_id])
                        .show_index(ui, &mut settings.renderer_id, RENDERER.len(), |i| {
                            RENDERER[i]
                        })
                        .changed();

                    settings_changed |= egui::Slider::new(
                        &mut settings.camera_horizontal_fov,
                        0.1..=FloatConsts::PI - 0.1,
                    )
                    .ui(ui)
                    .changed();

                    ui.collapsing("Render Settings:", |ui| {
                        ui.label(format!("{:#?}", &settings));
                    });
                });
            });

            if settings_changed {
                let _ = control_tx.send(settings.clone());
            }

            let (image_i, suboptimal, acquire_future) =
                match swapchain::acquire_next_image(swapchain.clone(), None)
                    .map_err(Validated::unwrap)
                {
                    Ok(r) => r,
                    Err(e) => panic!("failed to acquire next image: {e}"),
                };

            let previous_future = match fences[previous_fence_i as usize].clone() {
                // Create a NowFuture
                None => {
                    let mut now = sync::now(device.clone());
                    now.cleanup_finished();

                    now.boxed()
                }
                // Use the existing FenceSignalFuture
                Some(fence) => fence.boxed(),
            };

            if suboptimal {
                recreate_swapchain = true;
            }

            if let Some(image_fence) = &fences[image_i as usize] {
                image_fence.wait(None).unwrap();
            }

            while let Ok(b) = data_rx.try_recv() {
                buffer = Some(b);
            }

            if buffer.as_ref().is_some_and(|b| {
                b.width != window.inner_size().width || b.height != window.inner_size().height
            }) {
                buffer = None;
            }

            let compute_future = match &buffer {
                Some(b) => {
                    match cpu_buffers[image_i as usize].write() {
                        Ok(mut local) => {
                            local.samples = b.samples;
                        }
                        Err(e) => {
                            println!("{}", e);
                        }
                    }

                    let mut builder = AutoCommandBufferBuilder::primary(
                        &command_buffer_allocator,
                        queue.queue_family_index(),
                        vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
                    )
                    .unwrap();

                    let pipeline_layout = pipeline.layout();
                    let descriptor_set_layouts = pipeline_layout.set_layouts();

                    let descriptor_set_layout = descriptor_set_layouts.first().unwrap();

                    let descriptor_set = PersistentDescriptorSet::new(
                        &descriptor_set_allocator,
                        descriptor_set_layout.clone(),
                        [
                            WriteDescriptorSet::image_view(
                                0,
                                framebuffers[image_i as usize].attachments()[0].clone(),
                            ),
                            WriteDescriptorSet::buffer(1, cpu_buffers[image_i as usize].clone()),
                            WriteDescriptorSet::buffer(2, b.buffer.clone()),
                        ],
                        [],
                    )
                    .unwrap();

                    builder
                        .bind_pipeline_compute(pipeline.clone())
                        .unwrap()
                        .bind_descriptor_sets(
                            vulkano::pipeline::PipelineBindPoint::Compute,
                            pipeline_layout.clone(),
                            0,
                            descriptor_set,
                        )
                        .unwrap()
                        .dispatch([b.width.div_ceil(8), b.height.div_ceil(8), 1])
                        .unwrap();

                    let command = builder.build().unwrap();

                    acquire_future
                        .join(previous_future)
                        .then_execute(queue.clone(), command)
                        .unwrap()
                        .then_signal_fence()
                        .boxed()
                }
                None => acquire_future.join(previous_future).boxed(),
            };

            let gui_future = gui.draw_on_image(
                compute_future,
                framebuffers[image_i as usize].attachments()[0].clone(),
            );

            let future = gui_future
                .then_swapchain_present(
                    queue.clone(),
                    SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_i),
                )
                .then_signal_fence_and_flush();

            fences[image_i as usize] = match future.map_err(Validated::unwrap) {
                #[allow(clippy::arc_with_non_send_sync)]
                Ok(value) => Some(Arc::new(value)),
                Err(VulkanError::OutOfDate) => {
                    recreate_swapchain = true;
                    None
                }
                Err(e) => {
                    println!("failed to flush future: {e}");
                    None
                }
            };

            previous_fence_i = image_i;
        }
        _ => (),
    })
}
