use crate::rng::Rng;
use super::nickname::{self, Nickname};
#[derive(Clone, Copy)]
pub struct SepRules {
pub allowed: [u8; 3],
pub count: u8,
pub use_pct: u8,
}
pub const GITHUB_SEPS: SepRules = SepRules { allowed: [b'-', 0, 0], count: 1, use_pct: 9 };
pub const EMAIL_SEPS: SepRules = SepRules { allowed: [b'.', b'-', b'_'], count: 3, use_pct: 60 };
pub const TWITTER_SEPS: SepRules = SepRules { allowed: [b'_', 0, 0], count: 1, use_pct: 5 };
pub const INSTAGRAM_SEPS: SepRules = SepRules { allowed: [b'_', b'.', 0], count: 2, use_pct: 15 };
pub const FACEBOOK_SEPS: SepRules = SepRules { allowed: [b'.', 0, 0], count: 1, use_pct: 20 };
pub const TELEGRAM_SEPS: SepRules = SepRules { allowed: [b'_', 0, 0], count: 1, use_pct: 10 };
pub const YOUTUBE_SEPS: SepRules = SepRules { allowed: [b'-', b'_', b'.'], count: 3, use_pct: 15 };
pub const SOCIAL_SEPS: SepRules = SepRules { allowed: [b'_', b'.', 0], count: 2, use_pct: 15 };
pub const USERNAME_SEPS: SepRules = SepRules { allowed: [b'_', b'-', b'.'], count: 3, use_pct: 15 };
pub const NICKNAME_SEPS: SepRules = SepRules { allowed: [b'_', 0, 0], count: 1, use_pct: 45 };
fn sep_str(b: u8) -> &'static str {
match b {
b'-' => "-",
b'_' => "_",
b'.' => ".",
_ => "",
}
}
pub fn platform_sep(rules: SepRules) -> &'static str {
if rules.count == 0 {
""
} else {
sep_str(rules.allowed[0])
}
}
pub fn pick_sep(rng: &mut Rng, rules: SepRules) -> &'static str {
if rules.count == 0 {
return "";
}
let idx = usize::from(rules.count > 1 && rng.urange(0, 99) < 15);
sep_str(rules.allowed[idx])
}
pub fn maybe_sep(rng: &mut Rng, rules: SepRules) -> &'static str {
if rules.count == 0 || rng.urange(0, 99) >= rules.use_pct as usize {
return "";
}
pick_sep(rng, rules)
}
pub fn unique_tag(record: u64, key: u64) -> u64 {
let x = record.wrapping_add(1) ^ key;
x.wrapping_mul(0x9E37_79B9_7F4A_7C15)
}
pub(super) fn push_year2(buf: &mut String, h: &HandleInput<'_>) {
let y = if h.birth_year > 0 && (h.tag >> 40) % 100 < 60 {
(h.birth_year % 100) as u64
} else {
h.tag % 100
};
if y < 10 {
buf.push('0');
}
buf.push_str(itoa::Buffer::new().format(y));
}
pub(super) fn push_year4(buf: &mut String, h: &HandleInput<'_>) {
if h.birth_year > 0 && (h.tag >> 40) % 100 < 60 {
buf.push_str(itoa::Buffer::new().format(h.birth_year));
} else {
buf.push_str(itoa::Buffer::new().format(h.tag % 50 + 1975));
}
}
pub(super) fn push_bday_mmdd(buf: &mut String, h: &HandleInput<'_>) {
let (m, d) = if h.birth_month > 0 && (h.tag >> 40) % 100 < 60 {
(u64::from(h.birth_month), u64::from(h.birth_day))
} else {
((h.tag >> 5) % 12 + 1, h.tag % 28 + 1)
};
let mut ib = itoa::Buffer::new();
if m < 10 {
buf.push('0');
}
buf.push_str(ib.format(m));
if d < 10 {
buf.push('0');
}
buf.push_str(ib.format(d));
}
pub(super) fn push_bday(buf: &mut String, h: &HandleInput<'_>) {
let (d, m) = if h.birth_month > 0 && (h.tag >> 40) % 100 < 60 {
(u64::from(h.birth_day), u64::from(h.birth_month))
} else {
(h.tag % 28 + 1, (h.tag >> 5) % 12 + 1)
};
let mut ib = itoa::Buffer::new();
if d < 10 {
buf.push('0');
}
buf.push_str(ib.format(d));
if m < 10 {
buf.push('0');
}
buf.push_str(ib.format(m));
}
fn push_2digit(buf: &mut String, tag: u64) {
buf.push_str(itoa::Buffer::new().format(tag % 90 + 10));
}
fn push_3digit(buf: &mut String, tag: u64) {
buf.push_str(itoa::Buffer::new().format(tag % 900 + 100));
}
pub(super) fn push_zpad3(buf: &mut String, tag: u64) {
let v = tag % 999 + 1;
if v < 10 {
buf.push_str("00");
} else if v < 100 {
buf.push('0');
}
buf.push_str(itoa::Buffer::new().format(v));
}
pub(super) fn push_zpad4(buf: &mut String, tag: u64) {
let v = tag % 9999 + 1;
if v < 10 {
buf.push_str("000");
} else if v < 100 {
buf.push_str("00");
} else if v < 1000 {
buf.push('0');
}
buf.push_str(itoa::Buffer::new().format(v));
}
pub(super) fn push_letter_digit(buf: &mut String, tag: u64) {
buf.push((b'a' + (tag % 26) as u8) as char);
buf.push((b'0' + ((tag >> 5) % 10) as u8) as char);
}
pub(super) fn push_hex3(buf: &mut String, tag: u64) {
let h3 = tag & 0xFFF;
const HEX: &[u8; 16] = b"0123456789abcdef";
buf.push(HEX[((h3 >> 8) & 0xF) as usize] as char);
buf.push(HEX[((h3 >> 4) & 0xF) as usize] as char);
buf.push(HEX[(h3 & 0xF) as usize] as char);
}
fn push_letter_variation(buf: &mut String, tag: u64) {
let variant = (tag >> 20) % 10;
let letter_bits = tag >> 28; match variant {
0 => {
let c = (b'a' + (letter_bits % 26) as u8) as char;
buf.push(c);
buf.push(c);
}
1 => {
let c = if letter_bits & 1 == 0 { 'x' } else { 'z' };
buf.push(c);
buf.push((b'0' + (letter_bits % 10) as u8) as char);
}
2 => {
buf.push('_');
buf.push((b'a' + (letter_bits % 26) as u8) as char);
}
3..=4 => {
buf.push((b'a' + (letter_bits % 26) as u8) as char);
buf.push((b'a' + ((letter_bits >> 5) % 26) as u8) as char);
}
5 => {
const VOWELS: [u8; 5] = [b'a', b'e', b'i', b'o', b'u'];
const CONSONANTS: &[u8] = b"bcdfghjklmnpqrstvwxyz";
buf.push(VOWELS[(letter_bits % 5) as usize] as char);
buf.push(CONSONANTS[((letter_bits >> 3) % CONSONANTS.len() as u64) as usize] as char);
}
6..=7 => {
buf.push((b'a' + (letter_bits % 26) as u8) as char);
}
8 => {
let sfx = nickname::SUFFIXES[(letter_bits % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
}
_ => {
buf.push('_');
buf.push((b'a' + (letter_bits % 26) as u8) as char);
buf.push((b'a' + ((letter_bits >> 5) % 26) as u8) as char);
}
}
}
fn push_hex_tag(buf: &mut String, tag: u64, seps: SepRules) {
buf.push_str(platform_sep(seps));
let h3 = tag & 0xFFF; const HEX: &[u8; 16] = b"0123456789abcdef";
buf.push(HEX[((h3 >> 8) & 0xF) as usize] as char);
buf.push(HEX[((h3 >> 4) & 0xF) as usize] as char);
buf.push(HEX[(h3 & 0xF) as usize] as char);
}
fn push_adapted_suffix(buf: &mut String, h: &HandleInput<'_>) {
let v = (h.tag >> 16) % 100;
match v {
0..=7 => push_year2(buf, h), 8..=34 => buf.push_str(itoa::Buffer::new().format(h.tag % 9000 + 1)), 35..=49 => push_3digit(buf, h.tag), 50..=56 => push_bday(buf, h), 57..=59 => push_year4(buf, h), 60..=64 => push_zpad3(buf, h.tag), 65..=74 => push_letter_variation(buf, h.tag), 75..=82 => {
push_letter_variation(buf, h.tag); push_2digit(buf, h.tag >> 8);
}
83..=87 => push_2digit(buf, h.tag), 88..=93 => push_letter_digit(buf, h.tag), _ => push_hex_tag(buf, h.tag, h.seps), }
}
fn push_required_suffix(buf: &mut String, h: &HandleInput<'_>) {
let v = (h.tag >> 16) % 100;
match v {
0..=4 => push_year2(buf, h), 5..=39 => buf.push_str(itoa::Buffer::new().format(h.tag % 9000 + 1)), 40..=59 => push_3digit(buf, h.tag), 60..=69 => push_bday(buf, h), 70..=77 => push_zpad3(buf, h.tag), 78..=84 => {
push_letter_variation(buf, h.tag); push_2digit(buf, h.tag >> 8);
}
85..=89 => push_letter_digit(buf, h.tag), 90..=94 => push_2digit(buf, h.tag), _ => push_hex_tag(buf, h.tag, h.seps), }
}
pub(super) fn push_suffix(buf: &mut String, h: &HandleInput<'_>, rng: &mut Rng) {
let w = rng.urange(0, 99);
match w {
0..=24 => push_year2(buf, h), 25..=49 => push_3digit(buf, h.tag), 50..=64 => push_bday(buf, h), 65..=79 => push_2digit(buf, h.tag), _ => {
buf.push_str(itoa::Buffer::new().format(h.tag % 9000 + 1));
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum HandleArchetype {
NameOnly = 0,
NickSocial = 1,
FullNick = 2,
}
pub fn pick_archetype(rng: &mut Rng) -> HandleArchetype {
let w = rng.urange(0, 99);
match w {
0..=54 => HandleArchetype::NameOnly, 55..=84 => HandleArchetype::NickSocial, _ => HandleArchetype::FullNick, }
}
#[derive(Clone, Copy)]
pub struct HandlePolicy {
pub ns_reuse: u8,
pub ns_mutate: u8,
pub ns_fresh: u8,
pub fn_reuse: u8,
pub fn_mutate: u8,
pub fn_fresh: u8,
}
pub const EMAIL_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 5,
ns_mutate: 0,
ns_fresh: 0,
fn_reuse: 15,
fn_mutate: 5,
fn_fresh: 5,
};
pub const USERNAME_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 25,
ns_mutate: 15,
ns_fresh: 5,
fn_reuse: 50,
fn_mutate: 20,
fn_fresh: 10,
};
pub const SOCIAL_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 45,
ns_mutate: 25,
ns_fresh: 10,
fn_reuse: 55,
fn_mutate: 25,
fn_fresh: 10,
};
pub const FACEBOOK_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 15,
ns_mutate: 10,
ns_fresh: 5,
fn_reuse: 35,
fn_mutate: 15,
fn_fresh: 10,
};
pub const GITHUB_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 30,
ns_mutate: 20,
ns_fresh: 15,
fn_reuse: 45,
fn_mutate: 25,
fn_fresh: 15,
};
pub const HIGH_SOCIAL_POLICY: HandlePolicy = HandlePolicy {
ns_reuse: 45,
ns_mutate: 25,
ns_fresh: 10,
fn_reuse: 55,
fn_mutate: 25,
fn_fresh: 10,
};
pub struct HandleInput<'a> {
pub first: &'a str,
pub last: &'a str,
pub tag: u64,
pub seps: SepRules,
pub archetype: HandleArchetype,
pub policy: HandlePolicy,
pub nick: Option<&'a Nickname>,
pub birth_year: i64,
pub birth_month: u8,
pub birth_day: u8,
}
pub fn gen_handle(buf: &mut String, h: &HandleInput<'_>, rng: &mut Rng) {
if h.archetype == HandleArchetype::NameOnly {
gen_name_handle(buf, h, rng);
return;
}
let (p_reuse, p_mutate, p_fresh) = if h.archetype == HandleArchetype::NickSocial {
(h.policy.ns_reuse, h.policy.ns_mutate, h.policy.ns_fresh)
} else {
(h.policy.fn_reuse, h.policy.fn_mutate, h.policy.fn_fresh)
};
let roll = rng.urange(0, 99) as u8;
if let Some(n) = h.nick {
if roll < p_reuse {
nickname::mutate_nickname(buf, n, h, rng);
return;
}
if roll < p_reuse + p_mutate {
nickname::mutate_nickname(buf, n, h, rng);
return;
}
}
if roll < p_reuse + p_mutate + p_fresh {
nickname::gen_nickname(buf, h, rng);
} else {
if h.nick.is_some() && rng.urange(0, 99) < 25 {
gen_hybrid_handle(buf, h, rng);
} else {
gen_name_handle(buf, h, rng);
}
}
}
#[inline]
fn maybe_disambiguate(buf: &mut String, tag: u64) {
if (tag >> 44) & 1 == 1 {
buf.push((b'a' + ((tag >> 36) % 26) as u8) as char);
}
}
fn length_tier(tag: u64) -> u8 {
let v = (tag >> 48) % 100;
match v {
0..=14 => 0, 15..=36 => 1, 37..=59 => 2, 60..=84 => 3, _ => 4, }
}
fn gen_name_handle(buf: &mut String, h: &HandleInput<'_>, rng: &mut Rng) {
let first = h.first;
let last = h.last;
let f = first.as_bytes()[0] as char;
let l = last.as_bytes()[0] as char;
match length_tier(h.tag) {
0 => {
let w = rng.urange(0, 99);
match w {
0..=24 => {
buf.push(f);
buf.push_str(last);
maybe_disambiguate(buf, h.tag);
}
25..=44 => {
buf.push_str(first);
buf.push(l);
maybe_disambiguate(buf, h.tag);
}
45..=59 if first.len() >= 3 && last.len() >= 3 => {
let take_f = rng.urange(2, first.len().min(4));
let take_l = rng.urange(2, last.len().min(4));
buf.push_str(&first[..take_f]);
buf.push_str(&last[..take_l]);
maybe_disambiguate(buf, h.tag);
}
60..=74 => {
buf.push_str(first);
push_letter_digit(buf, h.tag);
}
75..=84 => {
buf.push_str(last);
push_letter_digit(buf, h.tag);
}
85..=94 => {
buf.push(f);
buf.push(l);
push_required_suffix(buf, h);
}
_ => {
buf.push_str(first);
push_year2(buf, h);
}
}
}
1 => {
let w = rng.urange(0, 99);
match w {
0..=39 => {
buf.push_str(first);
buf.push_str(last);
maybe_disambiguate(buf, h.tag);
}
40..=49 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
maybe_disambiguate(buf, h.tag);
}
50..=57 => {
buf.push_str(last);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
}
58..=64 => {
buf.push_str(first);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
}
65..=69 => {
buf.push_str(last);
buf.push_str(first);
maybe_disambiguate(buf, h.tag);
}
70..=74 => {
buf.push(f);
buf.push_str(last);
push_year2(buf, h);
}
75..=79 => {
buf.push_str(first);
push_year4(buf, h);
}
80..=84 => {
buf.push_str(first);
push_year2(buf, h);
}
85..=89 => {
let prefix =
nickname::PREFIXES[(h.tag % nickname::PREFIXES.len() as u64) as usize];
buf.push_str(prefix);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(first);
}
90..=94 => {
buf.push_str(first);
push_bday_mmdd(buf, h);
}
_ => {
buf.push_str(last);
push_year2(buf, h);
}
}
}
2 => {
let w = rng.urange(0, 99);
match w {
0..=24 => {
buf.push_str(first);
buf.push_str(last);
maybe_disambiguate(buf, h.tag);
}
25..=34 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
maybe_disambiguate(buf, h.tag);
}
35..=44 => {
buf.push_str(first);
buf.push_str(last);
push_year2(buf, h);
}
45..=54 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_year2(buf, h);
}
55..=62 => {
buf.push_str(first);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
push_year2(buf, h);
}
63..=69 => {
buf.push_str(first);
let sep = pick_sep(rng, h.seps);
buf.push_str(sep);
buf.push_str(last);
buf.push_str(sep);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
}
70..=74 => {
buf.push_str(first);
buf.push_str(last);
push_year4(buf, h);
}
75..=79 => {
buf.push_str(last);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(first);
push_year2(buf, h);
}
80..=84 => {
buf.push_str(first);
if last.len() >= 5 {
let take = rng.urange(2, last.len().min(4));
buf.push_str(&last[..take]);
} else {
buf.push_str(last);
}
push_adapted_suffix(buf, h);
}
85..=89 => {
buf.push_str(first);
push_bday(buf, h);
}
90..=94 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_bday_mmdd(buf, h);
}
_ => {
let prefix =
nickname::PREFIXES[(h.tag % nickname::PREFIXES.len() as u64) as usize];
buf.push_str(prefix);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(first);
push_year2(buf, h);
}
}
}
3 => {
let w = rng.urange(0, 99);
match w {
0..=19 => {
buf.push_str(first);
buf.push_str(last);
push_adapted_suffix(buf, h);
}
20..=34 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_adapted_suffix(buf, h);
}
35..=46 => {
buf.push_str(first);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
push_required_suffix(buf, h);
}
47..=56 => {
buf.push(f);
buf.push_str(last);
push_required_suffix(buf, h);
}
57..=64 => {
buf.push_str(last);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
push_adapted_suffix(buf, h);
}
65..=72 => {
buf.push_str(first);
push_required_suffix(buf, h);
}
73..=77 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push(l);
push_required_suffix(buf, h);
}
78..=82 => {
buf.push_str(last);
buf.push_str(itoa::Buffer::new().format(h.tag % 9000 + 1));
}
83..=87 => {
buf.push_str(first);
buf.push_str(itoa::Buffer::new().format(h.tag % 9000 + 1));
}
88..=92 => {
buf.push_str(first);
push_adapted_suffix(buf, h);
}
93..=97 => {
buf.push_str(last);
push_adapted_suffix(buf, h);
}
_ => {
buf.push(f);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_adapted_suffix(buf, h);
}
}
}
_ => {
let w = rng.urange(0, 99);
match w {
0..=19 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_required_suffix(buf, h);
}
20..=34 => {
buf.push_str(first);
buf.push_str(last);
push_required_suffix(buf, h);
}
35..=46 => {
let prefix =
nickname::PREFIXES[(h.tag % nickname::PREFIXES.len() as u64) as usize];
buf.push_str(prefix);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(first);
push_adapted_suffix(buf, h);
}
47..=56 => {
buf.push_str(first);
let sep = pick_sep(rng, h.seps);
buf.push_str(sep);
buf.push_str(last);
buf.push_str(sep);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
}
57..=66 => {
buf.push(f);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
buf.push_str(pick_sep(rng, h.seps));
push_required_suffix(buf, h);
}
67..=76 => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
push_required_suffix(buf, h);
}
77..=84 => {
buf.push_str(last);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(first);
push_adapted_suffix(buf, h);
}
85..=92 => {
buf.push_str(first);
let sfx =
nickname::SUFFIXES[(h.tag % nickname::SUFFIXES.len() as u64) as usize];
buf.push_str(sfx);
push_required_suffix(buf, h);
}
93..=96 => {
buf.push(f);
buf.push(l);
push_required_suffix(buf, h);
}
_ => {
buf.push_str(first);
buf.push_str(pick_sep(rng, h.seps));
buf.push_str(last);
push_year4(buf, h);
}
}
}
}
}
fn gen_hybrid_handle(buf: &mut String, h: &HandleInput<'_>, rng: &mut Rng) {
let first = h.first;
let last = h.last;
let Some(nick) = h.nick else {
gen_name_handle(buf, h, rng);
return;
};
let w = rng.urange(0, 99);
match w {
0..=19 => {
buf.push_str(first);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(nick.word2);
push_adapted_suffix(buf, h);
}
20..=34 => {
buf.push_str(nick.word1);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(first);
push_adapted_suffix(buf, h);
}
35..=49 => {
buf.push_str(nick.word1);
buf.push_str(first);
push_adapted_suffix(buf, h);
}
50..=64 => {
buf.push_str(first);
buf.push_str(nick.word1);
push_adapted_suffix(buf, h);
}
65..=79 => {
buf.push_str(nick.word2);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(last);
push_adapted_suffix(buf, h);
}
80..=89 => {
buf.push_str(first);
buf.push_str(nick.word2);
push_year2(buf, h);
}
_ => {
buf.push_str(nick.word1);
buf.push_str(maybe_sep(rng, h.seps));
buf.push_str(last);
push_adapted_suffix(buf, h);
}
}
}