//
// Syd: rock-solid application kernel
// src/utils/syd-sum.rs: Calculate checksum of the given file or standard input using AF_ALG.
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{io::Write, process::ExitCode};

use data_encoding::HEXLOWER;
use nix::errno::Errno;
use syd::{fd::open_static_proc, hash::hash_list};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    not(target_arch = "riscv64"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

// Set global allocator to tcmalloc if profiling is enabled.
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;

syd::main! {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    let mut opt_func: Option<String> = None;
    let mut opt_bino = false; // Binary output?
    let mut opt_path = None;

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('b') => opt_bino = true,
            Short('x') => opt_bino = false,
            Short('a') => opt_func = Some(parser.value()?.to_str().ok_or(Errno::EINVAL)?.to_string()),
            Value(path) if opt_path.is_none() => {
                opt_path = Some(path.to_str().ok_or(Errno::EINVAL).map(String::from)?)
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    let opt_func = match opt_func {
        Some(f) if f == "list" => {
            open_static_proc()?;
            for name in hash_list()? {
                println!("{name}");
            }
            return Ok(ExitCode::SUCCESS);
        }
        Some(f) => f,
        None => {
            eprintln!("Error: -a <algorithm> is required.");
            eprintln!("Run syd-sum -h for help.");
            return Ok(ExitCode::FAILURE);
        }
    };

    match opt_path.as_deref() {
        None | Some("-") => {
            // stdin is an fd, hash() uses splice/sendfile internally.
            let digest = syd::hash::hash(&opt_func, std::io::stdin())?;
            if opt_bino {
                std::io::stdout().write_all(&digest)?;
            } else {
                println!("{}", HEXLOWER.encode(&digest));
            }
        }
        Some(path) => {
            #[expect(clippy::disallowed_methods)]
            #[expect(clippy::disallowed_types)]
            let file = std::fs::File::open(path)?;
            let digest = syd::hash::hash(&opt_func, &file)?;
            if opt_bino {
                std::io::stdout().write_all(&digest)?;
            } else {
                println!("{} {path}", HEXLOWER.encode(&digest));
            }
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-sum -a <algorithm> [-bhx] <file|->");
    println!("Given a file, print the checksum of the file.");
    println!("Given no positional arguments, calculate the checksum of standard input.");
    println!();
    println!("  -a <alg>  Hash algorithm (required).");
    println!("            Any algorithm listed in proc_crypto(5) with type ahash or shash.");
    println!("            Use `-a list' to list available algorithms and their digest sizes.");
    println!("            Examples: sha256, sha512, sha3-512, blake2b-256, md5, crc32c");
    println!("  -b        Print binary output rather than hex-encoded string.");
    println!("  -x        Print hexadecimal output (default).");
    println!("  -h        Display this help.");
}
