use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use vulkano::device::{Device, DeviceExtensions, Queue};
use vulkano::format::{D16Unorm, Format};
use vulkano::framebuffer::{Framebuffer, FramebufferAbstract, RenderPassAbstract, Subpass};
use vulkano::image::{AttachmentImage, SwapchainImage};
use vulkano::instance::{Instance, PhysicalDevice};
use vulkano::pipeline::viewport::Viewport;
use vulkano::swapchain::{self, AcquireError, PresentMode, Surface, SurfaceTransform, Swapchain,
                         SwapchainCreationError};
use vulkano::sync::{FlushError, GpuFuture};
use vulkano_win::{self, VkSurfaceBuild};
use winit::{EventsLoop, Window, WindowBuilder};

use std::iter;
use std::sync::Arc;

use {quit, quit_msg, ScreenDimensions, d2, d3};

pub struct Renderer {
    pub(crate) d2: d2::Renderer,
    pub(crate) d3: d3::Renderer,
    pub(crate) queue: Arc<Queue>,
    surface: Arc<Surface<Window>>,
    swapchain: Arc<Swapchain<Window>>,
    render_pass: Arc<RenderPassAbstract + Send + Sync>,
    last_frame: Option<Box<GpuFuture + Send + Sync>>,
    framebuffers: Vec<Arc<FramebufferAbstract + Send + Sync>>,
}

impl Renderer {
    pub fn new(events_loop: &EventsLoop, builder: WindowBuilder) -> Self {
        let instance = {
            let extensions = vulkano_win::required_extensions();
            Instance::new(None, &extensions, None).unwrap_or_else(quit)
        };

        let phys = PhysicalDevice::enumerate(&instance)
            .next()
            .unwrap_or_else(|| quit_msg("no device available"));
        info!("Using device: {} (type: {:?}).", phys.name(), phys.ty());

        let surface = builder
            .build_vk_surface(&events_loop, Arc::clone(&instance))
            .unwrap_or_else(quit);

        let queue_family = phys.queue_families()
            .find(|&q| q.supports_graphics() && surface.is_supported(q).unwrap_or(false))
            .unwrap_or_else(|| quit_msg("couldn't find a graphical queue family"));

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

            Device::new(
                phys,
                phys.supported_features(),
                &device_ext,
                iter::once((queue_family, 0.5)),
            ).unwrap_or_else(quit)
        };

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

        let caps = surface
            .capabilities(queue.device().physical_device())
            .unwrap_or_else(quit);
        let format = caps.supported_formats
            .first()
            .unwrap_or_else(|| quit_msg("surface has no supported formats"));
        let alpha = caps.supported_composite_alpha
            .iter()
            .next()
            .unwrap_or_else(|| quit_msg("surface has no supported alpha modes"));

        let (w, h) = surface.window().get_inner_size().unwrap();
        let (swapchain, images) = Swapchain::new(
            Arc::clone(queue.device()),
            Arc::clone(&surface),
            caps.min_image_count,
            format.0,
            [w, h],
            1,
            caps.supported_usage_flags,
            &queue,
            SurfaceTransform::Identity,
            alpha,
            PresentMode::Mailbox,
            true,
            None,
        ).unwrap_or_else(quit);

        let render_pass = Arc::new(
            ordered_passes_renderpass!(Arc::clone(queue.device()),
            attachments: {
                color: {
                    load: Clear,
                    store: Store,
                    format: swapchain.format(),
                    samples: 1,
                },
                depth: {
                    load: Clear,
                    store: DontCare,
                    format: Format::D16Unorm,
                    samples: 1,
                }
            },
            passes: [
                {
                    color: [color],
                    depth_stencil: {depth},
                    input: []
                },
                {
                    color: [color],
                    depth_stencil: { },
                    input: []
                }
            ]
        ).unwrap_or_else(quit),
        ) as Arc<RenderPassAbstract + Send + Sync>;

        let d3 = d3::Renderer::new(
            Arc::clone(&queue.device()),
            Subpass::from(Arc::clone(&render_pass), 0).unwrap(),
        );
        let d2 = d2::Renderer::new(
            Arc::clone(&queue.device()),
            Subpass::from(Arc::clone(&render_pass), 1).unwrap(),
        );

        let depth_buffer = AttachmentImage::transient(Arc::clone(queue.device()), [w, h], D16Unorm)
            .unwrap_or_else(quit);
        let framebuffers = create_framebuffers(Arc::clone(&render_pass), images, depth_buffer);

        Renderer {
            surface,
            queue,
            swapchain,
            framebuffers,
            render_pass,
            last_frame: None,
            d2,
            d3,
        }
    }

    pub fn dimensions(&self) -> ScreenDimensions {
        self.swapchain.dimensions().into()
    }

    fn new_dimensions(&self) -> ScreenDimensions {
        self.surface
            .capabilities(self.queue.device().physical_device())
            .unwrap_or_else(quit)
            .current_extent
            .unwrap()
            .into()
    }

    pub fn render<D3: d3::Draw, D2: d2::Draw>(
        &mut self,
        d3: &D3,
        d2: &D2,
        dim: &mut ScreenDimensions,
    ) {
        if let Some(ref mut last_frame) = self.last_frame {
            last_frame.cleanup_finished();
        }

        let mut swapchain_dirty = false;
        for _ in 0..5 {
            if swapchain_dirty {
                if self.recreate_swapchain(dim) {
                    trace!("Recreate swapchain succeeded.");
                    swapchain_dirty = false;
                } else {
                    trace!("Recreate swapchain failed.");
                    break;
                }
            } else {
                if self.try_render(d3, d2, dim) {
                    trace!("Draw succeeded.");
                    break;
                } else {
                    trace!("Draw failed.");
                    swapchain_dirty = true;
                }
            }
        }
    }

    fn recreate_swapchain(&mut self, dim: &mut ScreenDimensions) -> bool {
        let new_dim = self.new_dimensions();
        match self.swapchain.recreate_with_dimension(new_dim.into()) {
            Ok((swapchain, images)) => {
                self.swapchain = swapchain;
                let depth_buffer = AttachmentImage::transient(
                    Arc::clone(self.queue.device()),
                    new_dim.into(),
                    D16Unorm,
                ).unwrap_or_else(quit);
                self.framebuffers =
                    create_framebuffers(Arc::clone(&self.render_pass), images, depth_buffer);
                *dim = new_dim;
                true
            }
            Err(SwapchainCreationError::UnsupportedDimensions) => false,
            Err(err) => quit(err),
        }
    }

    fn try_render<D3: d3::Draw, D2: d2::Draw>(
        &mut self,
        d3: &D3,
        d2: &D2,
        dim: &mut ScreenDimensions,
    ) -> bool {
        let (image_num, acquire) = match swapchain::acquire_next_image(self.swapchain.clone(), None)
        {
            Ok(r) => r,
            Err(AcquireError::OutOfDate) => return false,
            Err(err) => quit(err),
        };

        let fb = Arc::clone(&self.framebuffers[image_num]);

        let state = DynamicState {
            line_width: None,
            viewports: Some(vec![
                Viewport {
                    origin: [0.0, 0.0],
                    dimensions: [dim.w as f32, dim.h as f32],
                    depth_range: 0.0..1.0,
                },
            ]),
            scissors: None,
        };

        let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(
            Arc::clone(self.queue.device()),
            self.queue.family(),
        ).unwrap_or_else(quit)
            .begin_render_pass(fb, false, vec![[0.0, 0.0, 0.0, 1.0].into(), 1f32.into()])
            .unwrap_or_else(quit);
        let command_buffer = self.d3
            .draw(command_buffer, d3, state.clone())
            .next_subpass(false)
            .unwrap_or_else(quit);
        let command_buffer = self.d2
            .draw(command_buffer, d2, state)
            .end_render_pass()
            .unwrap_or_else(quit)
            .build()
            .unwrap_or_else(quit);

        let future = match self.last_frame.take() {
            Some(last_frame) => last_frame
                .join(acquire)
                .then_execute(Arc::clone(&self.queue), command_buffer)
                .unwrap_or_else(quit)
                .then_swapchain_present(
                    Arc::clone(&self.queue),
                    Arc::clone(&self.swapchain),
                    image_num,
                )
                .then_signal_fence_and_flush()
                .map(|f| Box::new(f) as Box<GpuFuture + Send + Sync>),
            None => acquire
                .then_execute(Arc::clone(&self.queue), command_buffer)
                .unwrap_or_else(quit)
                .then_swapchain_present(
                    Arc::clone(&self.queue),
                    Arc::clone(&self.swapchain),
                    image_num,
                )
                .then_signal_fence_and_flush()
                .map(|f| Box::new(f) as Box<GpuFuture + Send + Sync>),
        };

        match future {
            Ok(future) => self.last_frame = Some(future),
            Err(FlushError::OutOfDate) => return false,
            Err(err) => quit(err),
        }

        true
    }
}

fn create_framebuffers<I>(
    pass: Arc<RenderPassAbstract + Send + Sync>,
    images: I,
    dbuf: Arc<AttachmentImage<D16Unorm>>,
) -> Vec<Arc<FramebufferAbstract + Send + Sync>>
where
    I: IntoIterator<Item = Arc<SwapchainImage<Window>>>,
{
    images
        .into_iter()
        .map(|img| create_framebuffer(Arc::clone(&pass), img, Arc::clone(&dbuf)))
        .collect()
}

fn create_framebuffer(
    pass: Arc<RenderPassAbstract + Send + Sync>,
    img: Arc<SwapchainImage<Window>>,
    dbuf: Arc<AttachmentImage<D16Unorm>>,
) -> Arc<FramebufferAbstract + Send + Sync> {
    Arc::new(
        Framebuffer::start(pass)
            .add(img)
            .unwrap_or_else(quit)
            .add(dbuf)
            .unwrap_or_else(quit)
            .build()
            .unwrap_or_else(quit),
    )
}