use bevy::{gltf::Gltf, prelude::*, utils::HashMap};
use monologue_trees::debug::*;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins.set(WindowPlugin {
                primary_window: Some(Window {
                    title: "GLTF Inspector".into(),
                    resolution: (640., 480.).into(),
                    ..default()
                }),
                ..default()
            }),
            DebugInfoPlugin,
        ))
        .add_event::<ResetScene>()
        .add_event::<SpawnScene>()
        .init_resource::<Current>()
        .add_systems(Startup, spawn_ui)
        .add_systems(PreUpdate, (add_camera_ui, add_scene_ui, add_animation_ui))
        .add_systems(
            Update,
            (
                drag_and_drop,
                loading,
                reset_scene.before(spawn_scene),
                spawn_scene.after(reset_scene),
                control_scene,
                control_animation,
                control_camera,
                control_default_camera,
                control_default_light,
            ),
        )
        .add_systems(
            PostUpdate,
            (
                clean_ui::<SceneButton>,
                clean_ui::<AnimationButton>,
                clean_ui::<CameraButton>,
            ),
        )
        .run();
}

#[derive(Component)]
struct SceneMarker;

#[derive(Component)]
struct DefaultCamera;

#[derive(Component)]
struct DefaultLight;

#[derive(Component)]
struct MainUi;

#[derive(Component)]
struct UiTitle;

#[derive(Component)]
struct InstructionsUi;

#[derive(Component)]
struct SceneSelectUi;

#[derive(Component, PartialEq, Debug)]
struct SceneButton(Handle<Scene>);

#[derive(Event)]
struct SpawnScene(Handle<Scene>);

#[derive(Component)]
struct AnimationSelectUi;

#[derive(Component, PartialEq, Debug)]
struct AnimationButton(Handle<AnimationClip>);

#[derive(Component)]
struct CameraSelectUi;

#[derive(Component, PartialEq, Debug)]
struct CameraButton(Entity);

#[derive(Resource, Default)]
struct Current {
    gltf: Handle<Gltf>,
    scenes: HashMap<String, Handle<Scene>>,
    animations: HashMap<String, Handle<AnimationClip>>,
}

#[derive(Event)]
struct ResetScene;

#[derive(Component)]
struct ResetAnimation;

fn drag_and_drop(
    mut events: EventReader<FileDragAndDrop>,
    server: Res<AssetServer>,
    mut current: ResMut<Current>,
) {
    events
        .iter()
        .filter_map(|event| match event {
            FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf),
            _ => None,
        })
        .map(|path_buf| server.load(path_buf.to_str().expect("PathBuf to str")))
        .for_each(|handle| current.gltf = handle.clone());
}

fn spawn_ui(mut commands: Commands) {
    // TODO: Warn no camera (hidden)
    // TODO: Scene select container
    // TODO: Animation Play/Pause Placeholder
    commands.spawn((
        Camera3dBundle {
            transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
            ..default()
        },
        UiCameraConfig { show_ui: true },
        DefaultCamera,
    ));
    commands.spawn((
        DirectionalLightBundle {
            transform: Transform::default().looking_at(Vec3::new(1.0, -1.0, -1.0), Vec3::Y),
            ..default()
        },
        DefaultLight,
    ));
    commands
        .spawn((
            NodeBundle {
                style: Style {
                    flex_wrap: FlexWrap::Wrap,
                    flex_direction: FlexDirection::Row,
                    justify_content: JustifyContent::SpaceAround,
                    width: Val::Percent(100.0),
                    height: Val::Percent(100.0),
                    ..default()
                },
                ..default()
            },
            MainUi,
        ))
        .with_children(|parent| {
            parent
                .spawn(NodeBundle {
                    style: Style {
                        flex_direction: FlexDirection::Column,
                        ..default()
                    },
                    ..default()
                })
                .with_children(|parent| {
                    parent.spawn((
                        TextBundle::from_section(
                            "Drag and Drop .gltf/.glb file",
                            TextStyle {
                                color: Color::WHITE,
                                ..default()
                            },
                        ),
                        InstructionsUi,
                    ));
                    parent.spawn((
                        TextBundle::from_section(
                            "Using default camera",
                            TextStyle {
                                color: Color::WHITE,
                                ..default()
                            },
                        ),
                        DefaultCamera,
                    ));
                    parent.spawn((
                        TextBundle::from_section(
                            "Using default light",
                            TextStyle {
                                color: Color::WHITE,
                                ..default()
                            },
                        ),
                        DefaultLight,
                    ));
                });
            parent
                .spawn((
                    NodeBundle {
                        style: Style {
                            flex_direction: FlexDirection::Column,
                            ..default()
                        },
                        ..default()
                    },
                    SceneSelectUi,
                ))
                .with_children(|parent| {
                    parent.spawn((
                        TextBundle::from_section("Scenes", TextStyle { ..default() }),
                        UiTitle,
                    ));
                });
            parent
                .spawn((
                    NodeBundle {
                        style: Style {
                            flex_direction: FlexDirection::Column,
                            ..default()
                        },
                        ..default()
                    },
                    CameraSelectUi,
                ))
                .with_children(|parent| {
                    parent.spawn((
                        TextBundle::from_section("Cameras", TextStyle { ..default() }),
                        UiTitle,
                    ));
                });
            parent
                .spawn((
                    NodeBundle {
                        style: Style {
                            flex_direction: FlexDirection::Column,
                            ..default()
                        },
                        ..default()
                    },
                    AnimationSelectUi,
                ))
                .with_children(|parent| {
                    parent.spawn((
                        TextBundle::from_section("Animations", TextStyle { ..default() }),
                        UiTitle,
                    ));
                });
        });
}

/// When a new camera is loaded, clear the camera buttons
fn clean_ui<T: Component>(
    mut spawn_events: EventReader<SpawnScene>,
    mut clear_events: EventReader<ResetScene>,
    cameras: Query<Entity, With<T>>,
    mut commands: Commands,
) {
    // We don't care about the content of these events, just that we want to clear the UI after
    // each spawn or reset event, so we map each event to () and chain the two event streams.
    spawn_events
        .iter()
        .map(|_| ())
        .chain(clear_events.iter().map(|_| ()))
        .for_each(|_| {
            cameras.iter().for_each(|entity| {
                commands.entity(entity).despawn_recursive();
            });
        });
}

/// Translate UI button presses into scene spawn events
fn control_scene(
    mut events: EventWriter<SpawnScene>,
    interactions: Query<(&Interaction, &SceneButton), (Changed<Interaction>, With<Button>)>,
) {
    // Handle UI buttons
    interactions
        .iter()
        .for_each(|(interaction, SceneButton(handle))| match interaction {
            Interaction::Pressed => events.send(SpawnScene(handle.clone())),
            _ => (),
        });
}

/// Add scene buttons when a new scene is added/gltf is loaded
fn add_scene_ui(
    mut events: EventReader<SpawnScene>,
    current: Res<Current>,
    root: Query<Entity, With<SceneSelectUi>>,
    mut commands: Commands,
) {
    events.iter().for_each(|_| {
        commands.entity(root.single()).with_children(|parent| {
            current.scenes.iter().for_each(|(name, handle)| {
                parent
                    .spawn((
                        ButtonBundle {
                            background_color: BackgroundColor(Color::NONE),
                            ..default()
                        },
                        SceneButton(handle.clone()),
                    ))
                    .with_children(|parent| {
                        parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
                    });
            });
        });
    });
}

fn control_animation(
    interactions: Query<(&Interaction, &AnimationButton), (Changed<Interaction>, With<Button>)>,
    mut players: Query<(&mut AnimationPlayer, &Name)>,
    clips: Res<Assets<AnimationClip>>,
) {
    // For each button interaction
    interactions
        .iter()
        .for_each(|(interaction, AnimationButton(handle))| match interaction {
            // If this is a butotn press
            Interaction::Pressed => players
                .iter_mut()
                // Find all entities compatible with this animation
                .filter(|(_, name)| {
                    clips
                        .get(handle)
                        .expect("Check animation clip compatability")
                        .compatible_with(name)
                })
                // Play the given (checked compatible) animation
                .for_each(|(mut player, _)| {
                    player.play(handle.clone()).repeat();
                }),
            _ => (),
        })
}

/// Add animation buttons when a new animation is added/gltf is loaded
fn add_animation_ui(
    mut events: EventReader<SpawnScene>,
    current: Res<Current>,
    root: Query<Entity, With<AnimationSelectUi>>,
    mut commands: Commands,
) {
    events.iter().for_each(|_| {
        commands.entity(root.single()).with_children(|parent| {
            current.animations.iter().for_each(|(name, handle)| {
                parent
                    .spawn((
                        ButtonBundle {
                            background_color: BackgroundColor(Color::NONE),
                            ..default()
                        },
                        AnimationButton(handle.clone()),
                    ))
                    .with_children(|parent| {
                        parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
                    });
            });
        });
    });
}

fn control_camera(
    interactions: Query<(&Interaction, &CameraButton), (Changed<Interaction>, With<Button>)>,
    mut cameras: Query<(Entity, &mut Camera), Without<DefaultCamera>>,
) {
    interactions
        .iter()
        .for_each(|(interaction, CameraButton(entity))| match interaction {
            Interaction::Pressed => cameras
                .iter_mut()
                .for_each(|(e, mut camera)| camera.is_active = e == *entity),
            _ => (),
        })
}

/// If there are no other cameras, use default camera
fn control_default_camera(
    mut main_camera: Query<&mut Camera, With<DefaultCamera>>,
    other_cameras: Query<Entity, (With<Camera>, Without<DefaultCamera>)>,
    mut text_indicators: Query<&mut Visibility, With<DefaultCamera>>,
) {
    // Figure out if we should use default camera
    let state = other_cameras.is_empty();

    // Toggle camera
    main_camera
        .iter_mut()
        .for_each(|mut cam| cam.is_active = state);

    // Update UI indicator
    text_indicators.iter_mut().for_each(|mut vis| {
        *vis = match state {
            true => Visibility::Visible,
            false => Visibility::Hidden,
        }
    })
}

/// If there are no other lights, use default light
fn control_default_light(
    mut toggle: Query<&mut Visibility, With<DefaultLight>>,
    other_lights: Query<
        Entity,
        (
            Or<(With<SpotLight>, With<DirectionalLight>, With<PointLight>)>,
            Without<DefaultLight>,
        ),
    >,
) {
    toggle.iter_mut().for_each(|mut vis| {
        *vis = if other_lights.is_empty() {
            Visibility::Visible
        } else {
            Visibility::Hidden
        }
    });
}

/// Add camera buttons when a new camera is added/gltf is loaded
fn add_camera_ui(
    events: Query<(Entity, &Name), Added<Camera>>,
    ui_root: Query<Entity, With<CameraSelectUi>>,
    mut commands: Commands,
) {
    events.iter().for_each(|(entity, name)| {
        commands.entity(ui_root.single()).with_children(|parent| {
            parent
                .spawn((
                    ButtonBundle {
                        background_color: BackgroundColor(Color::NONE),
                        ..default()
                    },
                    CameraButton(entity),
                ))
                .with_children(|parent| {
                    parent.spawn(TextBundle::from_section(name, TextStyle { ..default() }));
                });
        });
    })
}

/// Load gltfs and spawn default scene
fn loading(
    mut events: EventReader<AssetEvent<Gltf>>,
    gltfs: Res<Assets<Gltf>>,
    mut spawn: EventWriter<SpawnScene>,
    mut current: ResMut<Current>,
) {
    events.iter().for_each(|event| {
        match event {
            AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
                let gltf = gltfs.get(handle).expect("Loaded Gltf");

                // Save active scenes
                current.scenes = gltf.named_scenes.clone();
                current.animations = gltf.named_animations.clone();

                // Despawn existing scene
                let default_scene = gltf.default_scene.clone().expect("Default scene");
                spawn.send(SpawnScene(default_scene));
            }
            AssetEvent::Removed { .. } => warn!("Ignoring asset removal"),
        }
    });
}

/// Reset the current scene
fn reset_scene(
    mut events: EventReader<ResetScene>,
    mut commands: Commands,
    current: Query<Entity, With<SceneMarker>>,
) {
    events.iter().for_each(|_| {
        info!("Reset scene");

        current
            .iter()
            .for_each(|e| commands.entity(e).despawn_recursive())
    });
}

/// Spawn a desired scene
fn spawn_scene(
    mut events: EventReader<SpawnScene>,
    mut commands: Commands,
    current: Query<Entity, With<SceneMarker>>,
) {
    // Handle SpawnScene events
    events.iter().for_each(|SpawnScene(handle)| {
        info!("Reset scene (inline)");
        current
            .iter()
            .for_each(|e| commands.entity(e).despawn_recursive());

        info!("Spawning scene {:?}", handle);
        commands.spawn((
            SceneBundle {
                scene: handle.clone(),
                ..default()
            },
            SceneMarker,
        ));
    });
}
