use std::cell::RefCell;
use std::iter::Chain;
use std::slice::Iter;
use std::sync::{Arc, Mutex};
use std::thread::current;
use std::time::Duration;
use std::{iter, num::NonZeroU32, ops::Range, rc::Rc};

use bytemuck::__core::mem;
use bytemuck::{Pod, Zeroable};
use cgmath::{
    vec3, Decomposed, Deg, Euler, InnerSpace, Matrix4, Point3, Quaternion, Rad, Rotation3,
    Transform, Vector3,
};
use futures::executor::LocalPool;
use imgui::sys::ImGuiContext;
use imgui::*;
use imgui_wgpu::{Renderer as ImguiRenderer, RendererConfig as ImguiRendererConfig};
use legion::world::SubWorld;
use legion::*;
use rapier3d::parry::motion::RigidMotionComposition;
use wgpu::util::DeviceExt;
use wgpu::{
    BackendBit, BindGroup, BindGroupLayout, Buffer, BufferBindingType, CommandEncoder, Device,
    FragmentState, Instance, Queue, Surface, SwapChain, SwapChainDescriptor, SwapChainFrame,
    TextureView, VertexState,
};
use winit_24::dpi::PhysicalSize;
use winit_24::platform::unix::x11::ffi::Time;
use winit_24::window::Window;

use crate::camera::{Camera, CameraController};
use crate::components::{ImguiWindow, LoopState, Mesh, Position, RangeCopy};
use crate::current_ui;
use crate::geometry::{load_obj, Vertex};
use crate::imgui_supp::imgui_support::{ImguiContext, ImguiPlatform};
use crate::light::{DirectionalLight, LightRaw};
use crate::render::state::RenderState;
use crate::render::{
    insert_debug_marker_checked, pop_debug_group_checked, push_debug_group_checked, EntityUniforms,
};

#[system]
#[write_component(Camera)]
pub fn imgui_prepare(
    world: &mut SubWorld,
    #[resource] winit_window: &mut Window,
    #[resource] imgui_context: &mut Arc<Mutex<ImguiContext>>,
    #[resource] imgui_platform: &mut Arc<Mutex<ImguiPlatform>>,
) {
    let mut imgui_context = &mut imgui_context.lock().unwrap().context;
    let mut imgui_platform = &mut imgui_platform.lock().unwrap().platform;

    //imgui_state.context.io_mut().update_delta_time(Duration::new(0,160));
    imgui_platform
        .prepare_frame(imgui_context.io_mut(), &winit_window)
        .expect("Failed to prepare frame");

    // get the frame and build a ui window
    unsafe { crate::CURRENT_UI = Some(std::mem::transmute(imgui_context.frame())) }
}

fn run_imgui_render_step<G: 'static + Sized + Send + Sync>(world: &mut SubWorld, ui: &Ui) {
    let mut component_query = <(&G)>::query();
    let mut window_query = <(&ImguiWindow<G>)>::query();

    let mut window_data = None;
    let mut window_func = None;
    for (window) in window_query.iter(world) {
        window_data = Some((window.window)());
        window_func = Some(window.func);
    }

    if window_data.is_some() {
        let mut v = Vec::new();
        for (component_state) in component_query.iter(world) {
            v.push(component_state)
        }
        window_data
            .unwrap()
            .build(&ui, || (window_func.unwrap())(ui, v));
    }
}
/// Go through each "global" window-data component and render it's data
#[system]
#[write_component(ImguiWindow<ImguiPerformanceProfilerLine>)]
#[write_component(ImguiPerformanceProfilerLine)]
#[write_component(ImguiWindow<ImguiGenericOutputLine>)]
#[write_component(ImguiGenericOutputLine)]
pub fn render_imgui(world: &mut SubWorld, #[resource] loop_state: &mut LoopState) {
    let ui = unsafe { crate::current_ui().unwrap() };

    // Pull out the window associated with this type, and render each of the components in the sytem
    run_imgui_render_step::<ImguiGenericOutputLine>(world, &ui);
    run_imgui_render_step::<ImguiPerformanceProfilerLine>(world, &ui);
}

// This would be the shared state for all imgui generic output things
pub struct ImguiGenericOutputLine {
    pub label: String,
}

impl ImguiGenericOutputLine {
    pub fn new(label: String) -> ImguiGenericOutputLine {
        ImguiGenericOutputLine { label }
    }
}

// This would be the shared state for all imgui performance window things
pub struct ImguiPerformanceProfilerLine {
    pub label: String,
    list_of_fps: [f32; 400],
    index: usize,
    scale_min: f32,
    pub scale_max: f32,
}

impl ImguiPerformanceProfilerLine {
    fn add_sample(&mut self, sample: f32) {
        self.list_of_fps[self.index] = sample;

        if self.index >= 399 {
            self.scale_max = self.list_of_fps.iter().cloned().fold(0. / 0., f32::max);
            self.index = 0;
        } else {
            self.index += 1;
        }

        if self.scale_max < sample {
            self.scale_max = sample;
        }
    }

    pub fn iter_data_from_head(&self) -> Chain<Iter<f32>, Iter<f32>> {
        let (left, right) = self.list_of_fps.split_at(self.index);
        right.iter().chain(left.iter())
    }

    pub fn current_average_label(&self) -> (f32, String) {
        let (left, right) = self.list_of_fps.split_at(self.index);
        (
            (left
                .iter()
                .rev()
                .chain(right.iter().rev())
                .take(50)
                .sum::<f32>()
                / 50.0),
            "FPS".to_string(),
        )
    }

    pub fn new(label: String) -> ImguiPerformanceProfilerLine {
        ImguiPerformanceProfilerLine {
            label,
            list_of_fps: [0.0; 400],
            index: 0,
            scale_min: 0.0,
            scale_max: 0.0,
        }
    }
}

#[system]
#[write_component(ImguiPerformanceProfilerLine)]
pub fn render_performance_flag(world: &mut SubWorld, #[resource] loop_state: &mut LoopState) {
    let delta_time = loop_state.delta_time.as_secs_f32();

    let mut query = <(&mut ImguiPerformanceProfilerLine)>::query();
    for (mut profiler) in query.iter_mut(world) {
        profiler.add_sample(delta_time);
    }
}

#[system]
#[write_component(Camera)]
#[write_component(Position)]
#[write_component(ImguiPerformanceProfilerLine)]
#[write_component(Point3<f32>)]
#[write_component(Mesh)]
#[write_component(DirectionalLight)]
pub fn render_test(
    world: &mut SubWorld,
    #[resource] loop_state: &mut LoopState,
    #[resource] renderer: &mut RenderState,
    #[resource] winit_window: &mut Window,
    #[resource] imgui_context: &mut Arc<Mutex<ImguiContext>>,
    #[resource] imgui_platform: &mut Arc<Mutex<ImguiPlatform>>,
) {
    let mut encoder = renderer
        .device
        .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });

    push_debug_group_checked("start render function", &mut encoder);

    let frame = renderer.get_current_frame();

    // Update the camera uniform buffers, need to make it support selection of
    // cameras
    let mut query = <(&mut Camera)>::query();
    for (camera) in query.iter_mut(world) {
        let matrix = camera.calc_matrix(renderer.camera_projection);
        let mx_ref: &[f32; 16] = matrix.as_ref();
        renderer.queue.write_buffer(
            &renderer.forward_pass.uniform_buf,
            0,
            bytemuck::cast_slice(mx_ref),
        );
    }

    let mut query = <(&mut Position, &mut Mesh)>::query();
    let mut mesh_stack = Vec::new();

    // Update the entity uniforms
    for (pos, mesh) in query.iter_mut(world) {
        let d = Decomposed {
            scale: 1.0,
            rot: Quaternion::from(pos.rot),
            disp: Vector3::new(pos.x, pos.y, pos.z),
        };
        let m = Matrix4::from(d);
        let data = EntityUniforms {
            model: m.into(),
            color: [
                mesh.color.r as f32,
                mesh.color.g as f32,
                mesh.color.b as f32,
                mesh.color.a as f32,
            ],
        };
        renderer
            .queue
            .write_buffer(&mesh.uniform_buffer, 0, bytemuck::bytes_of(&data));

        mesh_stack.push(mesh.clone());
    }

    if renderer.lights_are_dirty {
        renderer.lights_are_dirty = false;
        let mut query = <(&mut DirectionalLight, &mut Position)>::query();
        for (i, (light, pos)) in query.iter_mut(world).enumerate() {
            renderer.queue.write_buffer(
                &renderer.light_uniform_buf,
                (i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress,
                bytemuck::bytes_of(&light.to_raw(&pos)),
            );
        }
    }

    push_debug_group_checked("shadow passes", &mut encoder);

    let mut query = <(&mut DirectionalLight, &mut Position)>::query();

    for (i, (light, pos)) in query.iter_mut(world).enumerate() {
        insert_debug_marker_checked(
            &format!("shadow pass {} (light at position {:?})", i, pos),
            &mut encoder,
        );

        // The light uniform buffer already has the projection,
        // let's just copy it over to the shadow uniform buffer.
        encoder.copy_buffer_to_buffer(
            &renderer.light_uniform_buf,
            (i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress,
            &renderer.shadow_pass.uniform_buf,
            0,
            64,
        );

        insert_debug_marker_checked("render entities", &mut encoder);

        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("render pass"),
            color_attachments: &[],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                attachment: &light.target_view,
                depth_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(1.0),
                    store: true,
                }),
                stencil_ops: None,
            }),
        });
        pass.set_pipeline(&renderer.shadow_pass.pipeline);
        pass.set_bind_group(0, &renderer.shadow_pass.bind_group, &[]);

        for mesh in &mesh_stack {
            pass.set_bind_group(1, &mesh.bind_group, &[]);
            // TODO, pipe through this index format through the mesh
            pass.set_index_buffer(mesh.index_buffer.slice(..), mesh.index_format);
            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
            pass.draw_indexed(0..mesh.index_count as u32, 0, 0..1);
        }
    }

    pop_debug_group_checked(&mut encoder);
    // forward pass
    push_debug_group_checked("forward rendering pass", &mut encoder);

    {
        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("forward render pass"),
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment: &frame.output.view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Clear(wgpu::Color {
                        r: 0.1,
                        g: 0.2,
                        b: 0.3,
                        a: 1.0,
                    }),
                    store: true,
                },
            }],
            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachmentDescriptor {
                attachment: &renderer.forward_depth,
                depth_ops: Some(wgpu::Operations {
                    load: wgpu::LoadOp::Clear(1.0),
                    store: false,
                }),
                stencil_ops: None,
            }),
        });
        pass.set_pipeline(&renderer.forward_pass.pipeline);
        pass.set_bind_group(0, &renderer.forward_pass.bind_group, &[]);

        let mut query = <(&mut Position, &mut Mesh)>::query();

        for (pos, mesh) in query.iter_mut(world) {
            pass.set_bind_group(1, &mesh.bind_group, &[]);
            // TODO: Pipe this in through the mesh
            pass.set_index_buffer(mesh.index_buffer.slice(..), mesh.index_format);
            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
            pass.draw_indexed(0..mesh.index_count as u32, 0, 0..1);
        }
    }
    pop_debug_group_checked(&mut encoder);
    pop_debug_group_checked(&mut encoder);

    {
        let mut imgui_context = &mut imgui_context.lock().unwrap().context;
        let mut imgui_platform = &mut imgui_platform.lock().unwrap().platform;

        let ui = unsafe { crate::current_ui().unwrap() };

        // ui.show_demo_window(&mut true);
        imgui_platform.prepare_render(&ui, &winit_window);

        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: None,
            color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
                attachment: &frame.output.view,
                resolve_target: None,
                ops: wgpu::Operations {
                    load: wgpu::LoadOp::Load,
                    store: true,
                },
            }],
            depth_stencil_attachment: None,
        });

        // We gotta do some jank shit here and manually pull the draw data from the UI
        let draw_data = unsafe {
            crate::CURRENT_UI = None; // This is where the drop() occurs
            imgui::sys::igRender();
            &*(imgui::sys::igGetDrawData() as *mut imgui::DrawData)
        };

        renderer
            .imgui_renderer
            .render(draw_data, &renderer.queue, &renderer.device, &mut rpass)
            .expect("Rendering failed");
    }

    renderer.queue.submit(iter::once(encoder.finish()));
}
