use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use vulkano::device::{Device, DeviceExtensions, QueuesIter, Queue};
use vulkano::instance::{Instance, PhysicalDevice};
use vulkano::sync::{GpuFuture, FlushError};
use vulkano::sync;
use std::sync::Arc;
use vulkano::swapchain::{Swapchain, PresentMode, SurfaceTransform, Surface, SwapchainCreationError, AcquireError, Capabilities};
use vulkano::image::swapchain::SwapchainImage;
use winit::{Window};
use crate::util::compute_kernel::ComputeKernel;
use crate::util::compute_image::ComputeImage;
use crate::canvas::{Canvas, CanvasFrame};
use crate::compu_wip::{CompuState, ComputeFrame};
use vulkano::image::ImageUsage;

pub struct VkProcessor<'a> {

    // Vulkan state fields
    pub instance: Arc<Instance>,
    pub physical: PhysicalDevice<'a>,
    pub device: Arc<Device>,
    pub queues: QueuesIter,
    pub queue: Arc<Queue>,
    pub dynamic_state: DynamicState,

    pub compute_kernel: Option<ComputeKernel>,
    pub compute_image: Option<ComputeImage>,

    pub swapchain: Option<Arc<Swapchain<Window>>>,
    pub swapchain_images: Option<Vec<Arc<SwapchainImage<Window>>>>,

    swapchain_recreate_needed: bool,

    compute_state: CompuState,

    capabilities: Capabilities,
    canvas: Canvas,
}


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();

        let capabilities = surface.capabilities(physical).unwrap();

        VkProcessor {
            instance: instance.clone(),
            physical: physical.clone(),
            device: device.clone(),
            queue: queue.clone(),
            queues: queues,
            dynamic_state: DynamicState { line_width: None, viewports: None, scissors: None },
            compute_kernel: None,
            compute_image: None,
            swapchain: None,
            swapchain_images: None,
            swapchain_recreate_needed: false,
            compute_state: CompuState::new(),
            capabilities: capabilities.clone(),
            canvas: Canvas::new(queue, device, physical, capabilities),
        }
    }

    pub fn create_swapchain(&mut self, surface: &'a Arc<Surface<Window>>) {
        let (mut swapchain, images) = {
            let capabilities = surface.capabilities(self.physical).unwrap();
            let usage = capabilities.supported_usage_flags;
            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;

            // Set the swapchains window dimensions
            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.
                panic!("window closed");
            };

            Swapchain::new(self.device.clone(),
                           surface.clone(),
                           capabilities.min_image_count, // number of attachment images
                           format,
                           initial_dimensions,
                           1, // Layers
                           usage,
                           &self.queue,
                           SurfaceTransform::Identity,
                           alpha,
                           PresentMode::Fifo, true, None).unwrap()
        };

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

    // On resizes we have to recreate the swapchain
    pub fn recreate_swapchain(&mut self, surface: &'a Arc<Surface<Window>>) {
        let dimensions = if let Some(dimensions) = surface.window().get_inner_size() {
            let dimensions: (u32, u32) = dimensions.to_physical(surface.window().get_hidpi_factor()).into();
            [dimensions.0, dimensions.1]
        } else {
            return;
        };

        let (new_swapchain, new_images) = match self.swapchain.clone().unwrap().clone().recreate_with_dimension(dimensions) {
            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) => panic!("Uh oh"),
            Err(err) => panic!("{:?}", err)
        };

        self.swapchain = Some(new_swapchain);
        self.swapchain_images = Some(new_images);
    }

    pub fn preload_textures(&mut self) {
        self.canvas.load_texture_from_filename(String::from("funky-bird.jpg"));
    }
    pub fn preload_kernels(&mut self) {

    }
    pub fn preload_shaders(&mut self) {

    }

    pub fn get_texture_handle(&self, texture_name: String) -> Option<Arc<u32>> {
        None
    }
    pub fn get_kernel_handle(&self, kernel_name: String) -> Option<Arc<u32>> {
        None
    }
    pub fn get_shader_handle(&self, shader_name: String) -> Option<Arc<u32>> {
        None
    }

    // Create a new image which has the transfer usage
    pub fn new_swap_image(&mut self, dimensions: (u32, u32)) -> Arc<u32> {
        let mut usage = ImageUsage::none();
        usage.transfer_destination = true;
        usage.storage = true;

        self.canvas.create_image(dimensions, usage)
    }

    pub fn new_compute_buffer(&mut self, data: Vec<u8>, dimensions: (u32, u32), stride: u32) -> Arc<u32> {
        self.compute_state.new_compute_buffer(data, dimensions, stride, self.device.clone())
    }

    pub fn read_compute_buffer(&mut self, handle: Arc<u32>) -> Vec<u8> {
        self.compute_state.read_compute_buffer(handle)
    }

    pub fn write_compute_buffer(&self, handle: Arc<u32>, data: Vec<u8>) {
        self.compute_state.write_compute_buffer(handle, data)
    }

    pub fn run(&mut self,
        surface: &'a Arc<Surface<Window>>,
        mut frame_future: Box<dyn GpuFuture>,
        canvas_frame: CanvasFrame,
        compute_frame: ComputeFrame,
    )
        -> Box<dyn GpuFuture> {

        // take the canvas frame and create the vertex buffers
        self.canvas.draw(canvas_frame);

        // ditto with the compuframe
        self.compute_state.compute(compute_frame);

        let mut framebuffers =
            self.canvas.window_size_dependent_setup(&self.swapchain_images.clone().unwrap().clone());

        // The docs said to call this on each loop.
        frame_future.cleanup_finished();

        // Whenever the window resizes we need to recreate everything dependent on the window size.
        // In this example that includes the swapchain, the framebuffers and the dynamic state viewport.
        if self.swapchain_recreate_needed {
            self.recreate_swapchain(surface);
            framebuffers =
                self.canvas.window_size_dependent_setup(&self.swapchain_images.clone().unwrap().clone());
            self.swapchain_recreate_needed = false;
        }

        // 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 (image_num, acquire_future) = match vulkano::swapchain::acquire_next_image(self.swapchain.clone().unwrap().clone(), None) {
            Ok(r) => r,
            Err(AcquireError::OutOfDate) => {
                self.swapchain_recreate_needed = true;
                return Box::new(sync::now(self.device.clone())) as Box<_>;
            }
            Err(err) => panic!("{:?}", err)
        };

        let xy = self.compute_image.clone().unwrap().get_size();

        let mut command_buffer =
            AutoCommandBufferBuilder::primary_one_time_submit(self.device.clone(), self.queue.family())
                .unwrap()

                .dispatch([xy.0, xy.1, 1],
                          self.compute_kernel.clone().unwrap().clone().get_pipeline(),
                          self.compute_image.clone().unwrap().clone()
                              .get_descriptor_set(self.compute_kernel.clone().unwrap().clone().get_pipeline()).clone(), ()).unwrap()

                .copy_buffer_to_image(self.compute_image.clone().unwrap().clone().rw_buffers.get(0).unwrap().clone(),
                                      self.compute_image.clone().unwrap().clone().get_swap_buffer().clone()).unwrap();

        let mut command_buffer = self.canvas.draw_commands(command_buffer, framebuffers, image_num);

        let command_buffer = command_buffer.build().unwrap();

        // Wait on the previous frame, then execute the command buffer and present the image
        let future = frame_future.join(acquire_future)
            .then_execute(self.queue.clone(), command_buffer).unwrap()
            .then_swapchain_present(self.queue.clone(), self.swapchain.clone().unwrap().clone(), image_num)
            .then_signal_fence_and_flush();

        match future {
            Ok(future) => {
                (Box::new(future) as Box<_>)
            }
            Err(FlushError::OutOfDate) => {
                self.swapchain_recreate_needed = true;
                (Box::new(sync::now(self.device.clone())) as Box<_>)
            }
            Err(e) => {
                println!("{:?}", e);
                (Box::new(sync::now(self.device.clone())) as Box<_>)
            }
        }
    }
}



















