use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer, DeviceLocalBuffer, ImmutableBuffer, BufferAccess};
use vulkano::command_buffer::AutoCommandBufferBuilder;
use vulkano::descriptor::descriptor_set::{PersistentDescriptorSet, StdDescriptorPoolAlloc};
use vulkano::device::{Device, DeviceExtensions, QueuesIter, Queue};
use vulkano::instance::{Instance, InstanceExtensions, PhysicalDevice, QueueFamily};
use vulkano::pipeline::{ComputePipeline, GraphicsPipeline, GraphicsPipelineAbstract};
use vulkano::sync::GpuFuture;
use vulkano::sync;
use std::time::SystemTime;
use std::sync::Arc;
use std::ffi::CStr;
use std::path::PathBuf;
use shade_runner as sr;
use image::{DynamicImage, ImageBuffer};
use image::GenericImageView;
use vulkano::descriptor::pipeline_layout::PipelineLayout;
use image::GenericImage;
use shade_runner::{ComputeLayout, CompileError, FragLayout, FragInput, FragOutput, VertInput, VertOutput, VertLayout};
use vulkano::descriptor::descriptor_set::PersistentDescriptorSetBuf;
use shaderc::CompileOptions;
use vulkano::framebuffer::{Subpass, RenderPass};
use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule, GraphicsEntryPoint, SpecializationConstants, SpecializationMapEntry};
use vulkano::swapchain::{Swapchain, PresentMode, SurfaceTransform, Surface};
use vulkano::image::swapchain::SwapchainImage;
use winit::{EventsLoop, WindowBuilder, Window};
use vulkano_win::VkSurfaceBuild;
use vulkano::pipeline::vertex::{SingleBufferDefinition, Vertex};
use vulkano::descriptor::PipelineLayoutAbstract;
use std::alloc::Layout;

#[repr(C)]
struct MySpecConstants {
    my_integer_constant: i32,
    a_boolean: u32,
    floating_point: f32,
}

unsafe impl SpecializationConstants for MySpecConstants {
    fn descriptors() -> &'static [SpecializationMapEntry] {
        static DESCRIPTORS: [SpecializationMapEntry; 3] = [
            SpecializationMapEntry {
                constant_id: 0,
                offset: 0,
                size: 4,
            },
            SpecializationMapEntry {
                constant_id: 1,
                offset: 4,
                size: 4,
            },
            SpecializationMapEntry {
                constant_id: 2,
                offset: 8,
                size: 4,
            },
        ];

        &DESCRIPTORS
    }
}


pub struct VkProcessor<'a> {
    pub instance: Arc<Instance>,
    pub physical: PhysicalDevice<'a>,
    pub pipeline:         Option<Arc<GraphicsPipelineAbstract + Sync + Send>>,
    pub compute_pipeline: Option<std::sync::Arc<ComputePipeline<PipelineLayout<shade_runner::layouts::ComputeLayout>>>>,
    pub device: Arc<Device>,
    pub queues: QueuesIter,
    pub queue: Arc<Queue>,
    pub set: Option<Arc<PersistentDescriptorSet<std::sync::Arc<ComputePipeline<PipelineLayout<shade_runner::layouts::ComputeLayout>>>, ((((), PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u8]>>>), PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u8]>>>), PersistentDescriptorSetBuf<std::sync::Arc<vulkano::buffer::cpu_access::CpuAccessibleBuffer<[u32]>>>)>>>,
    pub image_buffer: Vec<u8>,
    pub img_buffers: Vec<Arc<CpuAccessibleBuffer<[u8]>>>,
    pub settings_buffer: Option<Arc<CpuAccessibleBuffer<[u32]>>>,
    pub swapchain: Option<Arc<Swapchain<Window>>>,
    pub images: Option<Vec<Arc<SwapchainImage<Window>>>>,
    pub xy: (u32, u32),
}

impl<'a> VkProcessor<'a> {
    pub fn new(instance : &'a Arc<Instance>, surface : &'a Arc<Surface<Window>>) -> VkProcessor<'a> {


        let physical = PhysicalDevice::enumerate(instance).next().unwrap();

        let queue_family = physical.queue_families().find(|&q| {
            // We take the first queue that supports drawing to our window.
            q.supports_graphics() &&
                surface.is_supported(q).unwrap_or(false) &&
                q.supports_compute()
        }).unwrap();

        let device_ext = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::none() };

        let (device, mut queues) = Device::new(physical,
                                               physical.supported_features(),
                                               &device_ext,
                                               [(queue_family, 0.5)].iter().cloned()).unwrap();

        let queue = queues.next().unwrap();

        VkProcessor {
            instance: instance.clone(),
            physical: physical.clone(),
            pipeline: Option::None,
            compute_pipeline: Option::None,
            device: device,
            queue: queues.next().unwrap(),
            queues: queues,
            set: Option::None,
            image_buffer: Vec::new(),
            img_buffers: Vec::new(),
            settings_buffer: Option::None,
            swapchain: Option::None,
            images: Option::None,
            xy: (0,0),
        }

    }

    pub fn compile_kernel(&mut self, filename: String) {

        let project_root =
            std::env::current_dir()
                .expect("failed to get root directory");

        let mut compute_path = project_root.clone();
        compute_path.push(PathBuf::from("resources/shaders/"));
        compute_path.push(PathBuf::from(filename));


        let mut options = CompileOptions::new().ok_or(CompileError::CreateCompiler).unwrap();
        options.add_macro_definition("SETTING_POS_X", Some("0"));
        options.add_macro_definition("SETTING_POS_Y", Some("1"));
        options.add_macro_definition("SETTING_BUCKETS_START", Some("2"));
        options.add_macro_definition("SETTING_BUCKETS_LEN", Some("2"));

        let shader =
            sr::load_compute_with_options(compute_path, options)
                .expect("Failed to compile");

        let vulkano_entry =
            sr::parse_compute(&shader)
                .expect("failed to parse");

        let x = unsafe {
            vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.compute)
        }.unwrap();

        let compute_pipeline = Arc::new({
            unsafe {
                ComputePipeline::new(self.device.clone(), &x.compute_entry_point(
                    CStr::from_bytes_with_nul_unchecked(b"main\0"),
                    vulkano_entry.compute_layout), &(),
                ).unwrap()
            }
        });

        self.compute_pipeline = Some(compute_pipeline);
    }

    pub fn compile_shaders(&mut self, filename: String, surface : &'a Arc<Surface<Window>>) {

        // Before we can draw on the surface, we have to create what is called a swapchain. Creating
        // a swapchain allocates the color buffers that will contain the image that will ultimately
        // be visible on the screen. These images are returned alongside with the swapchain.
        let (mut swapchain, images) = {
            // Querying the capabilities of the surface. When we create the swapchain we can only
            // pass values that are allowed by the capabilities.
            let capabilities = surface.capabilities(self.physical).unwrap();

            let usage = capabilities.supported_usage_flags;

            // The alpha mode indicates how the alpha value of the final image will behave. For example
            // you can choose whether the window will be opaque or transparent.
            let alpha = capabilities.supported_composite_alpha.iter().next().unwrap();

            // Choosing the internal format that the images will have.
            let format = capabilities.supported_formats[0].0;

            // The dimensions of the window, only used to initially setup the swapchain.
            // NOTE:
            // On some drivers the swapchain dimensions are specified by `caps.current_extent` and the
            // swapchain size must use these dimensions.
            // These dimensions are always the same as the window dimensions
            //
            // However other drivers dont specify a value i.e. `caps.current_extent` is `None`
            // These drivers will allow anything but the only sensible value is the window dimensions.
            //
            // Because for both of these cases, the swapchain needs to be the window dimensions, we just use that.
            let initial_dimensions = if let Some(dimensions) = surface.window().get_inner_size() {
                // convert to physical pixels
                let dimensions: (u32, u32) = dimensions.to_physical(surface.window().get_hidpi_factor()).into();
                [dimensions.0, dimensions.1]
            } else {
                // The window no longer exists so exit the application.
                return;
            };

            // Please take a look at the docs for the meaning of the parameters we didn't mention.
            Swapchain::new(self.device.clone(), surface.clone(), capabilities.min_image_count, format,
                           initial_dimensions, 1, usage, &self.queue, SurfaceTransform::Identity, alpha,
                           PresentMode::Fifo, true, None).unwrap()
        };

        self.swapchain = Some(swapchain);
        self.images = Some(images);

        let project_root =
            std::env::current_dir()
                .expect("failed to get root directory");

        let mut shader_path = project_root.clone();
        shader_path.push(PathBuf::from("resources/shaders/"));

        let mut vertex_shader_path = project_root.clone();
        vertex_shader_path.push(PathBuf::from("resources/shaders/"));
        vertex_shader_path.push(PathBuf::from(filename.clone()));
        vertex_shader_path.push(PathBuf::from(".vertex"));

        let mut fragment_shader_path = project_root.clone();
        fragment_shader_path.push(PathBuf::from("resources/shaders/"));
        fragment_shader_path.push(PathBuf::from(filename.clone()));
        fragment_shader_path.push(PathBuf::from(".fragment"));

        let mut options = CompileOptions::new().ok_or(CompileError::CreateCompiler).unwrap();
        options.add_macro_definition("SETTING_POS_X", Some("0"));
        options.add_macro_definition("SETTING_POS_Y", Some("1"));
        options.add_macro_definition("SETTING_BUCKETS_START", Some("2"));
        options.add_macro_definition("SETTING_BUCKETS_LEN", Some("2"));

        let shader =
            sr::load(vertex_shader_path, fragment_shader_path)
                .expect("Failed to compile");

        let vulkano_entry =
            sr::parse(&shader)
                .expect("failed to parse");

        let x1 : Arc<ShaderModule> = unsafe {
            vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.fragment)
        }.unwrap();

        let x2 = unsafe {
            vulkano::pipeline::shader::ShaderModule::from_words(self.device.clone(), &shader.vertex)
        }.unwrap();

        let frag_entry_point : GraphicsEntryPoint<MySpecConstants, FragInput, FragOutput, FragLayout> = unsafe {
            x1.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"),
                                    vulkano_entry.frag_input,
                                    vulkano_entry.frag_output,
                                    vulkano_entry.frag_layout,
                                   GraphicsShaderType::Fragment)
        };

        let vert_entry_point: GraphicsEntryPoint<MySpecConstants, VertInput, VertOutput, VertLayout>  = unsafe {
            x2.graphics_entry_point(CStr::from_bytes_with_nul_unchecked(b"main\0"),
                                    vulkano_entry.vert_input,
                                    vulkano_entry.vert_output,
                                    vulkano_entry.vert_layout,
                                   GraphicsShaderType::Vertex)
        };

        // The next step is to create a *render pass*, which is an object that describes where the
        // output of the graphics pipeline will go. It describes the layout of the images
        // where the colors, depth and/or stencil information will be written.
        let render_pass = Arc::new(vulkano::single_pass_renderpass!(
            self.device.clone(),
            attachments: {
                // `color` is a custom name we give to the first and only attachment.
                color: {
                    // `load: Clear` means that we ask the GPU to clear the content of this
                    // attachment at the start of the drawing.
                    load: Clear,
                    // `store: Store` means that we ask the GPU to store the output of the draw
                    // in the actual image. We could also ask it to discard the result.
                    store: Store,
                    // `format: <ty>` indicates the type of the format of the image. This has to
                    // be one of the types of the `vulkano::format` module (or alternatively one
                    // of your structs that implements the `FormatDesc` trait). Here we use the
                    // same format as the swapchain.
                    format: self.swapchain.clone().unwrap().clone().format(),
                    // TODO:
                    samples: 1,
                }
            },
            pass: {
                // We use the attachment named `color` as the one and only color attachment.
                color: [color],
                // No depth-stencil attachment is indicated with empty brackets.
                depth_stencil: {}
            }
        ).unwrap());


//      Before we draw we have to create what is called a pipeline. This is similar to an OpenGL
//      program, but much more specific.
        let pipeline = GraphicsPipeline::start()
            // We need to indicate the layout of the vertices.
            // The type `SingleBufferDefinition` actually contains a template parameter corresponding
            // to the type of each vertex. But in this code it is automatically inferred.
//            .vertex_input_single_buffer()
            // A Vulkan shader can in theory contain multiple entry points, so we have to specify
            // which one. The `main` word of `main_entry_point` actually corresponds to the name of
            // the entry point.
            .vertex_shader(vert_entry_point, MySpecConstants {
                my_integer_constant: 0,
                a_boolean: 0,
                floating_point: 0.0
            })
            // The content of the vertex buffer describes a list of triangles.
            .triangle_list()
            // Use a resizable viewport set to draw over the entire window
            .viewports_dynamic_scissors_irrelevant(1)
            // See `vertex_shader`.
            .fragment_shader(frag_entry_point, MySpecConstants {
                my_integer_constant: 0,
                a_boolean: 0,
                floating_point: 0.0
            })
            // We have to indicate which subpass of which render pass this pipeline is going to be used
            // in. The pipeline will only be usable from this particular subpass.
            .render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
            // Now that our builder is filled, we call `build()` to obtain an actual pipeline.
            .build(self.device.clone())
            .unwrap();


        self.pipeline = Option::Some(Arc::new(pipeline));
    }

    pub fn create_renderpass(&mut self) {

        let render_pass = Arc::new(vulkano::single_pass_renderpass!(
            self.device.clone(),
            attachments: {
                // `color` is a custom name we give to the first and only attachment.
                color: {
                    // `load: Clear` means that we ask the GPU to clear the content of this
                    // attachment at the start of the drawing.
                    load: Clear,
                    // `store: Store` means that we ask the GPU to store the output of the draw
                    // in the actual image. We could also ask it to discard the result.
                    store: Store,
                    // `format: <ty>` indicates the type of the format of the image. This has to
                    // be one of the types of the `vulkano::format` module (or alternatively one
                    // of your structs that implements the `FormatDesc` trait). Here we use the
                    // same format as the swapchain.
                    format: self.swapchain.clone().unwrap().clone().format(),
                    // TODO:
                    samples: 1,
                }
            },
            pass: {
                // We use the attachment named `color` as the one and only color attachment.
                color: [color],
                // No depth-stencil attachment is indicated with empty brackets.
                depth_stencil: {}
            }
        ).unwrap());
    }

    pub fn load_buffers(&mut self, image_filename: String)
    {
        let project_root =
            std::env::current_dir()
                .expect("failed to get root directory");

        let mut compute_path = project_root.clone();
        compute_path.push(PathBuf::from("resources/images/"));
        compute_path.push(PathBuf::from(image_filename));

        let img = image::open(compute_path).expect("Couldn't find image");

        self.xy = img.dimensions();

        let data_length = self.xy.0 * self.xy.1 * 4;
        let pixel_count = img.raw_pixels().len();
        println!("Pixel count {}", pixel_count);

        if pixel_count != data_length as usize {
            println!("Creating apha channel...");
            for i in img.raw_pixels().iter() {
                if (self.image_buffer.len() + 1) % 4 == 0 {
                    self.image_buffer.push(255);
                }
                self.image_buffer.push(*i);
            }
            self.image_buffer.push(255);
        } else {
            self.image_buffer = img.raw_pixels();
        }

        println!("Buffer length {}", self.image_buffer.len());
        println!("Size {:?}", self.xy);

        println!("Allocating Buffers...");

        // Pull out the image data and place it in a buffer for the kernel to write to and for us to read from
        let write_buffer = {
            let mut buff = self.image_buffer.iter();
            let data_iter = (0..data_length).map(|n| *(buff.next().unwrap()));
            CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::all(), data_iter).unwrap()
        };


        // Pull out the image data and place it in a buffer for the kernel to read from
        let read_buffer = {
            let mut buff = self.image_buffer.iter();
            let data_iter = (0..data_length).map(|n| *(buff.next().unwrap()));
            CpuAccessibleBuffer::from_iter(self.device.clone(), BufferUsage::all(), data_iter).unwrap()
        };


        // A buffer to hold many i32 values to use as settings
        let settings_buffer = {
            let vec = vec![self.xy.0, self.xy.1];
            let mut buff = vec.iter();
            let data_iter =
                (0..2).map(|n| *(buff.next().unwrap()));
            CpuAccessibleBuffer::from_iter(self.device.clone(),
                                           BufferUsage::all(),
                                           data_iter).unwrap()
        };

        println!("Done");

        // Create the data descriptor set for our previously created shader pipeline
        let mut set =
            PersistentDescriptorSet::start(self.compute_pipeline.clone().unwrap().clone(), 0)
            .add_buffer(write_buffer.clone()).unwrap()
            .add_buffer(read_buffer.clone()).unwrap()
            .add_buffer(settings_buffer.clone()).unwrap();

        self.set = Some(Arc::new(set.build().unwrap()));

        self.img_buffers.push(write_buffer);
        self.img_buffers.push(read_buffer);
        self.settings_buffer = Some(settings_buffer);
    }

    pub fn run_kernel(&mut self) {

        println!("Running Kernel...");

        // The command buffer I think pretty much serves to define what runs where for how many times
        let command_buffer =
            AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(),self.queue.family()).unwrap()
            .dispatch([self.xy.0, self.xy.1, 1],
                      self.compute_pipeline.clone().unwrap().clone(),
                      self.set.clone().unwrap().clone(), ()).unwrap()
            .build().unwrap();

        // Create a future for running the command buffer and then just fence it
        let future = sync::now(self.device.clone())
            .then_execute(self.queue.clone(), command_buffer).unwrap()
            .then_signal_fence_and_flush().unwrap();

        // I think this is redundant and returns immediately
        future.wait(None).unwrap();
        println!("Done running kernel");
    }

    pub fn read_image(&self) -> Vec<u8> {

        // The buffer is sync'd so we can just read straight from the handle
        let mut data_buffer_content = self.img_buffers.get(0).unwrap().read().unwrap();

        println!("Reading output");

        let mut image_buffer = Vec::new();

        for y in 0..self.xy.1 {
            for x in 0..self.xy.0 {

                let r = data_buffer_content[((self.xy.0 * y + x) * 4 + 0) as usize] as u8;
                let g = data_buffer_content[((self.xy.0 * y + x) * 4 + 1) as usize] as u8;
                let b = data_buffer_content[((self.xy.0 * y + x) * 4 + 2) as usize] as u8;
                let a = data_buffer_content[((self.xy.0 * y + x) * 4 + 3) as usize] as u8;

                image_buffer.push(r);
                image_buffer.push(g);
                image_buffer.push(b);
                image_buffer.push(a);
            }
        }

        image_buffer
    }

    pub fn save_image(&self) {
        println!("Saving output");

        let img_data = self.read_image();

        let img = ImageBuffer::from_fn(self.xy.0, self.xy.1, |x, y| {

            let r = img_data[((self.xy.0 * y + x) * 4 + 0) as usize] as u8;
            let g = img_data[((self.xy.0 * y + x) * 4 + 1) as usize] as u8;
            let b = img_data[((self.xy.0 * y + x) * 4 + 2) as usize] as u8;
            let a = img_data[((self.xy.0 * y + x) * 4 + 3) as usize] as u8;

            image::Rgba([r, g, b, a])
        });

        img.save(format!("output/{}.png", SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()));
    }
}


















