use crate::{
    game::{Board, BoardIndex, Piece, Side},
    prelude::*,
    tweak::Tweaks,
};
use bevy::{
    animation::RepeatAnimation,
    core_pipeline::{
        bloom::BloomSettings,
        experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasSettings},
        prepass::MotionVectorPrepass,
        tonemapping::{DebandDither, Tonemapping},
        Skybox,
    },
    input::{
        mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
        ButtonState,
    },
    pbr::{
        ExtendedMaterial, MaterialExtension, ScreenSpaceAmbientOcclusionBundle,
        ScreenSpaceAmbientOcclusionSettings,
    },
    render::{
        render_resource::{AsBindGroup, ShaderRef, TextureViewDescriptor, TextureViewDimension},
        view::ColorGrading,
    },
    utils::HashMap,
    window::PrimaryWindow,
};
use tweaks::*;

pub(crate) struct Display3dPlugin;

impl Plugin for Display3dPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins((
            TemporalAntiAliasPlugin,
            MaterialPlugin::<DissolveMaterial>::default(),
        ))
        .insert_resource(Msaa::Off)
        .add_systems(
            OnExit(GameState::Loading),
            (
                initialize,
                fix_skybox.before(initialize),
                update_tweaks.run_if(resource_exists::<tweak::GameTweaks>()),
            ),
        )
        .add_systems(
            Update,
            (
                load_assets
                    .run_if(in_state(GameState::Loading))
                    .run_if(on_event::<AssetEvent<Tweaks>>()),
                hydrate_camera.run_if(any_component_added::<Camera3d>),
                set_piece_model.run_if(any_component_added::<Piece>),
                set_piece_model.run_if(any_component_changed::<Piece>),
                set_board_model.run_if(any_component_added::<game::BoardComponent>),
                set_board_model.run_if(any_component_added::<TilesComponent>),
                set_valid_move_model.run_if(any_component_added::<game::ValidMove>),
                set_tile_hitbox.run_if(any_component_added::<game::Tile>),
                set_piece_position.run_if(any_component_changed::<BoardIndex>),
                set_piece_texture
                    .run_if(any_component_changed::<Side>)
                    .run_if(resource_exists::<tweak::GameTweaks>()),
                select
                    .run_if(in_state(GameState::Play))
                    .run_if(in_state(DisplayState::Display3d))
                    .run_if(on_event::<MouseButtonInput>()),
                pick_up.run_if(any_component_added::<game::Selected>),
                put_down.run_if(any_component_removed::<game::Selected>()),
                switch_sides
                    .run_if(in_state(GameState::Play))
                    .run_if(state_changed::<game::TurnState>()),
                update_tweaks
                    .run_if(on_event::<AssetEvent<Tweaks>>())
                    .run_if(resource_exists::<tweak::GameTweaks>()),
                scale_lighting.run_if(
                    any_component_added::<DirectionalLight>
                        .or_else(any_component_added::<SpotLight>)
                        .or_else(any_component_added::<PointLight>)
                        .or_else(on_event::<AssetEvent<Tweaks>>()),
                ),
                setup_capture_piece.run_if(any_component_changed::<Handle<StandardMaterial>>),
                capture_piece.run_if(any_with_component::<game::Captured>()),
                skip_animation
                    .run_if(|keys: Res<Input<KeyCode>>| -> bool {
                        keys.just_pressed(KeyCode::Return)
                    })
                    .run_if(in_state(GameState::Play)),
            ),
        )
        .add_systems(
            Update,
            (
                move_camera.run_if(on_event::<MouseMotion>()),
                mouse_zoom.run_if(on_event::<MouseWheel>()),
                gizmo_system,
                selected_gizmo,
                moves_gizmo,
                debug_selected.run_if(any_with_component::<game::Selected>()),
            )
                .run_if(resource_exists::<debug::DebugEnabled>())
                .run_if(in_state(GameState::Play))
                .run_if(in_state(DisplayState::Display3d)),
        )
        .add_systems(OnExit(DisplayState::Display3d), deactivate::<Display3d>)
        .add_systems(
            OnEnter(GameState::Play),
            (
                activate::<Display3d>.run_if(in_state(DisplayState::Display3d)),
                set_piece_texture.run_if(resource_exists::<tweak::GameTweaks>()),
                update_tweaks.run_if(resource_exists::<tweak::GameTweaks>()),
                opening_animation
                    .run_if(run_once())
                    .run_if(in_state(DisplayState::Display3d)),
            ),
        );
    }
}

#[derive(Debug, Component)]
pub(crate) struct Display3d;

#[derive(Debug, Component)]
struct TilesComponent;

#[derive(Debug, Resource, Clone)]
struct AssetsMap {
    hitbox_shape: Handle<Mesh>,
    hitbox_material: Handle<StandardMaterial>,
}

/// Load 3d models
/// This is kind of pulling double duty.
/// Both loads the GLTF file _and_ populates the ModelMap once that is loaded.
fn load_assets(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let hitbox_shape = meshes.add(shape::Box::new(1.0, 0.1, 1.0).into());
    let hitbox_material = materials.add(StandardMaterial {
        base_color: Color::NONE,
        perceptual_roughness: 0.0,
        reflectance: 0.0,
        alpha_mode: AlphaMode::Blend,
        ..default()
    });
    commands.insert_resource(AssetsMap {
        hitbox_shape,
        hitbox_material,
    });
}

/// Initialize the 3d board
fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<AssetsMap>) {
    info!("Initializing root");
    commands
        .spawn((
            SpatialBundle {
                visibility: Visibility::Hidden,
                ..default()
            },
            Display3d,
        ))
        .with_children(|parent| {
            info!("Intializing 3D Board!");
            parent
                .spawn((Display3d, game::BoardComponent, SceneBundle { ..default() }))
                .with_children(|parent| {
                    // TEMP: Tiles
                    parent.spawn((Display3d, TilesComponent, SceneBundle { ..default() }));

                    // Hitboxes
                    game::tiles().for_each(|(index, tile)| {
                        let side = Board::side(index).expect("Spawn valid side");

                        parent.spawn((
                            Display3d,
                            index,
                            tile,
                            PbrBundle {
                                mesh: assets.hitbox_shape.clone(),
                                material: assets.hitbox_material.clone(),
                                visibility: Visibility::Hidden,
                                ..default()
                            },
                            side,
                            game::Selectable,
                        ));
                    });

                    // Valid move indicators
                    game::tiles().for_each(|(index, _)| {
                        let side = Board::side(index).expect("Spawn valid side");

                        parent.spawn((
                            Display3d,
                            index,
                            SceneBundle {
                                visibility: Visibility::Hidden,
                                transform: Transform::from_translation(board_translation(&index)),
                                ..default()
                            },
                            side,
                            game::ValidMove,
                        ));
                    });

                    // Pieces
                    board.pieces().iter().for_each(|(index, piece)| {
                        let side = Board::side(*index).expect("Spawn valid side");

                        parent.spawn((
                            side,
                            Display3d,
                            piece.clone(),
                            index.clone(),
                            SceneBundle { ..default() },
                            game::Selectable,
                        ));
                    });
                });
        });
}

fn hydrate_camera(
    events: Query<(&Name, Entity), Added<Camera>>,
    gltfs: Res<Assets<Gltf>>,
    state: Res<State<game::TurnState>>,
    mut players: Query<&mut AnimationPlayer>,
    mut commands: Commands,
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<Tweaks>>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    events
        .iter()
        .filter(|(name, _)| name.as_str() == "GameCam")
        .for_each(|(_, entity)| {
            info!("Initialize 3d camera");
            let skybox_handle = tweak
                .get_handle::<Image>("display3d_models_skybox_file")
                .unwrap();
            info!("Hydrating camera {:?}", entity);
            // Populate the components for the camera
            commands.entity(entity).insert((
                Display3d,
                DisplayState::Display3d,
                Camera3dBundle {
                    camera: Camera {
                        is_active: true,
                        hdr: tweak.get::<bool>("display3d_hdr").unwrap(),
                        ..default()
                    },
                    dither: DebandDither::Enabled,
                    color_grading: ColorGrading { ..default() },
                    ..default()
                },
                BloomSettings { ..default() },
                Skybox(skybox_handle.clone()),
                EnvironmentMapLight {
                    diffuse_map: skybox_handle.clone(),
                    specular_map: skybox_handle.clone(),
                },
                UiCameraConfig { show_ui: true },
                FogSettings { ..default() },
                ScreenSpaceAmbientOcclusionBundle { ..default() },
                TemporalAntiAliasSettings { ..default() },
                MotionVectorPrepass { ..default() },
                // Name::new("3D Camera"),
            ));

            let assets_handle = tweak
                .get_handle::<Gltf>("display3d_models_assets_file")
                .unwrap();
            let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
            // Set it to the default position by starting the initial animation
            if let Ok(mut player) = players.get_mut(entity) {
                debug!("Animations: {:?}", gltf.named_animations.keys());
                // GameCamIntro1, GameCamIntro2, GameCamSide1>2, GameCamSide2>1
                let animation = match state.get() {
                    game::TurnState(game::Side::A) => gltf.named_animations.get(
                        tweak
                            .get::<String>("display3d_models_animations_intro_a")
                            .unwrap()
                            .as_str(),
                    ),
                    game::TurnState(game::Side::B) => gltf.named_animations.get(
                        tweak
                            .get::<String>("display3d_models_animations_intro_b")
                            .unwrap()
                            .as_str(),
                    ),
                }
                .expect("Camera Startup");
                player.play(animation.clone()).pause();
            }
        });
}

/// Update display3d tweaks in the game
/// Triggered on entering 3d state and when the tweakfile is updated.
fn update_tweaks(
    mut camera_settings: Query<
        (
            Entity,
            &mut FogSettings,
            &mut ColorGrading,
            &mut Tonemapping,
            &mut BloomSettings,
        ),
        With<Display3d>,
    >,
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<Tweaks>>,
    //  mut lights: Query<
    //      (
    //          Option<&mut SpotLight>,
    //          Option<&mut PointLight>,
    //          Option<&mut DirectionalLight>,
    //      ),
    //      Or<(With<SpotLight>, With<PointLight>, With<DirectionalLight>)>,
    //  >,
    mut commands: Commands,
) {
    if let Some(tweak) = tweaks.get(tweaks_file.handle.clone()) {
        camera_settings.iter_mut().for_each(
            |(entity, mut fog, mut color_grading, mut tonemapping, mut bloom)| {
                *fog = tweak
                    .get::<TweakFogSettings>("display3d_fog")
                    .unwrap()
                    .into();
                *color_grading = tweak
                    .get::<TweakColorGrading>("display3d_color_grading")
                    .unwrap()
                    .into();
                *tonemapping = tweak
                    .get::<TweakTonemapping>("display3d_color_tonemapping")
                    .unwrap()
                    .into();
                *bloom = tweak
                    .get::<TweakBloomSettings>("display3d_bloom")
                    .unwrap()
                    .into();

                let quality_level = tweak.get::<TweakScreenSpaceAmbientOcclusionQualityLevel>(
                    "display3d_ssao_quality_level",
                );
                match quality_level {
                    Some(quality_level) => {
                        commands
                            .entity(entity)
                            .insert(ScreenSpaceAmbientOcclusionSettings {
                                quality_level: quality_level.into(),
                            });
                        commands.insert_resource(Msaa::Off);
                    }
                    None => {
                        commands
                            .entity(entity)
                            .remove::<ScreenSpaceAmbientOcclusionSettings>();
                        let msaa: Msaa = tweak.get::<TweakMsaa>("display3d_msaa").unwrap().into();
                        commands.insert_resource(msaa);
                    }
                }
            },
        );
        //  lights
        //      .iter_mut()
        //      .for_each(|(mut spot, mut point, mut direction)| {
        //          // Depending on the light, set the scalar tweak
        //      });
    }
}

fn fix_skybox(
    mut images: ResMut<Assets<Image>>,
    tweaks_file: Res<tweak::GameTweaks>,
    tweaks: Res<Assets<Tweaks>>,
) {
    let tweak = tweaks.get(tweaks_file.handle.clone()).unwrap();
    let handle = tweak
        .get_handle_unchecked::<Image>("display3d_models_skybox_file")
        .unwrap();
    let image = images.get_mut(handle).unwrap();

    info!("Loaded skybox image");
    // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture,
    // so they appear as one texture. The following code reconfigures the texture as necessary.
    if image.texture_descriptor.array_layer_count() == 1 {
        image.reinterpret_stacked_2d_as_array(
            image.texture_descriptor.size.height / image.texture_descriptor.size.width,
        );
        image.texture_view_descriptor = Some(TextureViewDescriptor {
            dimension: Some(TextureViewDimension::Cube),
            ..default()
        });
    }
}

/// Set the model for each piece based on the game::Piece::* marker
fn set_piece_model(
    mut events: Query<
        (&mut Handle<Scene>, &Piece),
        (
            Or<(Added<game::Piece>, Changed<game::Piece>)>,
            With<Display3d>,
        ),
    >,
    gltfs: Res<Assets<Gltf>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    events.iter_mut().for_each(|(mut handle, piece)| {
        let assets_handle = tweak
            .get_handle::<Gltf>("display3d_models_assets_file")
            .unwrap();
        let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
        *handle = match piece {
            game::Piece::Pawn => gltf.named_scenes.get(
                tweak
                    .get::<String>("display3d_models_scenes_pawn")
                    .unwrap()
                    .as_str(),
            ),
            game::Piece::Drone => gltf.named_scenes.get(
                tweak
                    .get::<String>("display3d_models_scenes_drone")
                    .unwrap()
                    .as_str(),
            ),
            game::Piece::Queen => gltf.named_scenes.get(
                tweak
                    .get::<String>("display3d_models_scenes_queen")
                    .unwrap()
                    .as_str(),
            ),
        }
        .expect("Game board model")
        .clone();
    });
}

fn set_board_model(
    mut boards: Query<
        &mut Handle<Scene>,
        (
            With<game::BoardComponent>,
            Without<TilesComponent>,
            With<Display3d>,
        ),
    >,
    gltfs: Res<Assets<Gltf>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    boards.iter_mut().for_each(|mut handle| {
        let assets_handle = tweak
            .get_handle::<Gltf>("display3d_models_assets_file")
            .unwrap();
        let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
        *handle = gltf
            .named_scenes
            .get(
                tweak
                    .get::<String>("display3d_models_scenes_board")
                    .unwrap()
                    .as_str(),
            )
            .expect("Game board model")
            .clone();
    });
}

/// Sets a piece location given it's board index
fn set_piece_position(
    mut events: Query<
        (&mut Transform, &BoardIndex),
        (With<game::Piece>, With<Display3d>, Changed<BoardIndex>),
    >,
) {
    events.iter_mut().for_each(|(mut t, i)| {
        t.translation = board_translation(i);
    });
}

/// Given a board index returns the Vec3 location in space
fn board_translation(&BoardIndex { x, y }: &BoardIndex) -> Vec3 {
    // Scale x down by 4 to account for -4..4 scaling
    let x = x as i8 - 4;
    // Mirror y axis because our board index is inverted...
    let y = -(y as i8) + 1;

    let x = if x < 0 {
        x as f32 * 1.3 + 0.275 // 0.325
    } else {
        x as f32 * 1.3 + 1.05 // 1.0
    };

    let y = y as f32 * 1.3 + 0.65;

    Vec3::new(x, 0.0, y)
}

fn capture_translation(side: &Side, num: usize) -> Vec3 {
    info!("Side: {:?} Num: {:?}", side, num);
    let x = 5.0 - ((num % 4) as f32 * 1.3);
    let y = -1.3;
    let z = 4.0 + ((num / 4) as f32 * 1.3);
    match side {
        Side::B => Vec3::new(-x, y, z),
        Side::A => Vec3::new(x, y, -z),
    }
}

fn gizmo_system(mut gizmos: Gizmos) {
    for y in 0..4 {
        for x in 0..8 {
            gizmos.cuboid(
                Transform::from_translation(board_translation(&BoardIndex { x, y }))
                    .with_scale(Vec3::new(1.0, 0.1, 1.0)),
                Color::PURPLE,
            )
        }
    }
}

/// TODO: This has bad feel, needs to be tuned
fn move_camera(
    buttons: Res<Input<MouseButton>>,
    mut events: EventReader<MouseMotion>,
    mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
) {
    events.read().for_each(|MouseMotion { delta }| {
        if buttons.pressed(MouseButton::Left) {
            camera.iter_mut().for_each(|mut t| {
                t.rotate_around(Vec3::ZERO, Quat::from_rotation_y(delta.x / 256.0));
                t.rotate_around(Vec3::ZERO, Quat::from_rotation_x(delta.y / 256.0));
                t.look_at(Vec3::ZERO, Vec3::Y);
            });
        }
    });
}

fn mouse_zoom(
    mut events: EventReader<MouseWheel>,
    mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
) {
    events.read().for_each(|MouseWheel { unit, y, .. }| {
        camera.iter_mut().for_each(|mut t| {
            match unit {
                MouseScrollUnit::Line => {
                    t.translation *= 1.0 - (*y / 4.0);
                }
                MouseScrollUnit::Pixel => {
                    t.translation *= 1.0 - (*y / 64.0);
                }
            }
            t.look_at(Vec3::ZERO, Vec3::Y);
        });
    });
}

/// Set the Texture for a piece given it's position (left or right) on the bord.
/// Executed when Side is changed or upon entry to Display3d state
/// Getting this to run _after_ GLTF is loaded is a pain.
/// PERF: We are saving what to work on in a Vector which is bad.
/// CAVEAT: We are only exeucting this when a piece changes or state is changed.
fn set_piece_texture(
    events: Query<
        (Entity, &Piece, &Side),
        (
            With<game::Piece>,
            With<Display3d>,
            Or<(Changed<Side>, Added<Side>)>,
        ),
    >,
    gltfs: Res<Assets<Gltf>>,
    children: Query<&Children>,
    mut models: Query<(&Name, &mut Handle<StandardMaterial>)>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    events.iter().for_each(|(entity, piece, side)| {
        debug!("Checking piece texture for {:?}", entity);
        let tweak = tweaks.get(tweaks_file.handle.clone()).unwrap();
        let assets_handle = tweak
            .get_handle::<Gltf>("display3d_models_assets_file")
            .unwrap();
        if let Some(gltf) = gltfs.get(assets_handle) {
            // Why can't we just models.iter_many_mut(...).for_each(...)??
            let mut stuff = models.iter_many_mut(children.iter_descendants(entity));
            while let Some((n, mut m)) = stuff.fetch_next() {
                debug!("Setting piece texture for {:?}", n);
                match (*piece, side, n.as_str()) {
                    (Piece::Queen, Side::A, "Queen.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_queen_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Queen texture")
                            .clone()
                    }
                    (Piece::Queen, Side::A, "Queen.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Dots texture")
                            .clone()
                    }
                    (Piece::Queen, Side::B, "Queen.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_queen_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Blue Queen texture")
                            .clone()
                    }
                    (Piece::Queen, Side::B, "Queen.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Dots texture")
                            .clone()
                    }
                    (Piece::Drone, Side::A, "Drone.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_drone_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Drone texture")
                            .clone()
                    }
                    (Piece::Drone, Side::A, "Drone.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Dots texture")
                            .clone()
                    }
                    (Piece::Drone, Side::B, "Drone.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_drone_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Blue Drone texture")
                            .clone()
                    }
                    (Piece::Drone, Side::B, "Drone.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Blue Dots texture")
                            .clone()
                    }
                    (Piece::Pawn, Side::A, "Pawn.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_pawn_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Pawn texture")
                            .clone()
                    }
                    (Piece::Pawn, Side::A, "Pawn.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_red")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Red Dots texture")
                            .clone()
                    }
                    (Piece::Pawn, Side::B, "Pawn.0") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_pawn_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Blue Pawn texture")
                            .clone()
                    }
                    (Piece::Pawn, Side::B, "Pawn.1") => {
                        *m = gltf
                            .named_materials
                            .get(
                                tweak
                                    .get::<String>("display3d_models_materials_dots_blue")
                                    .unwrap()
                                    .as_str(),
                            )
                            .expect("Load Blue Dots texture")
                            .clone()
                    }
                    _ => warn!("???"),
                }
            }
        }
    });
}

/// Select tiles and pieces in 3d
/// There is a bug where we are selecting multiple entities...
/// TODO: Selectable generalize picking pieces **and** hitboxes
fn select(
    mut events: EventReader<MouseButtonInput>,
    query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>,
    meshes: Res<Assets<Mesh>>,
    cameras: Query<(&Camera, &GlobalTransform)>,
    windows: Query<&Window, With<PrimaryWindow>>,
    selectable: Query<(Entity, &BoardIndex, &Side), (With<game::Selectable>, With<Display3d>)>,
    selected: Query<Entity, With<game::Selected>>,
    children: Query<&Children>,
    mut selections: EventWriter<game::Selection>,
    state: Res<State<game::TurnState>>,
) {
    events
        .read()
        .filter(|ev| ev.state == ButtonState::Pressed)
        .for_each(|_| {
            windows.iter().for_each(|window| {
                window.cursor_position().and_then(|pos| {
                    cameras.iter().for_each(|(camera, gt)| {
                        camera.viewport_to_world(gt, pos).and_then(|ray| {
                            query
                                .iter()
                                .filter_map(|(entity, handle, gt)| {
                                    meshes.get(handle).map(|mesh| (entity, mesh, gt))
                                })
                                .for_each(|(entity, mesh, gt)| {
                                    hit::intersects3d(&ray, mesh, &gt).and_then(|_hit| {
                                        selectable
                                            .iter()
                                            .find_map(|(e, &board_index, &side)| {
                                                // Check the side of the selection if no piece is selected
                                                // Otherwise this is fine, select away
                                                let side_check =
                                                    !selected.is_empty() || state.get().0 == side;

                                                let hit_check = {
                                                    // This entity was hit (tile hitboxes)
                                                    let primary = entity == e;

                                                    // A child was hit (pieces)
                                                    let secondary = children
                                                        .iter_descendants(e)
                                                        .any(|child| child == entity);

                                                    primary || secondary
                                                };

                                                (side_check && hit_check).then_some(board_index)
                                            })
                                            .iter()
                                            .for_each(|&board_index| {
                                                info!("Board index selected: {:?}", board_index);
                                                selections
                                                    .send(game::Selection(board_index.clone()));
                                            });
                                        Some(())
                                    });
                                });
                            Some(())
                        });
                    });
                    Some(())
                });
            });
        });
}

fn selected_gizmo(
    selected: Query<&Transform, (With<game::Selected>, With<Display3d>)>,
    mut gizmos: Gizmos,
) {
    selected.iter().for_each(|transform| {
        gizmos.cuboid(transform.clone(), Color::GREEN);
    })
}

fn moves_gizmo(
    selected: Query<&BoardIndex, (With<game::Piece>, With<game::Selected>, With<Display3d>)>,
    board: Res<Board>,
    mut gizmos: Gizmos,
) {
    selected.iter().for_each(|idx| {
        board
            .valid_moves(*idx)
            .iter()
            .map(|i| Transform::from_translation(board_translation(i)))
            .for_each(|t| gizmos.cuboid(t, Color::WHITE))
    });
}

fn set_valid_move_model(
    mut events: Query<
        (&mut Handle<Scene>, &mut Visibility),
        (With<Display3d>, Added<game::ValidMove>),
    >,
    gltfs: Res<Assets<Gltf>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    let assets_handle = tweak
        .get_handle::<Gltf>("display3d_models_assets_file")
        .unwrap();
    if let Some(gltf) = gltfs.get(assets_handle) {
        events.iter_mut().for_each(|(mut handle, mut visibility)| {
            *handle = gltf
                .named_scenes
                .get(
                    tweak
                        .get::<String>("display3d_models_scenes_valid_move")
                        .unwrap()
                        .as_str(),
                )
                .unwrap()
                .clone();

            *visibility = Visibility::Hidden;
        })
    }
}

fn _play_valid_move_animation(_players: Query<&AnimationPlayer>) {
    todo!();
}

fn pick_up(
    mut events: Query<
        (Entity, &game::Piece),
        (With<game::Piece>, With<Display3d>, Added<game::Selected>),
    >,
    gltfs: Res<Assets<Gltf>>,
    children: Query<&Children>,
    mut players: Query<(&Name, &mut AnimationPlayer)>,
    clips: Res<Assets<AnimationClip>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    events.iter_mut().for_each(|(entity, piece)| {
        let assets_handle = tweak
            .get_handle::<Gltf>("display3d_models_assets_file")
            .unwrap();
        let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
        info!("Pickup animation for {:?}", entity);
        children.iter_descendants(entity).for_each(|child| {
            info!("    Child: {:?}", child);
            if let Ok((name, mut player)) = players.get_mut(child) {
                info!("Picking up {:?} ({:?}) {:?}", name, entity, piece);
                let pickup_animation =
                    format!("display3d_models_animations_pick_up_{:?}", piece).to_ascii_lowercase();
                let pickup_handle = gltf
                    .named_animations
                    .get(
                        tweak
                            .get::<String>(pickup_animation.as_str())
                            .unwrap()
                            .as_str(),
                    )
                    .expect("Pickup Animation");
                let idle_animation =
                    format!("display3d_models_animations_idle_{:?}", piece).to_ascii_lowercase();
                let idle_handle = gltf
                    .named_animations
                    .get(
                        tweak
                            .get::<String>(idle_animation.as_str())
                            .unwrap()
                            .as_str(),
                    )
                    .expect("Idle animation");
                if let Some(pickup_clip) = clips.get(pickup_handle) {
                    if let Some(idle_clip) = clips.get(idle_handle) {
                        if pickup_clip.compatible_with(name) && idle_clip.compatible_with(name) {
                            player
                                .start_with_transition(
                                    pickup_handle.clone(),
                                    Duration::from_secs_f32(0.75),
                                )
                                .start_with_transition(
                                    idle_handle.clone(),
                                    Duration::from_secs_f32(1.5),
                                )
                                .set_repeat(RepeatAnimation::Forever);
                        }
                    };
                }
            }
        })
    });
}

fn put_down(
    mut events: RemovedComponents<game::Selected>,
    mut query: Query<&game::Piece, (With<game::Piece>, With<game::Selectable>, With<Display3d>)>,
    gltfs: Res<Assets<Gltf>>,
    children: Query<&Children>,
    mut players: Query<(&Name, &mut AnimationPlayer)>,
    clips: Res<Assets<AnimationClip>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(&tweaks_file.handle.clone())
        .expect("Load tweakfile");
    events.read().for_each(|entity| {
        if let Ok(piece) = query.get_mut(entity) {
            let assets_handle = tweak
                .get_handle::<Gltf>("display3d_models_assets_file")
                .unwrap();
            let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
            children.iter_descendants(entity).for_each(|child| {
                if let Ok((name, mut player)) = players.get_mut(child) {
                    info!("Putting down {:?}", entity);
                    let putdown_animation =
                        format!("display3d_models_animations_put_down_{:?}", piece)
                            .to_ascii_lowercase();
                    let putdown_handle = gltf
                        .named_animations
                        .get(
                            tweak
                                .get::<String>(putdown_animation.as_str())
                                .unwrap()
                                .as_str(),
                        )
                        .expect("PutDown Animation");
                    if let Some(putdown_clip) = clips.get(putdown_handle) {
                        if putdown_clip.compatible_with(name) {
                            player
                                .start_with_transition(
                                    putdown_handle.clone(),
                                    Duration::from_secs_f32(0.75),
                                )
                                .set_repeat(RepeatAnimation::Never);
                        }
                    }
                }
            })
        }
    })
}

fn set_tile_hitbox(
    mut events: Query<(&mut Transform, &BoardIndex), (With<Display3d>, Added<game::Tile>)>,
) {
    events.iter_mut().for_each(|(mut transform, index)| {
        *transform = Transform::from_translation(board_translation(index));
    });
}

fn opening_animation(mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>) {
    players.iter_mut().for_each(|mut player| {
        info!("Playing intro camera animation");
        player.resume()
    });
}

// When called skips any running animations
fn skip_animation(
    mut players: Query<&mut AnimationPlayer>,
    clips: Res<Assets<AnimationClip>>,
    time: Res<Time>,
) {
    players.iter_mut().for_each(|mut p| {
        if let Some(c) = clips.get(p.animation_clip()) {
            // HACK: We should be able to skip to the end of an animation
            // But implementation details means this is as close as we can get...
            p.seek_to(c.duration() - (2.0 * time.delta_seconds()));
        }
    })
}

fn switch_sides(
    mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>,
    gltfs: Res<Assets<Gltf>>,
    state: Res<State<game::TurnState>>,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");
    let assets_handle = tweak
        .get_handle::<Gltf>("display3d_models_assets_file")
        .unwrap();
    let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
    players.iter_mut().for_each(|mut player| {
        let animation = match state.get() {
            game::TurnState(game::Side::A) => gltf.named_animations.get(
                tweak
                    .get::<String>("display3d_models_animations_turn_a")
                    .unwrap()
                    .as_str(),
            ),
            game::TurnState(game::Side::B) => gltf.named_animations.get(
                tweak
                    .get::<String>("display3d_models_animations_turn_b")
                    .unwrap()
                    .as_str(),
            ),
        };
        player.start_with_transition(
            animation.expect("Camera Transition Animation").clone(),
            Duration::from_secs_f32(1.00),
        );
    });
}

fn scale_lighting(
    mut directional: Query<(
        Entity,
        &mut DirectionalLight,
        Option<&Original<DirectionalLight>>,
    )>,
    mut spot: Query<(Entity, &mut SpotLight, Option<&Original<SpotLight>>)>,
    mut point: Query<(Entity, &mut PointLight, Option<&Original<PointLight>>)>,
    mut commands: Commands,
    tweaks: Res<Assets<Tweaks>>,
    tweaks_file: Res<tweak::GameTweaks>,
) {
    let tweak = tweaks
        .get(tweaks_file.handle.clone())
        .expect("Load tweakfile");

    let directional_tweak = tweak
        .get::<f32>("display3d_lights_scaling_directional")
        .expect("Directional lighting scalar");
    directional
        .iter_mut()
        .for_each(|(entity, mut val, original)| {
            debug!("Scaling directional light {:?}", entity);
            if let Some(Original(v)) = original {
                val.illuminance = v.illuminance * directional_tweak;
            } else {
                commands.entity(entity).insert(Original(val.clone()));
                val.illuminance *= directional_tweak;
            }
        });

    let spot_tweak = tweak
        .get::<f32>("display3d_lights_scaling_spot")
        .expect("Spot lighting scalar");
    spot.iter_mut().for_each(|(entity, mut val, original)| {
        debug!("Scaling spot light {:?}", entity);
        if let Some(Original(v)) = original {
            val.intensity = v.intensity * spot_tweak;
        } else {
            commands.entity(entity).insert(Original(val.clone()));
            val.intensity *= spot_tweak;
        }
    });

    let point_tweak = tweak
        .get::<f32>("display3d_lights_scaling_point")
        .expect("Point lighting scalar");
    point.iter_mut().for_each(|(entity, mut val, original)| {
        debug!("Scaling point light {:?}", entity);
        if let Some(Original(v)) = original {
            val.intensity = v.intensity * point_tweak;
        } else {
            commands.entity(entity).insert(Original(val.clone()));
            val.intensity *= point_tweak;
        }
    });
}

pub(super) mod tweaks {
    use bevy::{
        core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping},
        math::Vec3,
        pbr::{FogFalloff, FogSettings, ScreenSpaceAmbientOcclusionQualityLevel},
        prelude::*,
        render::{
            color::Color,
            view::{ColorGrading, Msaa},
        },
    };
    use serde::Deserialize;

    #[derive(Debug, Deserialize)]
    pub enum TweakTonemapping {
        None,
        Reinhard,
        ReinhardLuminance,
        AcesFitted,
        AgX,
        SomewhatBoringDisplayTransform,
        TonyMcMapface,
        BlenderFilmic,
    }

    impl From<TweakTonemapping> for Tonemapping {
        fn from(src: TweakTonemapping) -> Tonemapping {
            match src {
                TweakTonemapping::None => Tonemapping::None,
                TweakTonemapping::Reinhard => Tonemapping::Reinhard,
                TweakTonemapping::ReinhardLuminance => Tonemapping::ReinhardLuminance,
                TweakTonemapping::AcesFitted => Tonemapping::AcesFitted,
                TweakTonemapping::AgX => Tonemapping::AgX,
                TweakTonemapping::SomewhatBoringDisplayTransform => {
                    Tonemapping::SomewhatBoringDisplayTransform
                }
                TweakTonemapping::TonyMcMapface => Tonemapping::TonyMcMapface,
                TweakTonemapping::BlenderFilmic => Tonemapping::BlenderFilmic,
            }
        }
    }

    #[derive(Debug, Deserialize)]
    pub struct TweakBloomSettings {
        intensity: f32,
    }

    impl From<TweakBloomSettings> for BloomSettings {
        fn from(src: TweakBloomSettings) -> BloomSettings {
            BloomSettings {
                intensity: src.intensity,
                ..default()
            }
        }
    }

    #[derive(Debug, Deserialize)]
    pub enum TweakMsaa {
        Off,
        Sample2,
        Sample4,
        Sample8,
    }

    impl From<TweakMsaa> for Msaa {
        fn from(src: TweakMsaa) -> Msaa {
            match src {
                TweakMsaa::Off => Msaa::Off,
                TweakMsaa::Sample2 => Msaa::Sample2,
                TweakMsaa::Sample4 => Msaa::Sample4,
                TweakMsaa::Sample8 => Msaa::Sample8,
            }
        }
    }

    #[derive(Deserialize, Debug)]
    pub enum TweakScreenSpaceAmbientOcclusionQualityLevel {
        Low,
        Medium,
        High,
        Ultra,
    }

    impl From<TweakScreenSpaceAmbientOcclusionQualityLevel>
        for ScreenSpaceAmbientOcclusionQualityLevel
    {
        fn from(
            src: TweakScreenSpaceAmbientOcclusionQualityLevel,
        ) -> ScreenSpaceAmbientOcclusionQualityLevel {
            match src {
                TweakScreenSpaceAmbientOcclusionQualityLevel::Low => {
                    ScreenSpaceAmbientOcclusionQualityLevel::Low
                }
                TweakScreenSpaceAmbientOcclusionQualityLevel::Medium => {
                    ScreenSpaceAmbientOcclusionQualityLevel::Medium
                }
                TweakScreenSpaceAmbientOcclusionQualityLevel::High => {
                    ScreenSpaceAmbientOcclusionQualityLevel::High
                }
                TweakScreenSpaceAmbientOcclusionQualityLevel::Ultra => {
                    ScreenSpaceAmbientOcclusionQualityLevel::Ultra
                }
            }
        }
    }

    #[derive(Debug, Deserialize)]
    pub struct TweakFogSettings {
        pub color: Color,
        pub directional_light_color: Color,
        pub directional_light_exponent: f32,
        pub falloff: TweakFogFalloff,
    }

    impl From<TweakFogSettings> for FogSettings {
        fn from(src: TweakFogSettings) -> FogSettings {
            FogSettings {
                color: src.color,
                directional_light_color: src.directional_light_color,
                directional_light_exponent: src.directional_light_exponent,
                falloff: src.falloff.into(),
            }
        }
    }

    #[derive(Debug, Deserialize)]
    pub enum TweakFogFalloff {
        Linear {
            start: f32,
            end: f32,
        },
        Exponential {
            density: f32,
        },
        ExponentialSquared {
            density: f32,
        },
        Atmospheric {
            extinction: Vec3,
            inscattering: Vec3,
        },
    }

    impl From<TweakFogFalloff> for FogFalloff {
        fn from(src: TweakFogFalloff) -> FogFalloff {
            match src {
                TweakFogFalloff::Linear { start, end } => FogFalloff::Linear { start, end },
                TweakFogFalloff::Exponential { density } => FogFalloff::Exponential { density },
                TweakFogFalloff::ExponentialSquared { density } => {
                    FogFalloff::ExponentialSquared { density }
                }
                TweakFogFalloff::Atmospheric {
                    extinction,
                    inscattering,
                } => FogFalloff::Atmospheric {
                    extinction,
                    inscattering,
                },
            }
        }
    }

    #[derive(Debug, Deserialize)]
    pub struct TweakColorGrading {
        pub exposure: f32,
        pub gamma: f32,
        pub pre_saturation: f32,
        pub post_saturation: f32,
    }

    impl From<TweakColorGrading> for ColorGrading {
        fn from(src: TweakColorGrading) -> ColorGrading {
            ColorGrading {
                exposure: src.exposure,
                gamma: src.gamma,
                pre_saturation: src.pre_saturation,
                post_saturation: src.post_saturation,
            }
        }
    }
}

/// Type expressing the extended material of standardMaterial + dissolveMaterial
type DissolveMaterial = ExtendedMaterial<StandardMaterial, DissolveExtension>;

/// Material extension for dissolving effect
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
struct DissolveExtension {
    #[uniform(100)]
    percentage: f32,
}

impl MaterialExtension for DissolveExtension {
    fn fragment_shader() -> ShaderRef {
        "shaders/dissolve.wgsl".into()
    }
}

// Component for 'backing up' components which are temporarily not used
#[derive(Debug, Component, Clone)]
struct Backup<T: Component>(T);

/// Sets up all pieces to have an associated "dissolve" material ready for capture
fn setup_capture_piece(
    // All entities with materials are candidates for this procedure
    events: Query<
        (Entity, &Handle<StandardMaterial>),
        (
            Added<Handle<StandardMaterial>>,
            Changed<Handle<StandardMaterial>>,
        ),
    >,
    // Only process newly created pieces (we do not delete pieces at runtime)
    query: Query<Entity, (With<Piece>, Added<Children>)>,
    // Children of pieces are the actual meshes that need materials
    parents: Query<&Parent>,
    // Used to create DissovleMaterial
    standard_materials: Res<Assets<StandardMaterial>>,
    // Used to create Handle<DissolveMaterial>
    mut dissolve_materials: ResMut<Assets<DissolveMaterial>>,
    // Used to insert Backup(Handle<DissolveMaterial>);
    mut commands: Commands,
    // Cache dissolve textures that have already been created
    mut cache: Local<HashMap<Handle<StandardMaterial>, Handle<DissolveMaterial>>>,
) {
    events
        .iter()
        // Only process if this is a child of a piece
        .filter(|(child, _)| query.iter_many(parents.iter_ancestors(*child)).count() > 0)
        // Handle this entity (mesh)
        .for_each(|(child, std_handle)| {
            let dis_handle = match cache.get(std_handle) {
                // We have not seen this material, so create a new dissolve material
                None => {
                    // Extension we will add to existing gltf-sourced materials
                    let extension = DissolveExtension { percentage: 1.0 };
                    // Base material we will extend for the duration of the dissolve effect
                    let base = standard_materials
                        .get(std_handle)
                        .expect("Resolve material data")
                        .clone();

                    dissolve_materials.add(ExtendedMaterial { base, extension })
                }
                Some(dis_handle) => dis_handle.clone(),
            };

            // Insert this handle into the cache (may be redundant)
            cache.insert(std_handle.clone(), dis_handle.clone());

            // Add the dissolve handle as a Backup(T)
            commands.entity(child).insert(Backup(dis_handle.clone()));
        });
}

/// When a piece is captured...
/// 1. Play a cool "captured" animation and a neat sound
/// 2. Move the piece to the side of the board
/// 3. Play the same "captured" animation in reverse
/// The animation is like a 'beam me up scotty' sorta thing.
fn capture_piece(
    events: Query<Entity, (With<Display3d>, Added<game::Captured>)>,
    mut query: Query<
        (&mut Visibility, &mut Transform, &Side),
        (With<Display3d>, With<game::Captured>),
    >,
    mut state: Local<Option<game::CaptureFlow>>,
    mut kids: Local<Vec<Entity>>,
    mut prog: Local<f32>,
    mut dissolve_materials: ResMut<Assets<DissolveMaterial>>,
    object_standard_materials: Query<(
        Entity,
        &Handle<StandardMaterial>,
        &Backup<Handle<DissolveMaterial>>,
    )>,
    object_dissolve_materials: Query<(
        Entity,
        &Handle<DissolveMaterial>,
        &Backup<Handle<StandardMaterial>>,
    )>,
    children: Query<&Children>,
    mut commands: Commands,
    time: Res<Time>,
    score: Res<game::Score>,
) {
    let duration: f32 = 3.0;

    match *state {
        // State is None, so we need to initiate the animation by swapping the StadardMaterial for the DissolveMaterial
        None => {
            *state = events.iter().next().map(|entity| {
                // Reset dissolve progress
                *prog = 1.0;

                // store the kids we want to process
                *kids = object_standard_materials
                    .iter_many(children.iter_descendants(entity))
                    .map(|(child, std_handle, Backup(dis_handle))| {
                        // Swap standard and dissolve material
                        commands
                            .entity(child)
                            .insert(dis_handle.clone())
                            .insert(Backup(std_handle.clone()))
                            .remove::<Handle<StandardMaterial>>()
                            .remove::<Backup<Handle<DissolveMaterial>>>();

                        // Return child entity to be processed later in flow
                        child
                    })
                    .collect();

                // Set the next state to start fading out
                game::CaptureFlow::FadeOut(entity)
            });
        }
        Some(s) => {
            match s {
                game::CaptureFlow::FadeOut(_entity) => {
                    // Play fade-out animation
                    {
                        // Calculate how much of the animation has passed
                        let delta = time.delta_seconds() / duration;

                        // Set progress to current - delta
                        *prog -= delta;

                        // If progress is less than zero
                        if *prog <= 0.0 {
                            // Set to exactly 0 for simplicity
                            *prog = 0.0;

                            // Move to next state now that animation is done
                            *state = s.next();
                            // This takes effect after updating all children
                        }

                        object_dissolve_materials.iter_many(kids.iter()).for_each(
                            |(_child, dis_handle, _)| {
                                let dissolve_material = dissolve_materials
                                    .get_mut(dis_handle)
                                    .expect("Get the dissolve material");

                                // Change the material's value to create animation
                                dissolve_material.extension.percentage = *prog;

                                debug!(
                                    "Play fade out animation {:?} {:?}",
                                    delta, dissolve_material.extension.percentage
                                );
                            },
                        );
                    }
                }
                game::CaptureFlow::Store(entity) => {
                    let (mut v, mut t, side) = query
                        .get_mut(entity)
                        .expect("Visibility and Transform of captured piece");

                    // Hide piece now that animation is done
                    *v = Visibility::Hidden;

                    // HACK: This is dirty. Why side, but score.captures(!side)?
                    info!(
                        "Capture translation: ({:?}, {:?}) {:?}",
                        side,
                        score.captures(!*side).saturating_sub(1),
                        t.translation
                    );
                    t.translation =
                        capture_translation(side, score.captures(!*side).saturating_sub(1));

                    *state = s.next();
                }
                game::CaptureFlow::FadeIn(entity) => {
                    let (mut v, _, _) = query
                        .get_mut(entity)
                        .expect("Visibility and Transform of captured piece");

                    // Show piece now that it  is moved
                    *v = Visibility::Inherited;

                    // Calculate how much of the animation has passed
                    let delta = time.delta_seconds() / duration;

                    // Move the animation forward by delta
                    *prog += delta;

                    // If we have completed the animation
                    if *prog >= 1.0 {
                        // Move to next state now that animation is done
                        *state = s.next();
                    }

                    // Play fade-in animation
                    {
                        object_dissolve_materials.iter_many(kids.iter()).for_each(
                            |(child, dis_handle, Backup(std_handle))| {
                                let dissolve_material = dissolve_materials
                                    .get_mut(dis_handle)
                                    .expect("Get the dissolve material");

                                // Change the material's value to create animation
                                dissolve_material.extension.percentage = *prog;

                                debug!(
                                    "Play fade in animation {:?} {:?}",
                                    delta, dissolve_material.extension.percentage
                                );

                                // If we are done with the animation cleanup
                                if dissolve_material.extension.percentage >= 1.0 {
                                    // Re-add the original material
                                    commands
                                        .entity(child)
                                        .insert(std_handle.clone())
                                        .insert(Backup(dis_handle.clone()))
                                        .remove::<Handle<DissolveMaterial>>()
                                        .remove::<Backup<Handle<StandardMaterial>>>();
                                }
                            },
                        );
                    }
                }
            }
        }
    }
}

fn debug_selected(
    query: Query<(Entity, &BoardIndex, &Piece, &Side), With<game::Selected>>,
    mut debug_info: ResMut<debug::DebugInfo>,
) {
    query.iter().for_each(|(e, bi, p, s)| {
        debug_info.set(
            "Active".into(),
            format!(
                "\n>>ID: {:?}\n>>Piece: {:?}\n>>Side: {:?}\n>>Index: {:?}",
                e, p, s, bi
            ),
        );
    });
}
