use anput::{
    query::{Exclude, Include, Query},
    scheduler::{GraphScheduler, GraphSchedulerPlugin},
    systems::SystemContext,
    universe::Universe,
    world::{Relation, World},
};
use moirai::jobs::Jobs;
use std::error::Error;

#[derive(Debug, Default, Clone, Copy)]
struct Health(pub usize);

#[derive(Debug, Default, Clone, Copy)]
struct Strength(pub usize);

#[derive(Debug, Default, Clone, Copy)]
struct Hero;

#[derive(Debug, Default, Clone, Copy)]
struct Monster;

#[derive(Debug, Default, Clone, Copy)]
struct Pet;

fn main() -> Result<(), Box<dyn Error>> {
    let mut universe = Universe::default().with_plugin(
        GraphSchedulerPlugin::<true>::default()
            .system_setup(attack, |system| system.name("attack"))
            .system_setup(report_alive, |system| system.name("report_alive")),
    );
    let jobs = Jobs::default();
    let scheduler = GraphScheduler::<true>;

    // Setup hero and monsters.
    let hero = universe.simulation.spawn((Hero,))?;
    let wolf = universe
        .simulation
        .spawn((Monster, "wolf", Health(50), Strength(70)))?;
    let rabbit = universe
        .simulation
        .spawn((Monster, "rabbit", Health(20), Strength(30)))?;
    universe
        .simulation
        .spawn((Monster, "bear", Health(100), Strength(120)))?;
    universe
        .simulation
        .spawn((Monster, "lizard", Health(10), Strength(40)))?;

    // Setup pet relations.
    universe.simulation.relate::<true, _>(Pet, wolf, hero)?;
    universe.simulation.relate::<true, _>(Pet, rabbit, hero)?;

    scheduler.run(&jobs, &mut universe)?;

    Ok(())
}

fn attack(context: SystemContext) -> Result<(), Box<dyn Error>> {
    let (world, pet_monster_query, wild_monster_query) = context.fetch::<(
        &World,
        Query<
            true,
            (
                &&'static str,
                &mut Health,
                &Strength,
                Include<Monster>,
                Include<Relation<Pet>>,
            ),
        >,
        Query<
            true,
            (
                &&'static str,
                &mut Health,
                &Strength,
                Include<Monster>,
                Exclude<Relation<Pet>>,
            ),
        >,
    )>()?;

    for (pet_name, pet_health, pet_strength, _, _) in pet_monster_query.query(world) {
        for (wild_name, wild_health, wild_strength, _, _) in wild_monster_query.query(world) {
            if pet_health.0 == 0 || wild_health.0 == 0 {
                continue;
            }
            wild_health.0 = wild_health.0.saturating_sub(pet_strength.0);
            pet_health.0 = pet_health.0.saturating_sub(wild_strength.0);
            println!(
                "Pet {:?} and {:?} monster exchanged attacks. Pet health: {}, monster health: {}",
                pet_name, wild_name, pet_health.0, wild_health.0
            );
        }
    }

    Ok(())
}

fn report_alive(context: SystemContext) -> Result<(), Box<dyn Error>> {
    let (world, monster_query) =
        context.fetch::<(&World, Query<true, (&&'static str, &Health)>)>()?;

    let mut num_alive = 0;
    for (name, health) in monster_query.query(world) {
        println!("{:?} monster has {} health", name, health.0);

        if health.0 > 0 {
            num_alive += 1;
        }
    }

    if num_alive == 0 {
        println!("Dear Gods, what a bloodbath...");
    }

    Ok(())
}
