use anput::{
    commands::{CommandBuffer, DespawnCommand, SpawnCommand},
    entity::Entity,
    query::{Query, Update},
    scheduler::{GraphScheduler, GraphSchedulerPlugin},
    systems::SystemContext,
    universe::{Res, Universe},
    world::World,
};
use moirai::jobs::Jobs;
use rand::{Rng, rng};
use std::error::Error;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum MonsterEvolution {
    #[default]
    Puppy,
    Wolf,
}

#[derive(Debug, Default, Clone, Copy)]
struct Stats {
    created: usize,
    updated: usize,
    destroyed: usize,
}

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

    for index in 0..10 {
        println!("* Iteration: {index}");
        scheduler.run(&jobs, &mut universe)?;
    }

    Ok(())
}

fn spawn_monster(context: SystemContext) -> Result<(), Box<dyn Error>> {
    let mut commands = context.fetch::<Res<true, &mut CommandBuffer>>()?;

    for _ in 0..rng().random_range(0..3) {
        commands.command(SpawnCommand::new((MonsterEvolution::default(),)));
    }

    Ok(())
}

fn evolve_monster(context: SystemContext) -> Result<(), Box<dyn Error>> {
    let (world, mut commands, monster_query) = context.fetch::<(
        &World,
        Res<true, &mut CommandBuffer>,
        Query<true, (Entity, Update<MonsterEvolution>)>,
    )>()?;

    for (entity, mut monster) in monster_query.query(world) {
        match *monster.read() {
            MonsterEvolution::Puppy => {
                *monster.write_notified(world) = MonsterEvolution::Wolf;
            }
            MonsterEvolution::Wolf => {
                commands.command(DespawnCommand::new(entity));
            }
        };
    }

    Ok(())
}

fn stats_react(context: SystemContext) -> Result<(), Box<dyn Error>> {
    let (world, mut stats) = context.fetch::<(&World, Res<true, &mut Stats>)>()?;

    for entity in world.added().iter_of::<MonsterEvolution>() {
        println!("Monster created: {entity}");
        stats.created += 1;
    }

    for entity in world.updated().unwrap().iter_of::<MonsterEvolution>() {
        println!("Monster updated: {entity}");
        stats.updated += 1;
    }

    for entity in world.removed().iter_of::<MonsterEvolution>() {
        println!("Monster destroyed: {entity}");
        stats.destroyed += 1;
    }

    Ok(())
}
