use std::{sync::Arc};
use std::time::SystemTime;

use cgmath::{Matrix4, SquareMatrix};
use dds::get_block_size;
use vulkano::command_buffer::CommandBufferUsage::{MultipleSubmit, SimultaneousUse};
use vulkano::{buffer::{BufferUsage, CpuAccessibleBuffer}, command_buffer::{PrimaryAutoCommandBuffer, SubpassContents}, image::{ImageDimensions, ImageLayout, ImageUsage, MipmapsCount, immutable::SubImage}, render_pass::{FramebufferAbstract, RenderPass}};
use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryCommandBuffer, DynamicState};
use vulkano::descriptor::DescriptorSet;
use vulkano::device::{Device, Features, Queue};
use vulkano::format::{ClearValue, Format};
use vulkano::image::{ImageCreateFlags, ImmutableImage};
use vulkano::instance::{ApplicationInfo, Instance, PhysicalDevice, Version};
use vulkano::instance::debug::{DebugCallback, MessageSeverity, MessageType};
use vulkano::sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode};
use vulkano::swapchain::{AcquireError, FullscreenExclusive, PresentMode, Surface, SurfaceTransform, Swapchain, SwapchainCreationError};
use vulkano::swapchain;
use vulkano::sync::{FlushError, GpuFuture};
use vulkano::sync;
use vulkano_win::VkSurfaceBuild;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};

use mesh::CPUMesh;
use pipelines::{Drawcall, LineShader};
use pipelines::line_vs::ty::LinePushConstants;
use pipelines::DefaultShader;
use pipelines::vs;

use crate::{config::RenderConfig};
use crate::vulkan::gameobject::{GameObject, GameObjectHandle};

pub mod pipelines;
pub mod gameobject;
pub mod mesh;
pub mod dds;
mod renderpass;
mod framebuffers;

const VALIDATION_LAYERS: &[&str] = &[
    "VK_LAYER_KHRONOS_validation"
];

#[derive(Default, Debug, Clone)]
pub struct Vertex {
    pub position: [f32; 3],
    pub uv: [f32; 2],
    pub normal: [f32; 3],
    pub tangent: [f32; 4],
    pub bone_index: [i32; 4],
    pub bone_weight: [f32; 4],
}
vulkano::impl_vertex!(Vertex, position, uv, normal, tangent, bone_index, bone_weight);

#[derive(Default, Debug, Clone)]
pub struct LinePoint {
    pub position: [f32; 3],
}
vulkano::impl_vertex!(LinePoint, position);

pub trait Game {
    /// Returns true if event should be ignored by the vulkan handler
    fn on_window_event(self: &mut Self, event: &Event<()>);

    fn update(self: &mut Self, renderer: &mut VulkanRenderer) -> vs::ty::ObjectUniformData;
}

pub struct Mesh {
    pub vertex_buffer: Arc<CpuAccessibleBuffer<[Vertex]>>,
    pub index_buffer: Arc<CpuAccessibleBuffer<[u32]>>,
    pub original_path: String,
}

#[derive(Debug, Clone)]
pub struct MeshHandle {
    pub index: usize,
    pub diffuse_handle: TextureHandle,
    pub normal_handle: TextureHandle,
    pub original_path: String
}

pub(crate) type TextureHandle = usize;
pub struct Texture {
    pub image: Arc<ImmutableImage>,
    pub sampler: Arc<Sampler>
}

pub struct GameData {
    pub start_time: SystemTime,
    pub line_vertices: Vec<LinePoint>,
    pub line_push_constants: LinePushConstants,
    pub recreate_pipeline: bool,
    pub dimensions: [u32; 2],
    pub shutdown: bool,
    pub game_objects: Vec<GameObject>,
    pub meshes: Vec<Mesh>,
    pub textures: Vec<Texture>,
    pub use_line_pipeline: bool,
}

pub(crate) type RendererDescriptorSets = dyn DescriptorSet + Send + Sync;

pub struct VulkanRenderer {
    pub game_data: GameData,
    pub device: Arc<Device>,
    pub framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
    pub dynamic_state: DynamicState,
    pub pipelines: Vec<Box<dyn Drawcall>>,
    pub surface: Arc<Surface<Window>>,
    pub swapchain: Arc<Swapchain<Window>>,
    pub render_pass: Arc<RenderPass>,
    pub queue: Arc<Queue>,
    pub recreate_swapchain: bool,
    pub debug_callback: Option<DebugCallback>,
    pub previous_frame_end: Option<Box<dyn GpuFuture>>,
    pub uniform_buffers: Vec<Arc<CpuAccessibleBuffer<vs::ty::ObjectUniformData>>>,
    pub line_vertex_buffer: Arc<CpuAccessibleBuffer<[LinePoint]>>,
    pub render_config: RenderConfig
}

impl VulkanRenderer {
    pub fn init(line_vertices: Vec<LinePoint>, enable_validation_layers: bool, render_config: RenderConfig) -> (VulkanRenderer, EventLoop<()>) {
        // Create empty game data struct to be filled
        let mut data = GameData {
            line_push_constants: LinePushConstants {
                model: Matrix4::identity().into(),
                view: Matrix4::identity().into(),
                projection: Matrix4::identity().into(),
            },
            start_time: SystemTime::now(),
            recreate_pipeline: false,
            shutdown: false,
            line_vertices,
            dimensions: [0, 0],
            meshes: vec![],
            game_objects: vec![],
            textures: vec![],
            use_line_pipeline: true,
        };

        // Create basic vulkan instance with layers and info
        let instance = {
            let extensions = vulkano::instance::InstanceExtensions {
                ext_debug_utils: true,
                ..vulkano_win::required_extensions()
            };

            let app_info = ApplicationInfo {
                application_name: Some("Asuro's Editor".into()),
                application_version: Some(Version { major: 0, minor: 1, patch: 0 }),
                engine_name: Some("Asuro's Rust Engine".into()),
                engine_version: Some(Version { major: 0, minor: 1, patch: 0 })
            };

            if enable_validation_layers {
                println!("Enabling validation layers...");
                let available_layers = vulkano::instance::layers_list().unwrap().map(|layer| String::from(layer.name())).collect::<Vec<String>>();
                println!("Available layers: {:?}", available_layers);

                VALIDATION_LAYERS.iter().for_each(|wanted_layer_name| {
                    if !available_layers.iter().any(|available_layer_name| available_layer_name == wanted_layer_name) {
                        panic!("Validation layer not found: {:?}", wanted_layer_name);
                    }
                });

                Instance::new(Some(&app_info), Version::V1_2, &extensions, VALIDATION_LAYERS.iter().cloned()).expect("failed to create Vulkan instance")
            } else {
                Instance::new(Some(&app_info), Version::V1_2, &extensions, None).expect("failed to create Vulkan instance")
            }
        };

        // lifetime of this is important, even tho it isn't used!
        let mut debug_callback = None;

        // Debug stuff
        if enable_validation_layers {
            let msg_severity = MessageSeverity {
                verbose: false,
                information: true,
                warning: true,
                error: true
            };

            let msg_types = MessageType {
                general: true,
                performance: true,
                validation: true
            };

            debug_callback = DebugCallback::new(&instance, msg_severity, msg_types, |msg| {
                let type_str = match (msg.severity.error, msg.severity.warning, msg.severity.information, msg.severity.verbose) {
                    (true, _, _, _) => "!!",
                    (_, true, _, _) => "!",
                    (_, _, _, true) => "i",
                    _ => " "
                };

                let layer_str = msg.layer_prefix;

                println!("[{}][{:?}]: {}", type_str, layer_str, msg.description);
            }).ok();
        }

        // TODO: Just get the first physical device we find, it's fiiiine...
        let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
        let events_loop = EventLoop::new();
        let surface = WindowBuilder::new().build_vk_surface(&events_loop, instance.clone()).unwrap();
        let window = surface.window();

        // TODO: Tutorial says we need more queues
        // In a real-life application, we would probably use at least a graphics queue and a transfers
        // queue to handle data transfers in parallel. In this example we only use one queue.
        let queue_family = physical.queue_families().find(|&q| {
            q.supports_graphics() && surface.is_supported(q).unwrap_or(false)
        }).unwrap();

        // Queue
        let device_ext = vulkano::device::DeviceExtensions { khr_swapchain: true, ..vulkano::device::DeviceExtensions::none() };
        let (device, mut queues) = Device::new(physical, &Features::none(), &device_ext,
                                               [(queue_family, 0.5)].iter().cloned()).unwrap();
        let queue = queues.next().unwrap();

        // Swapchain
        let (swapchain, images) = {
            let caps = surface.capabilities(physical).unwrap();
            let usage = caps.supported_usage_flags;
            let alpha = caps.supported_composite_alpha.iter().next().unwrap();
            let (format, color_space) = caps.supported_formats[2];
            let inner_size = window.inner_size();
            data.dimensions = [inner_size.width, inner_size.height];

            Swapchain::start(device.clone(), surface.clone())
                .num_images(caps.min_image_count)
                .format(format)
                .dimensions(data.dimensions)
                .usage(usage)
                .sharing_mode(&queue)
                .transform(SurfaceTransform::Identity)
                .composite_alpha(alpha)
                .present_mode(PresentMode::Fifo)
                .fullscreen_exclusive(FullscreenExclusive::Default)
                .clipped(true)
                .color_space(color_space)
            .build().unwrap()
        };

        // Render pass
        let render_pass = renderpass::create_render_pass(device.clone(), &render_config, swapchain.format());

        let line_vertex_buffer = CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::vertex_buffer(), false, data.line_vertices.iter().cloned()).unwrap();

        let pipelines: Vec<Box<dyn Drawcall>> = vec![
            Box::new(DefaultShader::new(device.clone(), render_pass.clone())),
            // Box::new(LineShader::new(device.clone(), render_pass.clone(), line_vertex_buffer.clone())),
        ];

        // Dynamic viewports allow us to recreate just the viewport when the window is resized
        // Otherwise we would have to recreate the whole pipeline.
        let mut dynamic_state = DynamicState { line_width: None, viewports: None, scissors: None, compare_mask: None, write_mask: None, reference: None };



        // The render pass we created above only describes the layout of our framebuffers. Before we
        // can draw we also need to create the actual framebuffers.
        let framebuffers = framebuffers::create_framebuffers(device.clone(), &swapchain, &images, render_config.get_msaa(), render_pass.clone(), &mut dynamic_state);

        let mut uniform_buffers = Vec::new();
        let uniform_buffer = vs::ty::ObjectUniformData {
            view: Matrix4::identity().into(),
            projection: Matrix4::identity().into(),
            time: 0.0,
            light_position: [0.0, 0.0, 0.0],
            light_directional_rotation: [0.0, 0.0, 0.0],
            camera_position: [0.0, 0.0, 0.0],
            _dummy0: [0; 12],
            _dummy1: [0; 4],
            _dummy2: [0; 4],
        };

        for _ in 0..swapchain.num_images() {
            uniform_buffers.push(CpuAccessibleBuffer::from_data(
                device.clone(),
                BufferUsage::uniform_buffer_transfer_destination(),
                false,
                uniform_buffer,
            ).unwrap());
        }

        // In the loop below we are going to submit commands to the GPU. Submitting a command produces
        // an object that implements the `GpuFuture` trait, which holds the resources for as long as
        // they are in use by the GPU.
        //
        // Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid
        // that, we store the submission of the previous frame here.
        let previous_frame_end = Some(Box::new(sync::now(device.clone())) as Box<dyn GpuFuture>);

        (VulkanRenderer { game_data: data, device, framebuffers,
            dynamic_state, pipelines, uniform_buffers,
            surface, swapchain, render_pass, queue,
            recreate_swapchain: false, debug_callback, previous_frame_end,
            render_config,
            line_vertex_buffer,
        }, events_loop)
    }

    fn create_command_buffer(self: &mut Self, fb_index: usize, uniform_buffer_data: vs::ty::ObjectUniformData) -> Arc<PrimaryAutoCommandBuffer> {
        // General setup
        let mut builder = AutoCommandBufferBuilder::primary(self.device.clone(), self.queue.family(), SimultaneousUse).unwrap();
        builder.update_buffer(self.uniform_buffers[fb_index].clone(), Arc::new(uniform_buffer_data)).unwrap();
        if self.render_config.msaa_samples > 0 {
            builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::None, ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
        } else {
            builder.begin_render_pass(self.framebuffers[fb_index].clone(), SubpassContents::Inline, vec![ClearValue::Float([0.0, 0.0, 0.0, 1.0]), ClearValue::Depth(1.0)]).unwrap();
        }

        // Draw meshes etc.
        for pipeline in &self.pipelines {
            pipeline.draw(&mut builder, fb_index, &self.game_data, &self.dynamic_state);
        }
        
        // General cleanup
        builder.end_render_pass().unwrap();
        Arc::new(builder.build().unwrap())
    }



    pub fn render_loop(self: &mut Self, new_ubo: vs::ty::ObjectUniformData) {
        // cleanup previous frame
        self.previous_frame_end.as_mut().unwrap().cleanup_finished();

        // recreate swapchain if window size changed
        if self.recreate_swapchain {
            let window = self.surface.window();
            let inner_size = window.inner_size();
            self.game_data.dimensions = [inner_size.width, inner_size.height];

            let (new_swapchain, new_images) = match self.swapchain.recreate().dimensions(self.game_data.dimensions).build() {
                Ok(r) => r,
                // This error tends to happen when the user is manually resizing the window.
                // Simply restarting the loop is the easiest way to fix this issue.
                Err(SwapchainCreationError::UnsupportedDimensions) => {
                    println!("Swapchain rejected: UnsupportedDimensions");
                    return;
                }
                Err(err) => panic!("{:?}", err),
            };

            self.render_pass = renderpass::create_render_pass(self.device.clone(), &self.render_config, new_swapchain.format());

            self.pipelines = vec![
                Box::new(DefaultShader::new(self.device.clone(), self.render_pass.clone())),
                // Box::new(LineShader::new(self.device.clone(), self.render_pass.clone(), self.line_vertex_buffer.clone())),
            ];

            self.swapchain = new_swapchain;
            // Because framebuffers contains an Arc on the old swapchain, we need to
            // recreate framebuffers as well.
            self.framebuffers = framebuffers::create_framebuffers(self.device.clone(), &self.swapchain, &new_images, self.render_config.get_msaa(), self.render_pass.clone(), &mut self.dynamic_state);

            self.recreate_swapchain = false;
        }

        // recreate pipeline if requested
        if self.game_data.recreate_pipeline {
            let device = self.device.clone();
            let render_pass = self.render_pass.clone();
            self.pipelines.iter_mut().for_each(|pipeline| pipeline.recreate_pipeline(device.clone(), render_pass.clone()));
            self.game_data.recreate_pipeline = false;
        }

        // Before we can draw on the output, we have to *acquire* an image from the swapchain. If
        // no image is available (which happens if you submit draw commands too quickly), then the
        // function will block.
        // This operation returns the index of the image that we are allowed to draw upon.
        //
        // This function can block if no image is available. The parameter is an optional timeout
        // after which the function call will return an error.
        let (fb_index, _, acquire_future) = match swapchain::acquire_next_image(self.swapchain.clone(), None) {
            Ok(r) => r,
            Err(AcquireError::OutOfDate) => {
                self.recreate_swapchain = true;
                return;
            },
            Err(err) => panic!("{:?}", err)
        };

        let command_buffer = self.create_command_buffer(fb_index, new_ubo).clone();

        let future = self.previous_frame_end.take().unwrap()
            .join(acquire_future)
            .then_execute(self.queue.clone(), command_buffer).unwrap()
            .then_swapchain_present(self.queue.clone(), self.swapchain.clone(), fb_index)
            .then_signal_fence_and_flush();

        match future {
            Ok(future) => {
                // we're joining on the previous future but the CPU is running faster than the GPU so
                // eventually it stutters, and jumps ahead to the newer frames.
                //
                // See vulkano issue 1135: https://github.com/vulkano-rs/vulkano/issues/1135
                // This makes sure the CPU stays in sync with the GPU in situations when the CPU is
                // running "too fast"
                #[cfg(target_os = "macos")]
                future.wait(None).unwrap();

                self.previous_frame_end = Some(Box::new(future) as Box<_>);
            },
            Err(FlushError::OutOfDate) => {
                println!("Swapchain out of date!");
                self.recreate_swapchain = true;
                self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
            }
            Err(e) => {
                println!("{:?}", e);
                self.previous_frame_end = Some(Box::new(sync::now(self.device.clone())) as Box<_>);
            }
        };
    }

    pub fn upload_mesh(self: &mut Self, mesh: CPUMesh, original_path: String) -> usize {
        // let mut collision_mesh = mgf::Mesh::new();
        // mesh.vertices.iter().for_each(|v| {
        //     collision_mesh.push_vert(v.position.into());
        // }); // TODO: convert vert pos to world space
        // for i in (0..mesh.indices.len()).step_by(3) {
        //     collision_mesh.push_face((mesh.indices[i] as usize, mesh.indices[i + 1] as usize, mesh.indices[i + 2] as usize));
        // }

        let vertex_buffer = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::vertex_buffer(), false, mesh.vertices.into_iter()).unwrap();
        let index_buffer  = CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::index_buffer(),  false, mesh.indices.into_iter()).unwrap();

        self.game_data.meshes.push(Mesh { vertex_buffer, index_buffer, original_path });
        self.game_data.meshes.len() - 1
    }

    pub fn upload_texture(self: &mut Self, bytes: &[u8], width: u32, height: u32, format: Format, device: Arc<Device>) {
        let dimensions = ImageDimensions::Dim2d { width, height, array_layers: 1 };

        let usage = ImageUsage {
            transfer_destination: true,
            transfer_source: false,
            sampled: true,
            ..ImageUsage::none()
        };

        let layout = ImageLayout::ShaderReadOnlyOptimal;

        let (immutable_image, initializer) = ImmutableImage::uninitialized(
            device.clone(),
            dimensions,
            format,
            MipmapsCount::Log2,
            usage,
            ImageCreateFlags::default(),
            layout,
            device.active_queue_families(),
        ).unwrap();

        let init = SubImage::new(
            Arc::new(initializer),
            0,
            immutable_image.mipmap_levels(),
            0,
            1,
            ImageLayout::ShaderReadOnlyOptimal,
        );

        // TODO: do we need multiple submit?
        let mut cbb = AutoCommandBufferBuilder::primary(device.clone(), self.queue.family(), MultipleSubmit).unwrap();

        let mut offset = 0;

        let block_bytes = get_block_size(format).expect(&format!("Unknown texture format {:?}!", format));

        for i in 0..immutable_image.mipmap_levels() {
            let mip_size = dimensions.mipmap_dimensions(i).unwrap().width_height_depth();

            let mip_byte_size = (
                  (u32::max(4, mip_size[0]) / 4)
                * (u32::max(4, mip_size[1]) / 4)
                * block_bytes) as usize;

            let source = CpuAccessibleBuffer::from_iter(
                device.clone(),
                BufferUsage::transfer_source(),
                false,
                bytes[offset..(offset + mip_byte_size)].iter().cloned(),
            ).unwrap();

            cbb.copy_buffer_to_image_dimensions(
                source.clone(),
                init.clone(),
                [0, 0, 0],
                mip_size,
                0,
                dimensions.array_layers(),
                i,
            ).unwrap();

            offset += mip_byte_size;
        }

        let cb = cbb.build().unwrap();

        let future = match cb.execute(self.queue.clone()) {
            Ok(f) => f,
            Err(e) => unreachable!("{:?}", e)
        };

        future.flush().unwrap();

        let sampler = Sampler::new(device.clone(), Filter::Linear, Filter::Linear,
            MipmapMode::Linear, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat,
            SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, (immutable_image.mipmap_levels() - 1) as f32).unwrap();

        self.game_data.textures.push(Texture { image: immutable_image, sampler });
    }

    pub fn add_game_object(self: &mut Self, mut game_object: GameObject, pipeline_index: usize) -> GameObjectHandle {
        self.pipelines[pipeline_index].create_descriptor_set(&mut game_object, self);
        self.game_data.game_objects.push(game_object);

        GameObjectHandle {
            object_index: self.game_data.game_objects.len() - 1
        }
    }

    pub fn clear_all(&mut self) {
        self.game_data.game_objects.clear();
        self.game_data.meshes.clear();
        self.game_data.textures.clear();
    }
}

pub fn start_event_loop(mut renderer: VulkanRenderer, mut game: Box<dyn Game>, event_loop: EventLoop<()>) {
    event_loop.run(move |event, _, control_flow| {
        game.on_window_event(&event);

        if renderer.game_data.shutdown {
            *control_flow = ControlFlow::Exit;
        }

        match event {
            Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
                *control_flow = ControlFlow::Exit;
            },
            Event::RedrawEventsCleared => {
                let ubo = game.update(&mut renderer);
                renderer.render_loop(ubo);
            },
            _ => {}
        }
    });
}

