use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use vulkano::device::{Device, DeviceExtensions, QueuesIter, Queue};
use vulkano::instance::{Instance, PhysicalDevice};
use vulkano::sync::{GpuFuture, FlushError, NowFuture};
use vulkano::sync::now;
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::compute::compu_state::CompuState;
use vulkano::image::ImageUsage;
use crate::compute::compu_frame::CompuFrame;
use crate::canvas::canvas_state::{CanvasState, CanvasTextureHandle, CanvasImageHandle};
use crate::canvas::canvas_frame::CanvasFrame;
use crate::compute::compu_kernel::{CompuKernel, CompuKernelHandle};
use crate::compute::compu_buffer::{CompuBuffers, CompuBufferHandle};
use std::time::Duration;
use vulkano::pipeline::depth_stencil::{DynamicStencilValue, StencilFaceFlags};
use crate::canvas::shader::common::CompiledGraphicsPipelineHandle;
use crate::canvas::shader::generic_shader::GenericShader;
use crate::canvas::shader::text_shader::TextShader;

/// VKProcessor holds the vulkan instance information, the swapchain, and the compute and canvas states
///
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 swapchain: Option<Arc<Swapchain<Window>>>,
    pub swapchain_images: Option<Vec<Arc<SwapchainImage<Window>>>>,

    swapchain_recreate_needed: bool,

    compute_state: CompuState,

    capabilities: Capabilities,
    canvas_state: CanvasState,
}


impl<'a> VkProcessor<'a> {

    /// Creates a new VkProcessor from an instance and surface
    /// This includes the physical device, queues, compute and canvas state
    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,
            swapchain: None,
            swapchain_images: None,
            swapchain_recreate_needed: false,
            compute_state: CompuState::new(),
            capabilities: capabilities.clone(),
            canvas_state: CanvasState::new(queue, device, physical, capabilities),
        }
    }

    /// Using the surface, we calculate the surface capabilities and create the swapchain and swapchain images
    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::Immediate, true, None).unwrap()
        };

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

    /// On screen resizes, the swapchain and images must be recreated
    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);
    }

    /// A hardcoded list of textures which can be preloaded from this function
    pub fn preload_textures(&mut self) {
        self.canvas_state.load_texture(String::from("funky-bird.jpg"));
        self.canvas_state.load_texture(String::from("button.png"));
        self.canvas_state.load_texture(String::from("background.jpg"));
        self.canvas_state.load_texture(String::from("test2.png"));
        self.canvas_state.load_texture(String::from("sfml.png"));
    }

    /// A hardcoded list of kernels which can be preloaded from this function
    pub fn preload_kernels(&mut self) {
        self.compute_state.new_kernel(String::from("simple-homogenize.compute"), self.device.clone());
        self.compute_state.new_kernel(String::from("simple-edge.compute"), self.device.clone());
    }

    /// A hardcoded list of shaders which can be proloaded from this function
    pub fn preload_shaders(&mut self) {
        self.canvas_state.load_shader::<GenericShader>(String::from("color-passthrough"), self.physical.clone(), self.capabilities.clone());
        self.canvas_state.load_shader::<GenericShader>(String::from("simple_texture"), self.physical.clone(), self.capabilities.clone());
        self.canvas_state.load_shader::<GenericShader>(String::from("simple_image"), self.physical.clone(), self.capabilities.clone());
        self.canvas_state.load_shader::<TextShader>(String::from("simple_text"), self.physical.clone(), self.capabilities.clone());
    }

    /// O(n) Lookup for the matching texture string
    pub fn get_texture_handle(&self, texture_name: String) -> Option<Arc<CanvasTextureHandle>> {
        self.canvas_state.get_texture_handle(texture_name)
    }

    /// O(n) Lookup for the matching kernel string
    pub fn get_kernel_handle(&self, kernel_name: String) -> Option<Arc<CompuKernelHandle>> {
        self.compute_state.get_kernel_handle(kernel_name)
    }

    /// O(n) Lookup for the matching shader string
    pub fn get_shader_handle(&self, shader_name: String) -> Option<Arc<CompiledGraphicsPipelineHandle>> {
        self.canvas_state.get_shader_handle(shader_name)
    }

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

        self.canvas_state.create_image(dimensions, usage)
    }

    /// Builds a compute buffer and returns it's handle
    pub fn new_compute_buffer(&mut self, data: Vec<u8>, dimensions: (u32, u32), stride: u32) -> Arc<CompuBufferHandle> {
        self.compute_state.new_compute_buffer(data, dimensions, stride, self.device.clone())
    }

    /// Takes a compute buffer handle and returns the read data
    pub fn read_compute_buffer(&mut self, handle: Arc<CompuBufferHandle>) -> Vec<u8> {
        self.compute_state.read_compute_buffer(handle)
    }

    /// Takes a compute buffer handle and writes the received data
    pub fn write_compute_buffer(&self, handle: Arc<CompuBufferHandle>, data: Vec<u8>) {
        self.compute_state.write_compute_buffer(handle, data)
    }

    pub fn run(&mut self,
               surface: &'a Arc<Surface<Window>>,
               canvas_frame: CanvasFrame,
               compute_frame: CompuFrame,
    ) {

        {
            let g = hprof::enter("Waiting at queue");
            self.queue.wait();
        }

        let g = hprof::enter("Frame buffer, future, swapchain recreate");
        let mut framebuffers =
            self.canvas_state.window_size_dependent_setup(&self.swapchain_images.clone().unwrap().clone());

        // 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_state.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;
                }
                Err(err) => panic!("{:?}", err)
            };

        drop(g);

        {
            let g = hprof::enter("Canvas creates GPU buffers");
            // take the canvas frame and create the vertex buffers
            // TODO: This performs gpu buffer creation. Shouldn't be in hotpath??
            self.canvas_state.draw(canvas_frame);
        }

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

        let g = hprof::enter("Push compute commands to command buffer");
        // Add the compute commands
        let mut command_buffer = self.compute_state.compute_commands(compute_frame, command_buffer, &self.canvas_state);
        drop(g);

        let g = hprof::enter("Push draw commands to command buffer");
        
        // Add the draw commands
        let mut command_buffer = self.canvas_state.draw_commands(command_buffer, framebuffers, image_num);

        // And build
        let command_buffer = command_buffer.build().unwrap();
        drop(g);

        // Wait on the previous frame, then execute the command buffer and present the image
        {

            let g = hprof::enter("Joining on the framebuffer");
            let mut future = sync::now(self.device.clone())
                .join(acquire_future);
            drop(g);

            let g = hprof::enter("Running the kernel and waiting on the future");

            let future = 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) => {
                    future.wait(None).unwrap();
                }
                Err(FlushError::OutOfDate) => {
                    self.swapchain_recreate_needed = true;
                }
                Err(e) => {
                    println!("{:?}", e);
                }
            }
        }
    }
}


















