
use std::collections::HashSet;
use std::path::PathBuf;
use vulkano::pipeline::GraphicsPipelineAbstract;
use vulkano::framebuffer::RenderPassAbstract;
use std::sync::Arc;
use vulkano::pipeline::shader::{ShaderModule, GraphicsShaderType, GeometryShaderExecutionMode};
use vulkano::device::Device;
use shade_runner::Entry;
use shaderc::ShaderKind;

/*

Realistically, what should the API for this thing look like...

It's going to just generate a pipeline. But that consists of loading and compiling various shaders,
    and generating a pipeline for those shaders and other customer behaviour.

This best works I think if I allow users to
    A.) impl from a base trait which allows resource lookup
    B.) Generate 1 of each of the types of shaders
    C.) Modify specilization constants, whatever that might mean
    D.) impl from a base trait which defines it's interface

*/



/// Inheriting this gives private functions to grab resources
pub(super) trait CompiledGraphicsPipelineResources {

    fn get_path(filename: String, shader_type: ShaderType) -> 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 shader_path = shader_path.clone();

        match shader_type {
            ShaderType::VERTEX => {
                shader_path.push(PathBuf::from(filename.clone() + ".vert"));
            }
            ShaderType::FRAGMENT => {
                shader_path.push(PathBuf::from(filename.clone() + ".frag"));
            }
            ShaderType::GEOMETRY => {
                shader_path.push(PathBuf::from(filename.clone() + ".geom"));
            }
            ShaderType::TESSELLATION_CONTROL => {
                shader_path.push(PathBuf::from(filename.clone() + ".tesscont"));
            }
            ShaderType::TESSELLATION_EVALUATION => {
                shader_path.push(PathBuf::from(filename.clone() + ".tesseval"));
            }
        }

        shader_path
    }


    fn compile(filepath: PathBuf, device: Arc<Device>, shader_type: ShaderType) -> (Entry, Arc<ShaderModule>) {
        let compiled_shader = shade_runner::load(filepath, Self::convert_sr(shader_type))
            .expect("Shader didn't compile");

        let vulkano_entry =
            shade_runner::parse(&compiled_shader)
                .expect("failed to parse");

        (vulkano_entry, unsafe {
            ShaderModule::from_words(device.clone(), &compiled_shader.spriv.clone())
        }.unwrap())
    }

    fn convert_vk(shader_type: ShaderType) -> GraphicsShaderType {
        match shader_type {
            ShaderType::VERTEX => { GraphicsShaderType::Vertex }
            ShaderType::FRAGMENT => { GraphicsShaderType::Fragment }
            ShaderType::GEOMETRY => { GraphicsShaderType::Geometry(GeometryShaderExecutionMode::Triangles) }
            ShaderType::TESSELLATION_CONTROL => { GraphicsShaderType::TessellationControl }
            ShaderType::TESSELLATION_EVALUATION => { GraphicsShaderType::TessellationEvaluation }
        }
    }

    fn convert_sr(shader_type: ShaderType) -> ShaderKind {
        match shader_type {
            ShaderType::VERTEX => { ShaderKind::Vertex }
            ShaderType::FRAGMENT => { ShaderKind::Fragment }
            ShaderType::GEOMETRY => { ShaderKind::Geometry }
            ShaderType::TESSELLATION_CONTROL => { ShaderKind::TessControl }
            ShaderType::TESSELLATION_EVALUATION => { ShaderKind::TessEvaluation }
        }
    }
}

/// Typed wrapper for a u32 handle
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CompiledGraphicsPipelineHandle {
    pub handle: u32
}

pub trait CompiledGraphicsPipeline {
    fn new(filename: String,
           device: Arc<Device>,
           handle: Arc<CompiledGraphicsPipelineHandle>,
           render_pass: Arc<dyn RenderPassAbstract + Send + Sync>) -> Self where Self: Sized;
    fn get_name(&self) -> String;
    fn get_handle(&self) -> Arc<CompiledGraphicsPipelineHandle>;
    fn get_pipeline(&self) -> Arc<dyn GraphicsPipelineAbstract + Sync + Send>;
    fn recompile(self, render_pass: Arc<dyn RenderPassAbstract + Send + Sync>)
                 -> Self where Self: Sized;
}

/// Legacy ShaderType enum for single type shaders.
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum ShaderType {
    VERTEX = 0,
    FRAGMENT = 1,
    GEOMETRY = 2,
    TESSELLATION_CONTROL = 3,
    TESSELLATION_EVALUATION = 4,
}