use std::{convert::TryInto, io::{self, ErrorKind, Read, Write}, path::PathBuf, sync::Arc};

use vulkano::{command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}, descriptor::{descriptor_set::PersistentDescriptorSet}, image::view::ImageView, pipeline::{shader::{ShaderModule}}, render_pass::{RenderPass, Subpass}};
use vulkano::command_buffer::DynamicState;
use vulkano::device::Device;
use vulkano::pipeline::GraphicsPipeline;
use vulkano::pipeline::GraphicsPipelineAbstract;

use crate::GameObject;
use crate::vulkan::{LinePoint, Vertex};
use crate::vulkan::GameData;
use crate::VulkanRenderer;

type RP = Arc<RenderPass>;
type GP = Arc<dyn GraphicsPipelineAbstract + Send + Sync>;

pub trait Drawcall {
    fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState);
    fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer);
    fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP);
    fn get_pipeline(self: &Self) -> &GP;
}

pub mod vs {
    vulkano_shaders::shader!{
        ty: "vertex",
        path: "shaders/triangle.vert"
    }
}

pub mod fs {
    vulkano_shaders::shader! {
        ty: "fragment",
        path: "shaders/triangle.frag"
    }
}

#[derive(Clone)]
pub struct DefaultShader {
    pipeline: GP,
}

fn shader_module_from_file(device: Arc<Device>, path: &str) -> Arc<ShaderModule> {
    let mut file = std::fs::File::open(path).unwrap();
    let mut buffer = vec![];
    file.read_to_end(&mut buffer).unwrap();
    let words: Vec<u32> = buffer.chunks_exact(4).map(|c| u32::from_ne_bytes(c.try_into().unwrap())).collect();
    unsafe {
        ShaderModule::from_words(device.clone(), &words).unwrap()
    }
}

fn matches_extension(path: &PathBuf, extensions: &Vec<&str>) -> bool {
    if let Some(Some(path_extension)) = path.extension().map(|e| e.to_str()) {
        for extension in extensions {
            if *extension == path_extension { return true; }
        }
    }
    return false;
}

fn compile_shaders() -> io::Result<()> {
    for file_maybe in std::fs::read_dir("./shaders")? {
        let path = file_maybe?.path();
        if !path.is_dir() && matches_extension(&path, &vec!["frag", "vert"]) {
            let mut target_path = path.to_str().ok_or(ErrorKind::Other)?.to_string();
            target_path.push_str(".spv");

            let output = std::process::Command::new("glslangValidator")
                .arg("-V")
                .arg(path.to_str().ok_or(ErrorKind::Other)?)
                .arg("-o")
                .arg(target_path)
                .output().unwrap();
            std::io::stdout().write_all(&output.stdout)?;

            if !output.status.success() {
                eprintln!("Shader compiler {:?}", output.status);
            }
        }
    }
    Ok(())
}

impl DefaultShader {
    pub fn new(device: Arc<Device>, render_pass: RP) -> Self {
        DefaultShader {
            pipeline: Self::create_pipeline(device, render_pass)
        }
    }

    fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP {
        let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();

        #[cfg(debug_assertions)]
        {
            println!("Compiling shaders...");
            compile_shaders().unwrap();
        }

        // unsafe {

            // static ENTRY_NAME: [u8; 5usize] = [109u8, 97u8, 105u8, 110u8, 0];
            // let entry_name_c = std::ffi::CStr::from_ptr(ENTRY_NAME.as_ptr() as *const _);

            // let fs_module = shader_module_from_file(device.clone(), "shaders/triangle.frag.spv");
            // let fs_entry = fs_module.graphics_entry_point(entry_name_c, yeet, &[], ref_shader.main_entry_point().input().clone(), ref_shader.main_entry_point().output().clone(), GraphicsShaderType::Fragment);

            // let vs_module = shader_module_from_file(device.clone(), "shaders/triangle.vert.spv");
            // let vs_layout = vs::Layout(ShaderStages {
            //     vertex: true,
            //     ..ShaderStages::none()
            // });
            // let vs_entry = vs_module.graphics_entry_point(entry_name_c, vs_layout, &[], vs::MainInput, vs::MainOutput, GraphicsShaderType::Vertex);
        
            let fs = fs::Shader::load(device.clone()).unwrap();
            let vs = vs::Shader::load(device.clone()).unwrap();

            Arc::new(GraphicsPipeline::start()
                .vertex_input_single_buffer::<Vertex>()
                .vertex_shader(vs.main_entry_point(), ())
                .triangle_list()
                .viewports_dynamic_scissors_irrelevant(1)
                .depth_stencil_simple_depth()
                .fragment_shader(fs.main_entry_point(), ())
                .blend_alpha_blending()
                .cull_mode_back()
                .render_pass(sub_pass.clone())
                .build(device.clone())
                .unwrap())
        // }
    }
}

impl Drawcall for DefaultShader {
    fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState) {
        for i in 0..game_data.game_objects.len() {
            let game_object = &game_data.game_objects[i];
            let mesh = &game_data.meshes[game_object.mesh_index];
            let push_constants = game_object.get_push_constants();

            builder.draw_indexed(
                self.pipeline.clone(),
                dynamic_state,
                vec![mesh.vertex_buffer.clone()],
                mesh.index_buffer.clone(),
                game_object.descriptor_sets[fb_index].clone(),
                push_constants,
            vec![]).unwrap();
        }
    }

    fn create_descriptor_set(self: &Self, game_object: &mut GameObject, renderer: &VulkanRenderer) {
        let descriptor_set_layout = self.get_pipeline().layout().descriptor_set_layout(0).unwrap().clone();

        println!("Diff: {:?}, Norm: {:?}", game_object.texture_index, game_object.normal_map_index);

        game_object.descriptor_sets = renderer.uniform_buffers.iter().map(|uniform_buffer| {
            let descriptor_set: Arc<(dyn vulkano::descriptor::DescriptorSet + std::marker::Send + std::marker::Sync + 'static)>;
            let builder = PersistentDescriptorSet::start(descriptor_set_layout.clone());

            let diffuse = &renderer.game_data.textures[game_object.texture_index];
            let diffuse_view = ImageView::new(diffuse.image.clone()).unwrap();
            let normal_map = &renderer.game_data.textures[game_object.normal_map_index];
            let normal_view = ImageView::new(normal_map.image.clone()).unwrap();

            descriptor_set = Arc::new(builder
                .add_buffer(uniform_buffer.clone()).unwrap()
                .add_sampled_image(diffuse_view, diffuse.sampler.clone()).unwrap()
                .add_sampled_image(normal_view, normal_map.sampler.clone()).unwrap()
                .build().unwrap());

            descriptor_set
        }).collect();
    }

    fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) {
        self.pipeline = Self::create_pipeline(device, render_pass);
    }

    fn get_pipeline(self: &Self) -> &GP {
        &self.pipeline
    }
}

pub mod line_vs {
    vulkano_shaders::shader!{
        ty: "vertex",
        path: "shaders/line.vert"
    }
}

pub mod line_fs {
    vulkano_shaders::shader! {
        ty: "fragment",
        path: "shaders/line.frag"
    }
}

#[derive(Clone)]
pub struct LineShader {
    pipeline: GP,
    vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>
}

impl LineShader {
    pub fn new(device: Arc<Device>, render_pass: RP, vertex_buffer: Arc<vulkano::buffer::CpuAccessibleBuffer<[LinePoint]>>) -> Self {
        LineShader {
            pipeline: Self::create_pipeline(device, render_pass),
            vertex_buffer
        }
    }

    fn create_pipeline(device: Arc<Device>, render_pass: RP) -> GP {
        let sub_pass = Subpass::from(render_pass.clone(), 0).unwrap();
        let vertex_shader = line_vs::Shader::load(device.clone()).unwrap();
        let fragment_shader = line_fs::Shader::load(device.clone()).unwrap();

        Arc::new(GraphicsPipeline::start()
            .vertex_input_single_buffer::<LinePoint>()
            .vertex_shader(vertex_shader.main_entry_point(), ())
            .line_list()
            .viewports_dynamic_scissors_irrelevant(1)
            .depth_stencil_simple_depth()
            .fragment_shader(fragment_shader.main_entry_point(), ())
            .render_pass(sub_pass.clone())
            .build(device.clone())
            .unwrap())
    }
}

impl Drawcall for LineShader {
    fn draw(self: &Self, builder: &mut AutoCommandBufferBuilder<PrimaryAutoCommandBuffer>, _fb_index: usize, game_data: &GameData, dynamic_state: &DynamicState) {
        builder.draw(self.pipeline.clone(),
            &dynamic_state,
            vec![self.vertex_buffer.clone()],
            (),
            game_data.line_push_constants.clone(),
            vec![]).unwrap();
    }

    fn create_descriptor_set(self: &Self, _game_object: &mut GameObject, _renderer: &VulkanRenderer) {
        
    }

    fn recreate_pipeline(self: &mut Self, device: Arc<Device>, render_pass: RP) {
        self.pipeline = Self::create_pipeline(device, render_pass);
    }

    fn get_pipeline(self: &Self) -> &GP {
        &self.pipeline
    }
}