#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]

extern crate cgmath;
extern crate image;
extern crate nalgebra as na;
extern crate quick_xml;
extern crate rand;
extern crate sfml;

use image::{DynamicImage, GenericImage, GenericImageView, Pixel, SubImage};
use sfml::graphics::*;
use sfml::graphics::{
    Color, RenderTarget, RenderWindow,
};
use sfml::system::*;
use sfml::system::Vector2 as sfVec2;
use sfml::window::*;
use sfml::window::{Event, Key, Style};

use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer};
use vulkano::command_buffer::AutoCommandBufferBuilder;
use vulkano::descriptor::descriptor_set::PersistentDescriptorSet;
use vulkano::device::{Device, DeviceExtensions};
use vulkano::instance::{Instance, InstanceExtensions, PhysicalDevice};
use vulkano::pipeline::ComputePipeline;
use vulkano::sync::GpuFuture;
use vulkano::sync;
use std::sync::Arc;
use std::fs;

use crate::input::Input;
use crate::slider::Slider;
use crate::timer::Timer;

mod slider;
mod timer;
mod input;
mod util;

// The container trait for all the shaders
trait Effect: Drawable {
    fn update(&mut self, t: f32, x: f32, y: f32);
    fn name(&self) -> &str;
    fn as_drawable(&self) -> &Drawable;
}


// ======= LARGE MULTISPRITE SHADER DEMO ===========
struct Edge<'t> {
    surface: RenderTexture,
    bg_sprite: Sprite<'t>,
    entities: Vec<Sprite<'t>>,
    shader: Shader<'static>,
}

impl<'t> Edge<'t> {
    fn new(bg_texture: &'t Texture, entity_texture: &'t Texture) -> Self {
        let mut surface = RenderTexture::new(800, 600, false).unwrap();
        surface.set_smooth(true);
        let mut bg_sprite = Sprite::with_texture(bg_texture);
        bg_sprite.set_position((0., 0.));
        let mut entities = Vec::new();

        for i in 0..6 {
            let mut entity = Sprite::with_texture(entity_texture);
            entity.set_texture_rect(&IntRect::new(96 * i, 0, 96, 96));
            entities.push(entity);
        }

        let mut shader = Shader::from_file(None, None, Some("resources/shaders/edge.frag")).unwrap();
        shader.set_uniform_current_texture("texture");

        Self {
            surface,
            bg_sprite,
            entities,
            shader,
        }
    }
}

impl<'t> Drawable for Edge<'t> {
    fn draw<'a: 'shader, 'texture, 'shader, 'shader_texture>(
        &'a self,
        target: &mut RenderTarget,
        mut states: RenderStates<'texture, 'shader, 'shader_texture>,
    ) {
        states.shader = Some(&self.shader);
        target.draw_with_renderstates(&Sprite::with_texture(self.surface.texture()), states);
    }
}

impl<'t> Effect for Edge<'t> {
    fn update(&mut self, t: f32, x: f32, y: f32) {
        self.shader
            .set_uniform_float("edge_threshold", 1. - (x + y) / 2.);
        let entities_len = self.entities.len() as f32;

        for (i, en) in self.entities.iter_mut().enumerate() {
            let pos = (
                (0.25 * (t * i as f32 + (entities_len - i as f32))).cos() * 300. + 350.,
                (0.25 * (t * (entities_len - i as f32) + i as f32)).cos() * 200. + 250.,
            );
            en.set_position(pos);
        }
        self.surface.clear(&Color::WHITE);
        self.surface.draw(&self.bg_sprite);
        for en in &self.entities {
            self.surface.draw(en);
        }
        self.surface.display();
    }
    fn as_drawable(&self) -> &Drawable {
        self
    }
    fn name(&self) -> &str {
        "edge post-effect"
    }
}
// =================================================

fn surrounding_pixels(x: u32, y: u32, img: &DynamicImage) -> Vec<image::Rgba<u8>> {

    let mut pixels: Vec<image::Rgba<u8>> = Vec::new();

    if img.in_bounds(x+1, y+1) {pixels.push(img.get_pixel(x+1, y+1))}
    if img.in_bounds(x+1, y)   {pixels.push(img.get_pixel(x+1, y))}
    if img.in_bounds(x,   y+1) {pixels.push(img.get_pixel(x, y+1))}

    if x > 0 {
        if img.in_bounds(x-1, y+1) {pixels.push(img.get_pixel(x-1, y+1))}
        if img.in_bounds(x-1, y)   {pixels.push(img.get_pixel(x-1, y))}
    }

    if y > 0 {
        if img.in_bounds(x+1, y-1) {pixels.push(img.get_pixel(x+1, y-1))}
        if img.in_bounds(x,   y-1) {pixels.push(img.get_pixel(x, y-1))}
        if x > 0 {
            if img.in_bounds(x - 1, y - 1) { pixels.push(img.get_pixel(x - 1, y - 1)) }
        }
    }

    pixels
}

fn main() {

    // Create the vulkan instance, device, and device queue
    let instance = Instance::new(None, &InstanceExtensions::none(), None).unwrap();
    let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
    let queue_family = physical.queue_families().find(|&q| q.supports_compute()).unwrap();
    let (device, mut queues) = Device::new(physical,
                                           physical.supported_features(),
                                           &DeviceExtensions::none(),
                                           [(queue_family, 0.5)].iter().cloned()).unwrap();
    let queue = queues.next().unwrap();
    println!("Device initialized");

    // Compile the shader and add it to a pipeline
    let pipeline = Arc::new({
        mod cs {
            vulkano_shaders::shader!{
                ty: "compute",
                path: "resources/shaders/add.compute"
            }
        }
        let shader = cs::Shader::load(device.clone()).unwrap();
        ComputePipeline::new(device.clone(), &shader.main_entry_point(), &()).unwrap()
    });

    // Load up the input image, determine some details
    let mut img = image::open("resources/images/funky-bird.jpg").unwrap();
    let xy = img.dimensions();
    let data_length = xy.0*xy.1*3;

    println!("Buffer length {}", data_length);
    println!("Buffer length {}", img.raw_pixels().len());
    println!("Size {:?}", xy);

    let mut image_buffer = Vec::new();

    {
        // Pull out the image data and place it in a sync'd CPU<->GPU buffer
        let data_buffer = {
            let mut buff = img.raw_pixels();
            let mut buff = buff.iter();
            let data_iter = (0 .. data_length).map(|n| *(buff.next().unwrap()));
            CpuAccessibleBuffer::from_iter(device.clone(), BufferUsage::all(), data_iter).unwrap()
        };

        // Create the data descriptor set for our previously created shader pipeline
        let set = Arc::new(PersistentDescriptorSet::start(pipeline.clone(), 0)
            .add_buffer(data_buffer.clone()).unwrap()
            .build().unwrap()
        );

        // The command buffer I think pretty much serves to define what runs where for how many times
        let command_buffer = AutoCommandBufferBuilder::primary_one_time_submit(device.clone(), queue.family()).unwrap()
            .dispatch([xy.0, xy.1, 1], pipeline.clone(), set.clone(), ()).unwrap()
            .build().unwrap();

        // Create a future for running the command buffer and then just fence it
        let future = sync::now(device.clone())
            .then_execute(queue.clone(), command_buffer).unwrap()
            .then_signal_fence_and_flush().unwrap();

        // I think this is redundant and returns immediately
     //   future.wait(None).unwrap();

        // The buffer is sync'd so we can just read straight from the handle
        let data_buffer_content = data_buffer.read().unwrap();

        for y in 0 .. xy.1 {
            for x in 0 .. xy.0 {

                let r = data_buffer_content[((xy.0 * y + x) * 3 + 0) as usize] as u8;
                let g = data_buffer_content[((xy.0 * y + x) * 3 + 1) as usize] as u8;
                let b = data_buffer_content[((xy.0 * y + x) * 3 + 2) as usize] as u8;

                image_buffer.push(r);
                image_buffer.push(g);
                image_buffer.push(b);
                image_buffer.push(255);

                img.put_pixel(x, y, image::Rgba([r, g, b, 255]))

            }
        }
    }

    img.save("output.png");

    println!("Starting");

    let mut window = RenderWindow::new(
        (900, 900),
        "Custom drawable",
        Style::CLOSE,
        &Default::default(),
    );

    let mut timer = Timer::new();
    let mut input = Input::new();

    //==========================================
    let font = Font::from_file("resources/fonts/sansation.ttf").unwrap();


    let mut bg_texture = Texture::new(xy.0, xy.1).unwrap();
    bg_texture.update_from_pixels(image_buffer.as_slice(), xy.0, xy.1, 0, 0);
    //let mut bg_texture = Texture::from_file("resources/images/sfml.png").unwrap();
    //bg_texture.set_smooth(true);

    let mut entity_texture = Texture::from_file("resources/images/devices.png").unwrap();
    entity_texture.set_smooth(true);

    let mut effects: [Box<Effect>; 1] = [
        Box::new(Edge::new(&bg_texture, &entity_texture)),
    ];
    let mut current = 0;

    let text_bg_texture = Texture::from_file("resources/images/text-background.png").unwrap();
    let mut text_bg = Sprite::with_texture(&text_bg_texture);
    text_bg.set_position((0., 520.));
    text_bg.set_color(&Color::rgba(255, 255, 255, 200));
    //==========================================

    let mut slider = Slider::new(40.0, None);

    let step_size:            f32 = 0.005;
    let mut elapsed_time:     f32;
    let mut delta_time:       f32;
    let mut accumulator_time: f32 = 0.0;
    let mut current_time:     f32 = timer.elap_time();

    while window.is_open() {

        while let Some(event) = window.poll_event() {
            match event {
                Event::Closed => return,
                Event::KeyPressed { code, .. } => {
                    if code == Key::Escape {
                        return;
                    }
                }
                _ => {}
            }
            input.ingest(&event)
        }

        if input.is_held(Key::W) {
        }
        if input.is_held(Key::A) {
        }
        if input.is_held(Key::S) {
        }
        if input.is_held(Key::D) {
        }

        elapsed_time = timer.elap_time();
        delta_time = elapsed_time - current_time;
        current_time = elapsed_time;
        if delta_time > 0.02 {
            delta_time = 0.02;
        }
        accumulator_time += delta_time;

        while (accumulator_time - step_size) >= step_size {
            accumulator_time -= step_size;
        }

        let x = window.mouse_position().x as f32 / window.size().x as f32;
        let y = window.mouse_position().y as f32 / window.size().y as f32;
        effects[current].update(elapsed_time*1.0, x, y);

        window.clear(&Color::BLACK);

        window.draw(effects[current].as_drawable());
        window.draw(&slider);


        window.display();

    }
}
