use crate::util::md5::*;
use crate::util::thread::*;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::Mutex;
struct Shared<'a> {
input: &'a str,
part_two: bool,
done: AtomicBool,
counter: AtomicI32,
mutex: Mutex<Exclusive>,
}
struct Exclusive {
threes: BTreeMap<i32, u32>,
fives: BTreeMap<i32, u32>,
found: BTreeSet<i32>,
}
pub fn parse(input: &str) -> &str {
input.trim()
}
pub fn part1(input: &str) -> i32 {
generate_pad(input, false)
}
pub fn part2(input: &str) -> i32 {
generate_pad(input, true)
}
fn generate_pad(input: &str, part_two: bool) -> i32 {
let exclusive =
Exclusive { threes: BTreeMap::new(), fives: BTreeMap::new(), found: BTreeSet::new() };
let shared = Shared {
input,
part_two,
done: AtomicBool::new(false),
counter: AtomicI32::new(0),
mutex: Mutex::new(exclusive),
};
spawn(|| worker(&shared));
let exclusive = shared.mutex.into_inner().unwrap();
*exclusive.found.iter().nth(63).unwrap()
}
#[cfg(not(feature = "simd"))]
fn worker(shared: &Shared<'_>) {
while !shared.done.load(Ordering::Relaxed) {
let n = shared.counter.fetch_add(1, Ordering::Relaxed);
let (mut buffer, size) = format_string(shared.input, n);
let mut result = hash(&mut buffer, size);
if shared.part_two {
for _ in 0..2016 {
buffer[0..8].copy_from_slice(&to_ascii(result.0));
buffer[8..16].copy_from_slice(&to_ascii(result.1));
buffer[16..24].copy_from_slice(&to_ascii(result.2));
buffer[24..32].copy_from_slice(&to_ascii(result.3));
result = hash(&mut buffer, 32);
}
}
check(shared, n, result);
}
}
#[cfg(feature = "simd")]
#[expect(clippy::needless_range_loop)]
fn worker(shared: &Shared<'_>) {
let mut result = ([0; 32], [0; 32], [0; 32], [0; 32]);
let mut buffers = [[0; 64]; 32];
while !shared.done.load(Ordering::Relaxed) {
let start = shared.counter.fetch_add(32, Ordering::Relaxed);
for i in 0..32 {
let (mut buffer, size) = format_string(shared.input, start + i as i32);
let (a, b, c, d) = hash(&mut buffer, size);
result.0[i] = a;
result.1[i] = b;
result.2[i] = c;
result.3[i] = d;
}
if shared.part_two {
for _ in 0..2016 {
for i in 0..32 {
buffers[i][0..8].copy_from_slice(&to_ascii(result.0[i]));
buffers[i][8..16].copy_from_slice(&to_ascii(result.1[i]));
buffers[i][16..24].copy_from_slice(&to_ascii(result.2[i]));
buffers[i][24..32].copy_from_slice(&to_ascii(result.3[i]));
}
result = simd::hash::<32>(&mut buffers, 32);
}
}
for i in 0..32 {
let hash = (result.0[i], result.1[i], result.2[i], result.3[i]);
check(shared, start + i as i32, hash);
}
}
}
fn check(shared: &Shared<'_>, n: i32, hash: (u32, u32, u32, u32)) {
let (a, b, c, d) = hash;
let mut prev = u32::MAX;
let mut same = 1;
let mut three = 0;
let mut five = 0;
for mut word in [d, c, b, a] {
for _ in 0..8 {
let next = word & 0xf;
if next == prev {
same += 1;
} else {
same = 1;
}
if same == 3 {
three = 1 << next;
}
if same == 5 {
five |= 1 << next;
}
word >>= 4;
prev = next;
}
}
if three != 0 || five != 0 {
let mut exclusive = shared.mutex.lock().unwrap();
let mut candidates = Vec::new();
if three != 0 {
exclusive.threes.insert(n, three);
for (_, mask) in exclusive.fives.range(n + 1..n + 1001) {
if three & mask != 0 {
candidates.push(n);
}
}
}
if five != 0 {
exclusive.fives.insert(n, five);
for (&index, &mask) in exclusive.threes.range(n - 1000..n) {
if five & mask != 0 {
candidates.push(index);
}
}
}
exclusive.found.extend(candidates);
if exclusive.found.len() >= 64 {
shared.done.store(true, Ordering::Relaxed);
}
}
}
fn format_string(prefix: &str, n: i32) -> ([u8; 64], usize) {
let string = format!("{prefix}{n}");
let size = string.len();
let mut buffer = [0; 64];
buffer[0..size].copy_from_slice(string.as_bytes());
(buffer, size)
}
fn to_ascii(n: u32) -> [u8; 8] {
let mut n = n as u64;
n = ((n << 16) & 0x0000ffff00000000) | (n & 0x000000000000ffff);
n = ((n << 8) & 0x00ff000000ff0000) | (n & 0x000000ff000000ff);
n = ((n << 4) & 0x0f000f000f000f00) | (n & 0x000f000f000f000f);
let mask = ((n + 0x0606060606060606) >> 4) & 0x0101010101010101;
n = n + 0x3030303030303030 + 0x27 * mask;
n.to_be_bytes()
}