use std::time::Instant;

use cgmath::{Euler, Quaternion};
use legion::world::SubWorld;
use legion::IntoQuery;
use legion::*;
use nalgebra::Quaternion as naQuaternion;
use rapier3d::dynamics::{IntegrationParameters, JointSet, RigidBodySet};
use rapier3d::geometry::{BroadPhase, ColliderSet, NarrowPhase};
use rapier3d::pipeline::{PhysicsPipeline, ChannelEventCollector};

use crate::camera::{Camera, CameraController};
use crate::components::{Collider, LoopState, Mesh, Physics, Position, ImguiWindow};
use imgui::{FontSource, Condition};
use imgui::*;
use crate::physics::state::PhysicsState;
use crate::render::system::ImguiGenericOutputLine;


#[system]
#[write_component(Collider)]
#[write_component(Physics)]
#[write_component(Mesh)]
pub fn run_physics(
    world: &mut SubWorld,
    #[resource] physics_state: &mut PhysicsState,
    #[resource] physics_pipeline: &mut PhysicsPipeline,
) {
    // Make sure all the entities we care about are added to the system
    let mut query = <(&mut Collider, &mut Physics, &mut Mesh)>::query();
    for (collider, physics, mesh) in query.iter_mut(world) {
        let rigid_body_handle = match physics.rigid_body_handle {
            None => {
                let handle = physics_state.bodies.insert(physics.rigid_body.clone());
                physics.rigid_body_handle = Some(handle);
                physics.rigid_body_handle.unwrap()
            }
            Some(handle) => handle,
        };
        if collider.collider_handle == None {
            let handle = physics_state.colliders.insert(
                collider.collider.clone(),
                rigid_body_handle,
                &mut physics_state.bodies,
            );
            collider.collider_handle = Some(handle);
        }
    }

    let (contact_send, contact_recv) = crossbeam::channel::unbounded();
    let (intersection_send, intersection_recv) = crossbeam::channel::unbounded();
    let event_handler = ChannelEventCollector::new(intersection_send, contact_send);


    // run the physics step
    physics_pipeline.step(
        &physics_state.gravity,
        &physics_state.integration_parameters,
        &mut physics_state.broad_phase,
        &mut physics_state.narrow_phase,
        &mut physics_state.bodies,
        &mut physics_state.colliders,
        &mut physics_state.joints,
        None,
        None,
        &event_handler,
    );

    while let Ok(intersection_event) = intersection_recv.try_recv() {
        println!("{:?}", intersection_event)
    }

    while let Ok(contact_event) = contact_recv.try_recv() {
        println!("{:?}", contact_event)
    }
}


#[system]
#[write_component(Camera)]
#[write_component(CameraController)]
#[write_component(ImguiGenericOutputLine)]
pub fn update_camera(world: &mut SubWorld, #[resource] loop_state: &mut LoopState) {

    let mut query = <(&mut Camera, &mut CameraController)>::query();
    for (mut camera, controller) in query.iter_mut(world) {
        controller.update_camera(&mut camera, loop_state.step_size)
    }
    let mut query = <(&mut Camera, &mut CameraController, &mut ImguiGenericOutputLine)>::query();
    for (mut camera, controller, ui_entry) in query.iter_mut(world) {
        ui_entry.label = format!("Camera: {:.2}, {:.2}, {:.2}", camera.position.x, camera.position.y, camera.position.z);
    }

}

#[system]
#[write_component(Collider)]
#[write_component(Physics)]
#[write_component(Mesh)]
#[write_component(Position)]
pub fn update_models(
    world: &mut SubWorld,
    #[resource] physics_state: &mut PhysicsState,
    #[resource] physics_pipeline: &mut PhysicsPipeline,
) {

    // Make sure all the entities we care about are added to the system
    let mut query = <(&mut Collider, &mut Physics, &mut Mesh, &mut Position)>::query();
    for (collider, physics, mesh, position) in query.iter_mut(world) {
        let pos = physics_state
            .bodies
            .get(physics.rigid_body_handle.unwrap())
            .unwrap()
            .position();
        position.x = pos.translation.x;
        position.y = pos.translation.y;
        position.z = pos.translation.z;

        let q = Quaternion::new(
            pos.rotation.w,
            pos.rotation.i,
            pos.rotation.j,
            pos.rotation.k,
        );
        let rotation = Euler::from(q);
        position.rot.x = rotation.x.into();
        position.rot.y = rotation.y.into();
        position.rot.z = rotation.z.into();
    }
}
