use std::collections::VecDeque;

use bevy::{
    input::{keyboard::KeyboardInput, ButtonState},
    prelude::*,
};

/// Debug info plugin
pub struct DebugInfoPlugin;

impl Plugin for DebugInfoPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, init)
            .add_systems(Update, (toggle, update));
    }
}

/// Debug UI component
#[derive(Component)]
struct DebugUi;

#[derive(Component)]
struct DebugFps;

/// Debug info enabled marker
#[derive(Resource)]
struct DebugActive;

fn init(mut commands: Commands, server: Res<AssetServer>) {
    commands
        .spawn((
            NodeBundle {
                visibility: Visibility::Hidden,
                style: Style {
                    position_type: PositionType::Absolute,
                    top: Val::Px(0.0),
                    left: Val::Px(0.0),
                    ..default()
                },
                background_color: BackgroundColor(Color::GRAY),
                z_index: ZIndex::Global(999),
                ..default()
            },
            DebugUi,
        ))
        .with_children(|parent| {
            let font: Handle<Font> = server.load("fonts/JMH Typewriter-Bold.otf");
            let style = TextStyle {
                font,
                font_size: 16.0,
                color: Color::ORANGE,
            };
            parent.spawn((TextBundle::from_section("FPS", style), DebugUi, DebugFps));
        });
}

fn toggle(
    mut commands: Commands,
    mut key_evr: EventReader<KeyboardInput>,
    active: Option<Res<DebugActive>>,
    mut visibility: Query<&mut Visibility, With<DebugUi>>,
) {
    for event in key_evr.iter() {
        match event {
            KeyboardInput {
                key_code: Some(KeyCode::F12),
                state: ButtonState::Pressed,
                ..
            } => match active {
                None => {
                    commands.insert_resource(DebugActive);
                    visibility
                        .par_iter_mut()
                        .for_each_mut(|mut vis| *vis = Visibility::Visible);
                }
                Some(_) => {
                    commands.remove_resource::<DebugActive>();
                    visibility
                        .par_iter_mut()
                        .for_each_mut(|mut vis| *vis = Visibility::Hidden);
                }
            },
            _ => (),
        }
    }
}

fn update(
    active: Option<Res<DebugActive>>,
    mut fps: Query<&mut Text, With<DebugFps>>,
    time: Res<Time>,
    mut buffer: Local<VecDeque<f32>>,
) {
    if active.is_some() {
        // Moving average window size
        let buffer_size = 60;

        // Calculate instantaneous FPS
        let last_fps = 1.0 / time.delta().as_secs_f32();

        // Add current FPS to front
        (*buffer).push_back(last_fps);
        if (*buffer).len() > buffer_size {
            // Remove extra elements from back
            let _ = (*buffer).pop_front();
        }

        // Calculate moving average FPS
        let average: f32 = (*buffer).iter().sum::<f32>() / ((*buffer).len() as f32);

        // Display FPS value
        fps.single_mut().sections[0].value = format!("FPS: {:.3}", average);
    }
}
