use rapier::counters::Counters;
use rapier::math::Real;

use crate::harness::Harness;
use crate::testbed::{
    RunMode, TestbedActionFlags, TestbedState, TestbedStateFlags, PHYSX_BACKEND_PATCH_FRICTION,
    PHYSX_BACKEND_TWO_FRICTION_DIR,
};

use crate::PhysicsState;
use bevy_egui::egui::Slider;
use bevy_egui::{egui, EguiContext};

pub fn update_ui(ui_context: &mut EguiContext, state: &mut TestbedState, harness: &mut Harness) {
    egui::Window::new("Parameters").show(ui_context.ctx_mut(), |ui| {
        if state.backend_names.len() > 1 && !state.example_names.is_empty() {
            let mut changed = false;
            egui::ComboBox::from_label("backend")
                .width(150.0)
                .selected_text(state.backend_names[state.selected_backend])
                .show_ui(ui, |ui| {
                    for (id, name) in state.backend_names.iter().enumerate() {
                        changed = ui
                            .selectable_value(&mut state.selected_backend, id, *name)
                            .changed()
                            || changed;
                    }
                });

            if changed {
                state
                    .action_flags
                    .set(TestbedActionFlags::BACKEND_CHANGED, true);
            }

            ui.separator();
        }

        ui.horizontal(|ui| {
            if ui.button("<").clicked() {
                if state.selected_example > 0 {
                    state.selected_example -= 1;
                    state
                        .action_flags
                        .set(TestbedActionFlags::EXAMPLE_CHANGED, true)
                }
            }

            if ui.button(">").clicked() {
                if state.selected_example + 1 < state.example_names.len() {
                    state.selected_example += 1;
                    state
                        .action_flags
                        .set(TestbedActionFlags::EXAMPLE_CHANGED, true)
                }
            }

            let mut changed = false;
            egui::ComboBox::from_label("example")
                .width(150.0)
                .selected_text(state.example_names[state.selected_example])
                .show_ui(ui, |ui| {
                    for (id, name) in state.example_names.iter().enumerate() {
                        changed = ui
                            .selectable_value(&mut state.selected_example, id, *name)
                            .changed()
                            || changed;
                    }
                });
            if changed {
                state
                    .action_flags
                    .set(TestbedActionFlags::EXAMPLE_CHANGED, true);
            }
        });

        ui.separator();

        ui.collapsing("Profile infos", |ui| {
            ui.horizontal_wrapped(|ui| {
                ui.label(profiling_string(&harness.physics.pipeline.counters))
            });
        });
        ui.collapsing("Serialization infos", |ui| {
            ui.horizontal_wrapped(|ui| {
                ui.label(serialization_string(
                    harness.state.timestep_id,
                    &harness.physics,
                ))
            });
        });

        let integration_parameters = &mut harness.physics.integration_parameters;

        ui.checkbox(
            &mut integration_parameters.interleave_restitution_and_friction_resolution,
            "interleave friction resolution",
        );

        if state.selected_backend == PHYSX_BACKEND_PATCH_FRICTION
            || state.selected_backend == PHYSX_BACKEND_TWO_FRICTION_DIR
        {
            ui.add(
                Slider::new(&mut integration_parameters.max_velocity_iterations, 1..=200)
                    .text("pos. iters."),
            );
            ui.add(
                Slider::new(
                    &mut integration_parameters.max_stabilization_iterations,
                    1..=200,
                )
                .text("vel. iters."),
            );
        } else {
            ui.add(
                Slider::new(&mut integration_parameters.max_velocity_iterations, 1..=200)
                    .text("vel. rest. iters."),
            );
            ui.add(
                Slider::new(
                    &mut integration_parameters.max_velocity_friction_iterations,
                    1..=200,
                )
                .text("vel. frict. iters."),
            );
            ui.add(
                Slider::new(
                    &mut integration_parameters.max_stabilization_iterations,
                    1..=200,
                )
                .text("vel. stab. iters."),
            );
        }

        #[cfg(feature = "parallel")]
        {
            let mut num_threads = harness.state.num_threads();
            ui.add(
                Slider::new(&mut num_threads, 1..=num_cpus::get_physical()).text("num. threads"),
            );
            harness.state.set_num_threads(num_threads);
        }
        ui.add(
            Slider::new(&mut integration_parameters.max_ccd_substeps, 0..=10).text("CCD substeps"),
        );
        ui.add(
            Slider::new(&mut integration_parameters.min_island_size, 1..=10_000)
                .text("min island size"),
        );
        let mut frequency = integration_parameters.inv_dt().round() as u32;
        ui.add(Slider::new(&mut frequency, 0..=240).text("frequency (Hz)"));
        integration_parameters.set_inv_dt(frequency as Real);

        let mut sleep = state.flags.contains(TestbedStateFlags::SLEEP);
        // let mut contact_points = state.flags.contains(TestbedStateFlags::CONTACT_POINTS);
        // let mut wireframe = state.flags.contains(TestbedStateFlags::WIREFRAME);
        ui.checkbox(&mut sleep, "sleep enabled");
        // ui.checkbox(&mut contact_points, "draw contacts");
        // ui.checkbox(&mut wireframe, "draw wireframes");

        state.flags.set(TestbedStateFlags::SLEEP, sleep);
        // state
        //     .flags
        //     .set(TestbedStateFlags::CONTACT_POINTS, contact_points);
        // state.flags.set(TestbedStateFlags::WIREFRAME, wireframe);
        ui.separator();

        let label = if state.running == RunMode::Stop {
            "Start (T)"
        } else {
            "Pause (T)"
        };

        if ui.button(label).clicked() {
            if state.running == RunMode::Stop {
                state.running = RunMode::Running
            } else {
                state.running = RunMode::Stop
            }
        }

        if ui.button("Single Step (S)").clicked() {
            state.running = RunMode::Step;
        }

        if ui.button("Take snapshot").clicked() {
            state
                .action_flags
                .set(TestbedActionFlags::TAKE_SNAPSHOT, true);
        }

        if ui.button("Restore snapshot").clicked() {
            state
                .action_flags
                .set(TestbedActionFlags::RESTORE_SNAPSHOT, true);
        }

        if ui.button("Restart (R)").clicked() {
            state.action_flags.set(TestbedActionFlags::RESTART, true);
        }
    });
}

fn profiling_string(counters: &Counters) -> String {
    format!(
        r#"Total: {:.2}ms
Collision detection: {:.2}ms
|_ Broad-phase: {:.2}ms
   Narrow-phase: {:.2}ms
Island computation: {:.2}ms
Solver: {:.2}ms
|_ Velocity assembly: {:.2}ms
   Velocity resolution: {:.2}ms
   Velocity integration: {:.2}ms
   Position assembly: {:.2}ms
   Position resolution: {:.2}ms
CCD: {:.2}ms
|_ # of substeps: {}
   TOI computation: {:.2}ms
   Broad-phase: {:.2}ms
   Narrow-phase: {:.2}ms
   Solver: {:.2}ms"#,
        counters.step_time(),
        counters.collision_detection_time(),
        counters.broad_phase_time(),
        counters.narrow_phase_time(),
        counters.island_construction_time(),
        counters.solver_time(),
        counters.solver.velocity_assembly_time.time(),
        counters.velocity_resolution_time(),
        counters.solver.velocity_update_time.time(),
        counters.solver.position_assembly_time.time(),
        counters.position_resolution_time(),
        counters.ccd_time(),
        counters.ccd.num_substeps,
        counters.ccd.toi_computation_time.time(),
        counters.ccd.broad_phase_time.time(),
        counters.ccd.narrow_phase_time.time(),
        counters.ccd.solver_time.time(),
    )
}

fn serialization_string(timestep_id: usize, physics: &PhysicsState) -> String {
    let t = instant::now();
    // let t = instant::now();
    let bf = bincode::serialize(&physics.broad_phase).unwrap();
    // println!("bf: {}", instant::now() - t);
    // let t = instant::now();
    let nf = bincode::serialize(&physics.narrow_phase).unwrap();
    // println!("nf: {}", instant::now() - t);
    // let t = instant::now();
    let bs = bincode::serialize(&physics.bodies).unwrap();
    // println!("bs: {}", instant::now() - t);
    // let t = instant::now();
    let cs = bincode::serialize(&physics.colliders).unwrap();
    // println!("cs: {}", instant::now() - t);
    // let t = instant::now();
    let js = bincode::serialize(&physics.impulse_joints).unwrap();
    // println!("js: {}", instant::now() - t);
    let serialization_time = instant::now() - t;
    let hash_bf = md5::compute(&bf);
    let hash_nf = md5::compute(&nf);
    let hash_bodies = md5::compute(&bs);
    let hash_colliders = md5::compute(&cs);
    let hash_joints = md5::compute(&js);
    format!(
        r#"Serialization time: {:.2}ms
Hashes at frame: {}
|_ Broad phase [{:.1}KB]: {}
|_ Narrow phase [{:.1}KB]: {}
|_ &RigidBodySet [{:.1}KB]: {}
|_ Colliders [{:.1}KB]: {}
|_ Joints [{:.1}KB]: {}"#,
        serialization_time,
        timestep_id,
        bf.len() as f32 / 1000.0,
        format!("{:?}", hash_bf).split_at(10).0,
        nf.len() as f32 / 1000.0,
        format!("{:?}", hash_nf).split_at(10).0,
        bs.len() as f32 / 1000.0,
        format!("{:?}", hash_bodies).split_at(10).0,
        cs.len() as f32 / 1000.0,
        format!("{:?}", hash_colliders).split_at(10).0,
        js.len() as f32 / 1000.0,
        format!("{:?}", hash_joints).split_at(10).0,
    )
}
