//! Independent exact finite checker for the definition of S(N).
//!
//! This implementation deliberately shares no source with research generators.
//! It clears denominators by L = lcm(1,...,N), so equality is exact integer
//! equality and no floating-point arithmetic is used.

use num_bigint::BigUint;
use num_integer::Integer;
use num_traits::{One, Zero};
use std::collections::HashSet;
use std::env;
use std::fs;
use std::process::ExitCode;

const MAX_N: usize = 24;

fn exact_count(n: usize) -> usize {
    let mut lcm = BigUint::one();
    for d in 1..=n {
        lcm = lcm.lcm(&BigUint::from(d));
    }

    let mut sums = HashSet::with_capacity(1usize << n.min(20));
    sums.insert(BigUint::zero());
    for d in 1..=n {
        let weight = &lcm / BigUint::from(d);
        let translated: Vec<BigUint> = sums.iter().map(|x| x + &weight).collect();
        sums.extend(translated);
    }
    sums.len()
}

fn parse_candidate(path: &str) -> Result<(usize, usize), String> {
    let text = fs::read_to_string(path).map_err(|e| format!("cannot read {path}: {e}"))?;
    let tokens: Vec<&str> = text
        .lines()
        .map(|line| line.split('#').next().unwrap_or(""))
        .flat_map(str::split_whitespace)
        .collect();
    if tokens.len() != 2 {
        return Err("candidate must contain exactly two integers: N EXPECTED_S_N".into());
    }
    let n = tokens[0]
        .parse::<usize>()
        .map_err(|_| "N is not a nonnegative machine integer".to_string())?;
    let expected = tokens[1]
        .parse::<usize>()
        .map_err(|_| "EXPECTED_S_N is not a nonnegative machine integer".to_string())?;
    if n > MAX_N {
        return Err(format!(
            "N={n} exceeds the audited finite-check limit {MAX_N}; use Lean for general theorems"
        ));
    }
    Ok((n, expected))
}

fn self_test() -> Result<(), String> {
    // OEIS A072207, independently published finite values S(0),...,S(20).
    let expected = [
        1usize, 2, 4, 8, 16, 32, 52, 104, 208, 416, 832, 1664, 1856, 3712, 7424,
        9664, 19328, 38656, 59264, 118528, 126976,
    ];
    for (n, &want) in expected.iter().enumerate() {
        let got = exact_count(n);
        if got != want {
            return Err(format!("self-test failed at N={n}: computed {got}, expected {want}"));
        }
    }
    println!("PASS: exact finite self-test S(0)..S(20)");
    Ok(())
}

fn run() -> Result<(), String> {
    let args: Vec<String> = env::args().collect();
    match args.as_slice() {
        [_, flag] if flag == "--self-test" => self_test(),
        [_, path] => {
            let (n, expected) = parse_candidate(path)?;
            let got = exact_count(n);
            if got != expected {
                return Err(format!("FAIL: S({n})={got}, candidate claimed {expected}"));
            }
            println!("PASS: exact integer enumeration confirms S({n})={expected}");
            Ok(())
        }
        _ => Err("usage: check-reciprocal-subset-count (--self-test | CANDIDATE_FILE)".into()),
    }
}

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(msg) => {
            eprintln!("{msg}");
            ExitCode::FAILURE
        }
    }
}
