//! Watch for a (re)launched FIFA17.exe and auto-apply the ProtoSSL cert patches
//! the moment its unpacked code is mapped, plus the CardsDLL FUT store patches.
//! Idempotent; keeps watching across relaunches.
//!
//! Port of `fifa17-recon/tools/autopatch.py`, tick for tick: cert gates once per
//! pid, store patches re-enforced every second (the game rewrites those sites),
//! then the resolver-guard capability reported once per pid. The passes
//! themselves live in `openfut_autopatch::patch`; this is the loop and the CLI.

use std::collections::HashSet;
use std::process::ExitCode;
use std::thread::sleep;
use std::time::Duration;

use openfut_autopatch::patch::{cert_pass, enforce_store_patches};
use openfut_autopatch::procmem::{self, ProcMem};
use openfut_autopatch::{parse_launcher_pid, Logger};

/// One tick per second, as in the Python.
const TICK: Duration = Duration::from_secs(1);

/// Per-pid bookkeeping so each of these lines is logged exactly once per client
/// process (the Python's three module-level sets).
#[derive(Default)]
struct Seen {
    patched: HashSet<u32>,
    store_patched: HashSet<u32>,
    guard_reported: HashSet<u32>,
}

fn main() -> ExitCode {
    let launcher_pid = match parse_launcher_pid(std::env::args().skip(1)) {
        Ok(pid) => pid,
        Err(()) => {
            eprintln!("invalid --launcher-pid");
            return ExitCode::FAILURE;
        }
    };

    let logger = match Logger::from_env() {
        Ok(logger) => logger,
        Err(e) => {
            eprintln!("cannot resolve the autopatch log path: {e}");
            return ExitCode::FAILURE;
        }
    };

    logger.log("=== AUTOPATCH watching for FIFA17.exe ===");

    let mut seen = Seen::default();
    loop {
        // `if launcher_pid and not os.path.exists(...)`: pid 0 is falsy in the
        // Python, so `--launcher-pid 0` parses but is never watched.
        if let Some(pid) = launcher_pid {
            if pid != 0 && !procmem::pid_alive(pid) {
                logger.log(&format!("launcher pid {pid} exited; stopping autopatch"));
                break;
            }
        }

        for pid in procmem::find_pids() {
            let mem = ProcMem::new(pid);

            if !seen.patched.contains(&pid)
                && !cert_pass(&mem, &mut |line| logger.log(line), &mut seen.patched)
            {
                // Code not mapped yet: nothing else to do for this pid this tick.
                continue;
            }

            // Store patches are enforced on EVERY tick, not once: the game
            // rewrites these sites, so a single pass at startup does not hold.
            let Some(cbase) = procmem::cardsdll_base(pid) else {
                continue;
            };
            match enforce_store_patches(
                &mem,
                cbase,
                &mut |line| logger.log(line),
                &mut seen.guard_reported,
            ) {
                Ok(()) => {
                    if seen.store_patched.insert(pid) {
                        logger.log(&format!(
                            "pid {pid}: PATCHED store gates in CardsDLL @ {cbase:#x}"
                        ));
                    }
                }
                Err(e) => logger.log(&format!("pid {pid}: store patch write failed: {e}")),
            }
        }

        sleep(TICK);
    }

    ExitCode::SUCCESS
}
