use crate::vertex_2d::{ColoredVertex2D, Vertex2D};
use vulkano::command_buffer::{AutoCommandBufferBuilder, DynamicState};
use std::collections::HashMap;
use vulkano::buffer::{BufferAccess, BufferUsage, ImmutableBuffer, CpuAccessibleBuffer};
use std::sync::Arc;
use vulkano::format::{ClearValue, Format};
use vulkano::framebuffer::{FramebufferAbstract, Framebuffer};
use vulkano::device::{Device, Queue};
use vulkano::instance::PhysicalDevice;
use vulkano::image::immutable::ImmutableImage;
use vulkano::image::{Dimensions, ImageAccess, ImageDimensions, SwapchainImage, ImageUsage, AttachmentImage};
use vulkano::sampler::{Sampler, SamplerAddressMode, MipmapMode, Filter};
use vulkano::descriptor::DescriptorSet;
use vulkano::descriptor::descriptor_set::PersistentDescriptorSet;
use std::path::PathBuf;
use image::GenericImageView;
use std::iter::FromIterator;
use vulkano::swapchain::Capabilities;
use winit::Window;
use vulkano::pipeline::viewport::Viewport;
use vulkano::descriptor::descriptor::DescriptorDescTy::TexelBuffer;
use crate::canvas_frame::CanvasFrame;
use std::hash::Hash;
use crate::canvas_shader::CanvasShader;
use crate::canvas_buffer::{CanvasImage, CanvasTexture};

// Canvas is the accumulator of Sprites for drawing

// Needs to know:
//   textured?
//   colored?
//   vertices

/*

If it is textured. It needs to be rendered with the texture shader which requires a separate
    graphics pipeline. Might as well have a new render pass as well.


So framebuffer is tied to the swapchains images as well as the renderpass

it appears that renderpass is tied to the individual shader


*/

// I want to be able to draw 2d sprites.

// These sprites might be textured or a single color

// All of the single colors will be grouped into one batch using colored vertices.
// The rest will be grouped by their texture and run individually


pub trait Vertex {
    fn position(&self) -> (f32, f32) {
        (0.0, 0.0)
    }

    fn color(&self) -> Option<(f32, f32, f32, f32)> {
        Some((0., 0., 0., 0.))
    }
}


impl Vertex for ColoredVertex2D {
    fn position(&self) -> (f32, f32) {
        (0.0, 0.0)
    }

    fn color(&self) -> Option<(f32, f32, f32, f32)> {
        Some((0., 0., 0., 0.))
    }
}

pub trait Drawable {
    fn get_vertices(&self) -> Vec<(f32, f32)>;
    fn get_color(&self) -> (f32, f32, f32, f32);
    fn get_texture_handle(&self) -> Option<Arc<CanvasTextureHandle>>;
    fn get_image_handle(&self) -> Option<Arc<CanvasImageHandle>>;
}

// Need three types of shaders. Solid, Textured, Image
#[derive(PartialEq, Eq, Hash, Clone)]
pub enum ShaderType {
    SOLID = 0,
    TEXTURED = 1,
    IMAGE = 2,
}


#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CanvasTextureHandle {
    pub handle: u32
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CanvasImageHandle {
    pub handle: u32
}

#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct CanvasShaderHandle {
    pub handle: u32
}

#[derive(Clone)]
pub struct CanvasState {
    dynamic_state: DynamicState,
    sampler: Arc<Sampler>,

    // hold the image, texture, and shader buffers the same was as we do CompuState
    image_buffers: Vec<Arc<CanvasImage>>,
    texture_buffers: Vec<Arc<CanvasTexture>>,
    shader_buffers: HashMap<String, Arc<CanvasShader>>,

    // Hold onto the vertices we get from the Compu and Canvas Frames
    // When the run comes around, push the vertices to the GPU
    colored_drawables: Vec<ColoredVertex2D>,
    colored_vertex_buffer: Vec<Arc<(dyn BufferAccess + std::marker::Send + std::marker::Sync)>>,

    textured_drawables: HashMap<Arc<CanvasTextureHandle>, Vec<Vec<Vertex2D>>>,
    textured_vertex_buffer: HashMap<Arc<CanvasTextureHandle>, Arc<(dyn BufferAccess + std::marker::Send + std::marker::Sync)>>,

    image_drawables: HashMap<Arc<CanvasImageHandle>, Vec<Vec<Vertex2D>>>,
    image_vertex_buffer: HashMap<Arc<CanvasImageHandle>, Arc<(dyn BufferAccess + std::marker::Send + std::marker::Sync)>>,

    // Looks like we gotta hold onto the queue for managing textures
    queue: Arc<Queue>,
    device: Arc<Device>,
}


impl CanvasState {
    // This method is called once during initialization, then again whenever the window is resized
    pub fn window_size_dependent_setup(&mut self, images: &[Arc<SwapchainImage<Window>>])
                                       -> Vec<Arc<dyn FramebufferAbstract + Send + Sync>> {
        let dimensions = images[0].dimensions();

        self.dynamic_state.viewports =
            Some(vec![Viewport {
                origin: [0.0, 0.0],
                dimensions: [dimensions.width() as f32, dimensions.height() as f32],
                depth_range: 0.0..1.0,
            }]);

        images.iter().map(|image| {
            Arc::new(
                Framebuffer::start(self.shader_buffers.get("color-passthrough").unwrap().render_pass.clone())
                    .add(image.clone()).unwrap()
                    .build().unwrap()
            ) as Arc<dyn FramebufferAbstract + Send + Sync>
        }).collect::<Vec<_>>()
    }

    // needs to take in the texture list
    pub fn new(queue: Arc<Queue>,
               device: Arc<Device>,
               physical: PhysicalDevice,
               capabilities: Capabilities) -> CanvasState {
        let solid_color_kernel = String::from("color-passthrough");
        let texture_kernel = String::from("simple_texture");

        CanvasState {
            dynamic_state: DynamicState { line_width: None, viewports: None, scissors: None },
            sampler: Sampler::new(device.clone(), Filter::Linear, Filter::Linear,
                                  MipmapMode::Nearest, SamplerAddressMode::Repeat, SamplerAddressMode::Repeat,
                                  SamplerAddressMode::Repeat, 0.0, 1.0, 0.0, 0.0).unwrap(),
            image_buffers: vec![],
            texture_buffers: vec![],
            shader_buffers: HashMap::from_iter(vec![
                (solid_color_kernel.clone(), Arc::new(CanvasShader::new_colored(solid_color_kernel.clone(),
                                                                                capabilities.clone(),
                                                                                queue.clone(),
                                                                                physical.clone(),
                                                                                device.clone()))
                ),
                (texture_kernel.clone(), Arc::new(CanvasShader::new_textured(texture_kernel.clone(),
                                                                             capabilities.clone(),
                                                                             queue.clone(),
                                                                             physical.clone(),
                                                                             device.clone()))
                ),
            ]),

            colored_drawables: vec![],
            colored_vertex_buffer: vec![],
            textured_drawables: HashMap::default(),
            textured_vertex_buffer: Default::default(),
            image_drawables: Default::default(),
            image_vertex_buffer: Default::default(),

            queue: queue.clone(),
            device: device.clone(),
        }
    }

    pub fn create_image(&mut self, dimensions: (u32, u32), usage: ImageUsage) -> Arc<CanvasImageHandle> {
        let handle = Arc::new(CanvasImageHandle { handle: self.image_buffers.len() as u32 });

        let image = CanvasImage {
            handle: handle.clone(),
            buffer: AttachmentImage::with_usage(
                self.device.clone(),
                [dimensions.0, dimensions.1],
                Format::R8G8B8A8Uint,
                usage).unwrap(),
            size: dimensions,
        };

        self.image_buffers.push(Arc::new(image));

        handle
    }

    pub fn get_image(&self, image_handle: Arc<CanvasImageHandle>) -> Arc<AttachmentImage> {
        self.image_buffers.get((*image_handle).clone().handle as usize).unwrap()
            .clone().buffer.clone()
    }

    // TODO Handle file not found gracefully
    fn get_texture_from_file(&self, image_filename: String) -> Arc<ImmutableImage<Format>> {
        let project_root =
            std::env::current_dir()
                .expect("failed to get root directory");

        let mut compute_path = project_root.clone();
        compute_path.push(PathBuf::from("resources/images/"));
        compute_path.push(PathBuf::from(image_filename));

        let img = image::open(compute_path).expect("Couldn't find image");

        let xy = img.dimensions();

        let data_length = xy.0 * xy.1 * 4;
        let pixel_count = img.raw_pixels().len();

        let mut image_buffer = Vec::new();

        if pixel_count != data_length as usize {
            println!("Creating apha channel...");
            for i in img.raw_pixels().iter() {
                if (image_buffer.len() + 1) % 4 == 0 {
                    image_buffer.push(255);
                }
                image_buffer.push(*i);
            }
            image_buffer.push(255);
        } else {
            image_buffer = img.raw_pixels();
        }

        let (texture, tex_future) = ImmutableImage::from_iter(
            image_buffer.iter().cloned(),
            Dimensions::Dim2d { width: xy.0, height: xy.1 },
            Format::R8G8B8A8Srgb,
            self.queue.clone(),
        ).unwrap();

        texture
    }

    pub fn load_texture(&mut self, filename: String) -> Option<Arc<CanvasTextureHandle>> {
        let texture_buffer = self.get_texture_from_file(filename.clone());

        let handle = Arc::new(CanvasTextureHandle {
            handle: self.texture_buffers.len() as u32
        });

        let texture = Arc::new(CanvasTexture {
            handle: handle.clone(),
            buffer: self.get_texture_from_file(filename.clone()),
            name: filename.clone(),
            size: (0, 0),
        });

        self.texture_buffers.push(texture);

        Some(handle)
    }

    pub fn get_texture_handle(&self, texture_name: String)
                              -> Option<Arc<CanvasTextureHandle>> {
        for i in self.texture_buffers.clone() {
            if i.name == texture_name {
                return Some(i.handle.clone());
            }
        }
        None
    }

    pub fn get_texture(&self, texture_handle: Arc<CanvasTextureHandle>)
                       -> Arc<ImmutableImage<Format>> {
        let handle = texture_handle.handle as usize;

        if let Some(i) = self.texture_buffers.get(handle) {
            return i.clone().buffer.clone();
        } else {
            panic!("{} : Texture not loaded", handle);
        }
    }

    // After done using this, need to call allocated vertex buffers
    pub fn draw(&mut self, canvas_frame: CanvasFrame) {
        self.textured_drawables = canvas_frame.textured_drawables;
        self.colored_drawables = canvas_frame.colored_drawables;
        self.image_drawables = canvas_frame.image_drawables;

        self.allocate_vertex_buffers(self.device.clone());
    }

    fn allocate_vertex_buffers(&mut self, device: Arc<Device>) {
        self.image_vertex_buffer.clear();


        /*
            So a bit of brainstorming with the shaders:

            I compile shaders into their respective buffers and add them to a descriptor set
            along with the textures or whatever other resource buffer

            So I'm gonna fix that texturing issue by adding vertex texture coordinate attributes

            Still don't really know how I'm gonna do this...

                * Going to definitely need to use the CpuAccessbileBuffer
                * Maybe calculate deltas between frames???
                *


        */
        //TODO should probably use cpu accessible buffer instead of recreating immutes each frame
        /*
          CpuAccessibleBuffer::from_iter(

                    device.clone(),
                    BufferUsage::vertex_buffer(),
                    self.colored_drawables.iter().cloned(),
                ).unwrap().0;
        */

        //if self.colored_vertex_buffer.len() == 0 {



        self.colored_vertex_buffer.clear();
        {
            let g = hprof::enter("Colored Vertex Buffer : From Data");
            self.colored_vertex_buffer.push(
                ImmutableBuffer::from_data(self.colored_drawables.clone(),
                                           BufferUsage::vertex_buffer(),
                                           self.queue.clone()).unwrap().0
            );
        }
        {
            let g = hprof::enter("Colored Vertex Buffer : From Iter");
            self.colored_vertex_buffer.push(
                ImmutableBuffer::from_iter(
                    self.colored_drawables.iter().cloned(),
                    BufferUsage::vertex_buffer(),
                    self.queue.clone(),
                ).unwrap().0
            );
        }


        self.colored_vertex_buffer.clear();
        //println!("{:?}", self.colored_vertex_buffer.last().unwrap().size());
        //self.colored_vertex_buffer.pop();
        // }
        //  println!("{} {}", self.textured_vertex_buffer.len() , self.textured_drawables.len());

        //   if self.textured_vertex_buffer.len() != self.textured_drawables.len() {
        self.textured_vertex_buffer.clear();
        for (k, v) in self.textured_drawables.drain() {
            {
                let g = hprof::enter("Textured Vertex Buffer : From Iter");
                self.textured_vertex_buffer.insert(
                    k.clone(),
                    ImmutableBuffer::from_iter(
                        v.first().unwrap().iter().cloned(),
                        BufferUsage::vertex_buffer(),
                        self.queue.clone(),
                    ).unwrap().0,
                );
            }
//                {
//                    let g = hprof::enter("Textured Vertex Buffer : From Data");
//                    self.textured_vertex_buffer.insert(
//                        k.clone(),
//                        ImmutableBuffer::from_data(v.first().unwrap().clone(),
//                                                   BufferUsage::vertex_buffer(),
//                                                   self.queue.clone()
//                        ).unwrap().0
//                    );
//                }
            //self.textured_vertex_buffer.pop();
        }
        //    }
    }

    fn get_solid_color_descriptor_set(&self, kernel: Arc<CanvasShader>) -> Box<dyn DescriptorSet + Send + Sync> {
        let o: Box<dyn DescriptorSet + Send + Sync> = Box::new(
            PersistentDescriptorSet::start(
                kernel.clone().get_pipeline().clone(), 0,
            ).build().unwrap());
        o
    }

    pub fn draw_commands(&self,
                         mut command_buffer: AutoCommandBufferBuilder,
                         framebuffers: Vec<Arc<dyn FramebufferAbstract + Send + Sync>>,
                         image_num: usize) -> AutoCommandBufferBuilder {

        // Specify the color to clear the framebuffer with i.e. blue
        let clear_values = vec!(ClearValue::Float([0.0, 0.0, 1.0, 1.0]));

        let mut command_buffer = command_buffer.begin_render_pass(
            framebuffers[image_num].clone(), false, clear_values.clone(),
        ).unwrap();

        // Solid colors
        let mut shader = self.shader_buffers.get("color-passthrough").unwrap().clone();

        // This looks a little weird as colored_vertex_buffer is a vec of GPU allocated vecs.
        // But we can pass in multiple vertex buffers
        if !self.colored_vertex_buffer.is_empty() {
            command_buffer = command_buffer.draw(
                shader.get_pipeline().clone(),
                &self.dynamic_state.clone(),
                self.colored_vertex_buffer.clone(),
                (), (),
            ).unwrap();
        }

        // Images
        let mut shader = self.shader_buffers.get("simple_texture").unwrap().clone();


        if !self.textured_vertex_buffer.is_empty() {
            let handle = self.get_texture_handle(String::from("funky-bird.jpg")).unwrap().clone();

            // TODO : BAD BAD BAD. SELECTS FIRST TEXTURE ONLY!!!!!!!!!!!!
            let descriptor_set = self.texture_buffers.first().clone().unwrap().clone()
                .get_descriptor_set(shader.clone(), self.sampler.clone());

            let vertex_buffer = self.textured_vertex_buffer.get(&handle).unwrap().clone();

            command_buffer = command_buffer.draw(
                shader.get_pipeline().clone(),
                &self.dynamic_state.clone(), vec![vertex_buffer],
                vec![descriptor_set], (),
            ).unwrap();
        }
        /*for (shader_type, kernel) in self.shader_kernels.clone().iter() {
            match shader_type {
                ShaderType::SOLID => {

                }
                ShaderType::TEXTURED => {
                    command_buffer = command_buffer.draw(
                        kernel.clone().get_pipeline().clone(),
                        &dynamic_state.clone(), self.textured_vertex_buffer.clone(),
                        vec![self.get_textured_descriptor_set(String::from("funky-bird.jpg"))], ()
                    ).unwrap();
                }
                ShaderType::IMAGE => {}
            }
        }*/

        command_buffer
            .end_render_pass()
            .unwrap()
    }
}






























