mod geom;

pub use self::geom::Point;

use std::ops::Deref;
use std::sync::Arc;

use vulkano::buffer::{BufferUsage, CpuBufferPool};
use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use vulkano::descriptor::PipelineLayoutAbstract;
use vulkano::device::Device;
use vulkano::framebuffer::{RenderPassAbstract, Subpass};
use vulkano::pipeline::GraphicsPipeline;
use vulkano::pipeline::vertex::SingleBufferDefinition;

use {quit, Color};

pub trait Draw {
    fn draw(&self, visitor: &mut FnMut(&[Point], Color));
}

impl<T> Draw for T
where
    T: Deref + ?Sized,
    T::Target: Draw,
{
    fn draw(&self, visitor: &mut FnMut(&[Point], Color)) {
        self.deref().draw(visitor)
    }
}

pub(crate) struct Renderer {
    cpu_buf: CpuBufferPool<Vertex>,
    pipe: Arc<
        GraphicsPipeline<
            SingleBufferDefinition<Vertex>,
            Box<PipelineLayoutAbstract + Send + Sync>,
            Arc<RenderPassAbstract + Send + Sync>,
        >,
    >,
}

impl Renderer {
    pub(crate) fn new(
        device: Arc<Device>,
        subpass: Subpass<Arc<RenderPassAbstract + Send + Sync>>,
    ) -> Self {
        let vs = vs::Shader::load(Arc::clone(&device)).unwrap_or_else(quit);
        let fs = fs::Shader::load(Arc::clone(&device)).unwrap_or_else(quit);

        let pipe = Arc::new(
            GraphicsPipeline::start()
                .vertex_input_single_buffer::<Vertex>()
                .vertex_shader(vs.main_entry_point(), ())
                .triangle_list()
                .viewports_dynamic_scissors_irrelevant(1)
                .fragment_shader(fs.main_entry_point(), ())
                .render_pass(subpass)
                .build(Arc::clone(&device))
                .unwrap_or_else(quit),
        );

        let usage = BufferUsage::vertex_buffer_transfer_destination();
        let cpu_buf = CpuBufferPool::new(device, usage);

        Renderer { pipe, cpu_buf }
    }

    pub(crate) fn draw<D: Draw>(
        &self,
        cmd: AutoCommandBufferBuilder,
        draw: &D,
        state: DynamicState,
    ) -> AutoCommandBufferBuilder {
        let mut vx_buf = Vec::new();
        draw.draw(&mut |vertices, color| {
            vx_buf.extend(vertices.iter().map(|&vx| Vertex::new(vx, color)));
        });
        let buf = self.cpu_buf.chunk(vx_buf).unwrap_or_else(quit);
        cmd.draw(Arc::clone(&self.pipe), state, buf, (), ())
            .unwrap_or_else(quit)
    }
}

#[derive(Copy, Clone, Debug)]
struct Vertex {
    position: Point,
    color: Color,
}

impl Vertex {
    fn new(position: Point, color: Color) -> Self {
        Vertex { position, color }
    }
}

impl_vertex!(Vertex, position, color);

#[allow(unused)]
mod vs {
    #[derive(VulkanoShader)]
    #[ty = "vertex"]
    #[path = "shader/vert2d.glsl"]
    struct Dummy;
}

#[allow(unused)]
mod fs {
    #[derive(VulkanoShader)]
    #[ty = "fragment"]
    #[path = "shader/frag2d.glsl"]
    struct Dummy;
}