//! Example for `ScatteredMap`: registering named commands at link-time.
//!
//! Each command's lookup key is derived from its type name and lowercased at
//! compile time, inside the `#[scatter]` `const` block. Registration only names
//! the type — the `"ping"` / `"pong"` keys are generated by a `const fn`, never
//! written by hand.
use scattered_collect::{ScatteredMap, gather, scatter};

trait Command: Send + Sync {
    fn execute(&self);
}

#[gather]
static COMMANDS: ScatteredMap<&'static str, &'static dyn Command>;

/// Lowercase an ASCII string at compile time.
///
/// The result is returned as a fixed-size byte array so it can be held in a
/// `const` and re-borrowed as a `&'static str`.
const fn ascii_lowercase<const N: usize>(s: &str) -> [u8; N] {
    let src = s.as_bytes();
    let mut out = [0u8; N];
    let mut i = 0;
    while i < N {
        out[i] = src[i].to_ascii_lowercase();
        i += 1;
    }
    out
}

/// Register each command type under the lowercase form of its type name.
macro_rules! register_commands {
    ($($handle:ident: $type:ident),* $(,)?) => {
        $(
            #[scatter(COMMANDS)]
            static $handle: (&'static str, &'static dyn Command) = {
                // Compute the lowercase at compile time
                const LEN: usize = stringify!($type).len();
                const KEY_BYTES: [u8; LEN] = ascii_lowercase::<LEN>(stringify!($type));
                const KEY: &str = match ::core::str::from_utf8(&KEY_BYTES) {
                    Ok(key) => key,
                    Err(_) => panic!("command name was not valid UTF-8"),
                };
                (KEY, &$type)
            };
        )*
    };
}

struct Ping;
impl Command for Ping {
    fn execute(&self) {
        println!("Ping executed");
    }
}

struct Pong;
impl Command for Pong {
    fn execute(&self) {
        println!("Pong executed");
    }
}

register_commands!(
    PING: Ping,
    PONG: Pong,
);

fn main() {
    for name in ["ping", "pong"] {
        let command = COMMANDS.get(name).expect("command not found");
        print!("{name}: ");
        command.execute();
    }

    PING.value.execute();
    PONG.value.execute();
}
