use bevy::{
    animation::{animated_field, AnimationTarget, AnimationTargetId},
    input::{keyboard::KeyboardInput, ButtonState},
    prelude::*,
    utils::HashMap,
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, init)
        .add_systems(Update, switch)
        .run();
}

#[derive(Resource)]
struct AnimationStore(HashMap<String, (AnimationGraphHandle, AnimationNodeIndex)>);

fn init(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    mut animations: ResMut<Assets<AnimationClip>>,
    mut graphs: ResMut<Assets<AnimationGraph>>,
) {
    commands.spawn(Camera2d);

    commands.spawn((
        Text2d("Press 1/2/3 to trigger animation".into()),
        Node {
            top: Val::Px(-50.0),
            height: Val::Percent(100.0),
            ..default()
        },
    ));

    let mut hash_map = HashMap::new();
    {
        let mut a = AnimationClip::default();
        let animatable_curve = AnimatableCurve::new(
            animated_field!(Transform::translation),
            UnevenSampleAutoCurve::new([0.0, 1.0, 2.0, 3.0, 4.0].into_iter().zip([
                Vec3::new(200.0, 200.0, 0.0),
                Vec3::new(-200.0, 200.0, 0.0),
                Vec3::new(-200.0, -200.0, 0.0),
                Vec3::new(200.0, -200.0, 0.0),
                // in case seamless looping is wanted, the last keyframe should
                // be the same as the first one
                Vec3::new(200.0, 200.0, 0.0),
            ]))
            .expect("should be able to build translation curve because we pass in valid samples"),
        );
        a.add_curve_to_target(
            AnimationTargetId::from_name(&Name::new("Circle")),
            animatable_curve,
        );
        let (graph, animation_index) = AnimationGraph::from_clip(animations.add(a));
        let graph_handle = AnimationGraphHandle(graphs.add(graph));
        hash_map.insert("Box".into(), (graph_handle, animation_index));
    };
    {
        let mut a = AnimationClip::default();
        let animatable_curve = AnimatableCurve::new(
            animated_field!(Transform::translation),
            UnevenSampleAutoCurve::new([0.0, 1.0, 2.0].into_iter().zip([
                Vec3::new(200.0, 200.0, 0.0),
                Vec3::new(-200.0, -200.0, 0.0),
                Vec3::new(200.0, 200.0, 0.0),
            ]))
            .expect("should be able to build translation curve because we pass in valid samples"),
        );
        a.add_curve_to_target(
            AnimationTargetId::from_name(&Name::new("Circle")),
            animatable_curve,
        );
        let (graph, animation_index) = AnimationGraph::from_clip(animations.add(a));
        let graph_handle = AnimationGraphHandle(graphs.add(graph));
        hash_map.insert("Diag1".into(), (graph_handle, animation_index));
    };
    {
        let mut a = AnimationClip::default();
        let animatable_curve = AnimatableCurve::new(
            animated_field!(Transform::translation),
            UnevenSampleAutoCurve::new([0.0, 1.0, 2.0].into_iter().zip([
                Vec3::new(-200.0, 200.0, 0.0),
                Vec3::new(200.0, -200.0, 0.0),
                // in case seamless looping is wanted, the last keyframe should
                // be the same as the first one
                Vec3::new(-200.0, 200.0, 0.0),
            ]))
            .expect("should be able to build translation curve because we pass in valid samples"),
        );
        a.add_curve_to_target(
            AnimationTargetId::from_name(&Name::new("Circle")),
            animatable_curve,
        );
        let (graph, animation_index) = AnimationGraph::from_clip(animations.add(a));
        let graph_handle = AnimationGraphHandle(graphs.add(graph));
        hash_map.insert("Diag2".into(), (graph_handle, animation_index));
    };

    let store = AnimationStore(hash_map);
    commands.insert_resource(store);

    let shape = meshes.add(Circle::new(50.0));
    let material = materials.add(Color::WHITE);
    commands.spawn((
        Mesh2d(shape),
        MeshMaterial2d(material),
        Name::new("Circle"),
        AnimationPlayer::default(),
    ));
}

fn switch(
    mut commands: Commands,
    mut evr_kbd: EventReader<KeyboardInput>,
    mut query: Query<(Entity, &Name, &mut AnimationPlayer)>,
    store: Res<AnimationStore>,
) {
    evr_kbd
        .read()
        .filter(|ev| ev.state == ButtonState::Pressed)
        .for_each(|ev| match ev.key_code {
            KeyCode::Digit1 => {
                let (g, ai) = store.0.get("Box".into()).unwrap();
                let (e, n, mut ap) = query.single_mut();
                ap.stop_all().play(ai.clone());
                commands.entity(e).insert((
                    g.clone(),
                    AnimationTarget {
                        id: AnimationTargetId::from_name(n),
                        player: e,
                    },
                ));
            }
            KeyCode::Digit2 => {
                let (g, ai) = store.0.get("Diag1".into()).unwrap();
                let (e, n, mut ap) = query.single_mut();
                ap.stop_all().play(ai.clone());
                commands.entity(e).insert((
                    g.clone(),
                    AnimationTarget {
                        id: AnimationTargetId::from_name(n),
                        player: e,
                    },
                ));
            }
            KeyCode::Digit3 => {
                let (g, ai) = store.0.get("Diag2".into()).unwrap();
                let (e, n, mut ap) = query.single_mut();
                ap.stop_all().play(ai.clone());
                commands.entity(e).insert((
                    g.clone(),
                    AnimationTarget {
                        id: AnimationTargetId::from_name(n),
                        player: e,
                    },
                ));
            }
            _ => (),
        });
}
