//! Stress test: large entity count with multiple processors and explicit ordering.
//!
//! Spawns many entities and runs several processors per tick to exercise the
//! scheduler's topological sort, wave partitioning, and per-block parallel execution.
//!
//! Run with: `cargo run -p latticeon --example ecs_stress --release`

use std::marker::PhantomData;
use std::sync::atomic::{AtomicU64, Ordering as AtomicOrd};
use std::sync::Mutex;
use std::time::Instant;

use latticeon::ecs::prelude::*;

// ---------------------------------------------------------------------------
// Components
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Component)]
struct Position(f32, f32);

#[derive(Debug, Clone, Component)]
struct Velocity(f32, f32);

#[derive(Debug, Clone, Component)]
struct Health(f32);

#[derive(Debug, Clone, Component)]
struct Damage(f32);

// ---------------------------------------------------------------------------
// Diagnostics: collect unique thread IDs per processor on the first tick.
// ---------------------------------------------------------------------------

static MOVE_ENTITIES: AtomicU64 = AtomicU64::new(0);
static DAMAGE_ENTITIES: AtomicU64 = AtomicU64::new(0);
static BOUNDS_ENTITIES: AtomicU64 = AtomicU64::new(0);
static REGEN_ENTITIES: AtomicU64 = AtomicU64::new(0);

static MOVE_THREADS: Mutex<Vec<u64>> = Mutex::new(Vec::new());
static DAMAGE_THREADS: Mutex<Vec<u64>> = Mutex::new(Vec::new());
static BOUNDS_THREADS: Mutex<Vec<u64>> = Mutex::new(Vec::new());
static REGEN_THREADS: Mutex<Vec<u64>> = Mutex::new(Vec::new());

fn tid() -> u64 {
    let id = std::thread::current().id();
    let s = format!("{:?}", id);
    s.chars().filter(|c| c.is_ascii_digit()).collect::<String>().parse().unwrap_or(0)
}

fn record_thread(threads: &Mutex<Vec<u64>>) {
    let t = tid();
    let mut v = threads.lock().unwrap();
    if !v.contains(&t) {
        v.push(t);
    }
}

// ---------------------------------------------------------------------------
// Processors (with explicit dependency ordering)
// ---------------------------------------------------------------------------

#[derive(Default, Processor)]
struct MoveProcessor {
    #[read]  _vel: PhantomData<Velocity>,
    #[write] _pos: PhantomData<Position>,
}

impl MoveProcessorRun for MoveProcessor {
    fn run(&self, view: &mut MoveProcessorView<'_>, _commands: &Commands) {
        MOVE_ENTITIES.fetch_add(1, AtomicOrd::Relaxed);
        record_thread(&MOVE_THREADS);
        view.pos.0 += view.vel.0;
        view.pos.1 += view.vel.1;
    }
}

#[derive(Default, Processor)]
struct DamageProcessor {
    #[read]  _dmg: PhantomData<Damage>,
    #[write] _hp:  PhantomData<Health>,
}

impl DamageProcessorRun for DamageProcessor {
    fn run(&self, view: &mut DamageProcessorView<'_>, _commands: &Commands) {
        DAMAGE_ENTITIES.fetch_add(1, AtomicOrd::Relaxed);
        record_thread(&DAMAGE_THREADS);
        view.hp.0 -= view.dmg.0;
    }
}

#[derive(Default, Processor)]
#[after(MoveProcessor)]
struct BoundsClampProcessor {
    #[write] _pos: PhantomData<Position>,
}

impl BoundsClampProcessorRun for BoundsClampProcessor {
    fn run(&self, view: &mut BoundsClampProcessorView<'_>, _commands: &Commands) {
        BOUNDS_ENTITIES.fetch_add(1, AtomicOrd::Relaxed);
        record_thread(&BOUNDS_THREADS);
        view.pos.0 = view.pos.0.clamp(-1000.0, 1000.0);
        view.pos.1 = view.pos.1.clamp(-1000.0, 1000.0);
    }
}

#[derive(Default, Processor)]
#[after(DamageProcessor)]
struct RegenProcessor {
    #[write] _hp: PhantomData<Health>,
}

impl RegenProcessorRun for RegenProcessor {
    fn run(&self, view: &mut RegenProcessorView<'_>, _commands: &Commands) {
        REGEN_ENTITIES.fetch_add(1, AtomicOrd::Relaxed);
        record_thread(&REGEN_THREADS);
        if view.hp.0 < 50.0 {
            view.hp.0 += 0.5;
        }
    }
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

const ENTITY_COUNT: usize = 100_000;
const TICK_COUNT: usize = 100;

fn main() {
    println!(
        "Stress test: {} entities, {} ticks\n",
        ENTITY_COUNT, TICK_COUNT
    );

    // -- Spawn --
    let t0 = Instant::now();
    let mut universe = Universe::new();

    for i in 0..ENTITY_COUNT {
        let x = (i as f32 * 0.1).sin() * 100.0;
        let y = (i as f32 * 0.1).cos() * 100.0;
        let vx = (i % 7) as f32 - 3.0;
        let vy = (i % 5) as f32 - 2.0;

        if i % 3 == 0 {
            universe.spawn((
                Position(x, y),
                Velocity(vx, vy),
                Health(100.0),
                Damage(0.1),
            ));
        } else {
            universe.spawn((Position(x, y), Velocity(vx, vy)));
        }
    }
    let spawn_ms = t0.elapsed().as_secs_f64() * 1000.0;
    println!("Spawn:    {:.2} ms", spawn_ms);
    println!("Entities: {}\n", universe.entity_count());

    // -- Build schedule --
    let mut schedule = Schedule::new();
    schedule.add_processor(MoveProcessor::default());
    schedule.add_processor(DamageProcessor::default());
    schedule.add_processor(BoundsClampProcessor::default());
    schedule.add_processor(RegenProcessor::default());

    // -- Run 1 tick to capture thread diagnostics --
    schedule.run(&mut universe);

    println!("--- Per-block parallelism diagnostic (first tick) ---");
    let mt = MOVE_THREADS.lock().unwrap();
    println!("  MoveProcessor:      {} entities on {} threads {:?}",
        MOVE_ENTITIES.load(AtomicOrd::Relaxed), mt.len(), *mt);
    let dt = DAMAGE_THREADS.lock().unwrap();
    println!("  DamageProcessor:    {} entities on {} threads {:?}",
        DAMAGE_ENTITIES.load(AtomicOrd::Relaxed), dt.len(), *dt);
    let bt = BOUNDS_THREADS.lock().unwrap();
    println!("  BoundsClampProc:    {} entities on {} threads {:?}",
        BOUNDS_ENTITIES.load(AtomicOrd::Relaxed), bt.len(), *bt);
    let rt = REGEN_THREADS.lock().unwrap();
    println!("  RegenProcessor:     {} entities on {} threads {:?}",
        REGEN_ENTITIES.load(AtomicOrd::Relaxed), rt.len(), *rt);
    drop((mt, dt, bt, rt));
    println!();

    // -- Run remaining ticks --
    let t1 = Instant::now();
    for _ in 1..TICK_COUNT {
        schedule.run(&mut universe);
    }
    let run_ms = t1.elapsed().as_secs_f64() * 1000.0;

    println!(
        "Run:      {:.2} ms total ({} ticks), {:.3} ms/tick",
        run_ms, TICK_COUNT - 1, run_ms / (TICK_COUNT - 1) as f64
    );

    // -- Sample entities --
    println!("\nSample entities after {} ticks:", TICK_COUNT);
    let mut count = 0;
    for (entity, pos, vel) in universe.query::<(&Position, &Velocity)>() {
        if count >= 5 { break; }
        print!("  {:?}: pos=({:.1}, {:.1}), vel=({:.1}, {:.1})",
            entity, pos.0, pos.1, vel.0, vel.1);
        if let Some(hp) = universe.get::<Health>(entity) {
            print!(", hp={:.1}", hp.0);
        }
        println!();
        count += 1;
    }
}
