use vulkano::device::{Device, Queue};
use vulkano::instance::{PhysicalDevice, QueueFamily};
use vulkano::pipeline::{GraphicsPipeline, GraphicsPipelineAbstract, GraphicsPipelineBuilder};
use std::sync::Arc;
use std::ffi::CStr;
use std::path::PathBuf;
use shade_runner as sr;
use vulkano::framebuffer::{Subpass, RenderPassAbstract, Framebuffer, FramebufferAbstract};
use vulkano::pipeline::shader::{GraphicsShaderType, ShaderModule, SpecializationConstants, SpecializationMapEntry};
use vulkano::swapchain::{Capabilities};
use crate::vertex_2d::{ColoredVertex2D, Vertex2D};

/*

CanvasShader holds the pipeline and render pass for the inputted shader source

*/

#[derive(Clone)]
pub struct CanvasShader {

    pub render_pass: Arc<dyn RenderPassAbstract + Send + Sync>,
    graphics_pipeline: Option<Arc<dyn GraphicsPipelineAbstract + Sync + Send>>,

    device: Arc<Device>,
}

impl CanvasShader {

    fn get_path(filename: String) -> (PathBuf, PathBuf) {

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

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

        (vertex_shader_path, fragment_shader_path)
    }

    pub fn get_pipeline(&self) -> Arc<dyn GraphicsPipelineAbstract + Sync + Send> {
        self.graphics_pipeline.clone().unwrap()
    }

    pub fn new_colored(filename: String,
               capabilities: Capabilities,
               queue: Arc<Queue>,
               physical: PhysicalDevice,
               device: Arc<Device>) -> CanvasShader {

        let format = capabilities.supported_formats[0].0;

        let filenames = CanvasShader::get_path(filename.clone());

        // TODO: better compile message, run til successful compile
        let shader = sr::load(filenames.0, filenames.1)
            .expect("Shader didn't compile");

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

        let fragment_shader_module: Arc<ShaderModule> = unsafe {
            let filenames1 = CanvasShader::get_path(filename.clone());
            let shader1 = sr::load(filenames1.0, filenames1.1)
                .expect("Shader didn't compile");
            vulkano::pipeline::shader::ShaderModule::from_words(device.clone(), &shader1.fragment.clone())
        }.unwrap();

        let vertex_shader_module: Arc<ShaderModule> = unsafe {
            let filenames1 = CanvasShader::get_path(filename.clone());
            let shader1 = sr::load(filenames1.0, filenames1.1)
                .expect("Shader didn't compile");
            vulkano::pipeline::shader::ShaderModule::from_words(device.clone(), &shader1.vertex.clone())
        }.unwrap();

        let filenames = CanvasShader::get_path(filename.clone());


        let frag_entry_point = unsafe {
            Some(fragment_shader_module.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 vertex_entry_point = unsafe {
            Some(vertex_shader_module.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))
        };


        let render_pass = Arc::new(vulkano::single_pass_renderpass!(
            device.clone(),

            // Attachments are outgoing like f_color
            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: format,
                    // TODO:
                    samples: 1,
                }
            },
            pass: {
                // We use the attachment named `color` as the one and only color attachment.
                color: [color],
                //color: [],
                // No depth-stencil attachment is indicated with empty brackets.
                depth_stencil: {}
            }
        ).unwrap());


        CanvasShader {

            graphics_pipeline: Some(Arc::new(GraphicsPipeline::start()

                .vertex_input_single_buffer::<ColoredVertex2D>()

                .vertex_shader(vertex_entry_point.clone().unwrap(), ShaderSpecializationConstants {
                    first_constant: 0,
                    second_constant: 0,
                    third_constant: 0.0,
                })

                .triangle_list()
                // Use a resizable viewport set to draw over the entire window
                .viewports_dynamic_scissors_irrelevant(1)

                .fragment_shader(frag_entry_point.clone().unwrap(), ShaderSpecializationConstants {
                    first_constant: 0,
                    second_constant: 0,
                    third_constant: 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())

                .build(device.clone())
                .unwrap())),

            device: device,
            render_pass: render_pass,
        }
    }

    pub fn new_textured(filename: String,
                       capabilities: Capabilities,
                       queue: Arc<Queue>,
                       physical: PhysicalDevice,
                       device: Arc<Device>) -> CanvasShader {

        let format = capabilities.supported_formats[0].0;

        let filenames = CanvasShader::get_path(filename.clone());

        // TODO: better compile message, run til successful compile
        let shader = sr::load(filenames.0, filenames.1)
            .expect("Shader didn't compile");

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

        let fragment_shader_module: Arc<ShaderModule> = unsafe {
            let filenames1 = CanvasShader::get_path(filename.clone());
            let shader1 = sr::load(filenames1.0, filenames1.1)
                .expect("Shader didn't compile");
            vulkano::pipeline::shader::ShaderModule::from_words(device.clone(), &shader1.fragment.clone())
        }.unwrap();

        let vertex_shader_module: Arc<ShaderModule> = unsafe {
            let filenames1 = CanvasShader::get_path(filename.clone());
            let shader1 = sr::load(filenames1.0, filenames1.1)
                .expect("Shader didn't compile");
            vulkano::pipeline::shader::ShaderModule::from_words(device.clone(), &shader1.vertex.clone())
        }.unwrap();

        let filenames = CanvasShader::get_path(filename.clone());


        let frag_entry_point = unsafe {
            Some(fragment_shader_module.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 vertex_entry_point = unsafe {
            Some(vertex_shader_module.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))
        };


        let render_pass = Arc::new(vulkano::single_pass_renderpass!(
            device.clone(),

            // Attachments are outgoing like f_color
            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: format,
                    // TODO:
                    samples: 1,
                }
            },
            pass: {
                // We use the attachment named `color` as the one and only color attachment.
                color: [color],
                //color: [],
                // No depth-stencil attachment is indicated with empty brackets.
                depth_stencil: {}
            }
        ).unwrap());


        CanvasShader {

            graphics_pipeline: Some(Arc::new(GraphicsPipeline::start()

                .vertex_input_single_buffer::<Vertex2D>()

                .vertex_shader(vertex_entry_point.clone().unwrap(), ShaderSpecializationConstants {
                    first_constant: 0,
                    second_constant: 0,
                    third_constant: 0.0,
                })

                .triangle_list()
                // Use a resizable viewport set to draw over the entire window
                .viewports_dynamic_scissors_irrelevant(1)

                .fragment_shader(frag_entry_point.clone().unwrap(), ShaderSpecializationConstants {
                    first_constant: 0,
                    second_constant: 0,
                    third_constant: 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())

                .build(device.clone())
                .unwrap())),

            device: device,
            render_pass: render_pass,
        }
    }

}

#[repr(C)]
#[derive(Default, Debug, Clone)]
// TODO: This needs to be duplicated and moved into their respective containers shaderkenrels copute
struct ShaderSpecializationConstants {
    first_constant: i32,
    second_constant: u32,
    third_constant: f32,
}

unsafe impl SpecializationConstants for ShaderSpecializationConstants {
    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
    }
}