//
// Syd: rock-solid application kernel
// src/utils/syd-sha.rs: Calculate MD5 or SHA1,3-{256,384,512} checksum of the given file or standard input.
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::File,
    io::{BufReader, Write},
    process::ExitCode,
};

use data_encoding::HEXLOWER;
use nix::errno::Errno;
use syd::hash::HashAlgorithm;

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    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 = HashAlgorithm::Sha512;
    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('c') => opt_func = HashAlgorithm::Crc64,
            Short('C') => opt_func = HashAlgorithm::Crc32,
            Short('1') => opt_func = HashAlgorithm::Sha1,
            Short('2') => opt_func = HashAlgorithm::Sha256,
            Short('3') => opt_func = HashAlgorithm::Sha384,
            Short('5') => opt_func = HashAlgorithm::Sha512,
            Short('m') => opt_func = HashAlgorithm::Md5,
            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()),
        }
    }

    match opt_path.as_deref() {
        None | Some("-") => {
            if opt_bino {
                // Binary output for standard input
                std::io::stdout()
                    .write_all(&syd::hash::hash(std::io::stdin().lock(), opt_func)?)?;
            } else {
                // Hexadecimal output for standard input
                println!(
                    "{}",
                    HEXLOWER.encode(&syd::hash::hash(std::io::stdin().lock(), opt_func)?)
                );
            }
        }
        Some(path) => {
            #[expect(clippy::disallowed_methods)]
            let file = BufReader::new(File::open(path)?);
            if opt_bino {
                // Binary output for file input
                std::io::stdout().write_all(&syd::hash::hash(file, opt_func)?)?;
            } else {
                // Hexadecimal output for file input
                println!(
                    "{} {path}",
                    HEXLOWER.encode(&syd::hash::hash(file, opt_func)?)
                );
            }
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-sha [-bcChm1235] <file|->");
    println!("Given a file, print the SHA3-512 checksum of the file.");
    println!("Given no positional arguments, calculate the SHA3-512 checksum of standard input.");
    println!("Use -c to calculate CRC64 checksum instead of SHA3-512 (\x1b[91minsecure\x1b[0m).");
    println!("Use -C to calculate CRC32 checksum instead of SHA3-512 (\x1b[91minsecure\x1b[0m).");
    println!("Use -m to calculate MD5 instead of SHA3-512 (\x1b[91minsecure\x1b[0m, \x1b[96mPortage\x1b[0m/\x1b[95mPaludis\x1b[0m vdb compat).");
    println!("Use -1 to calculate SHA1 instead of SHA3-512 (\x1b[91minsecure\x1b[0m).");
    println!("Use -2 to calculate SHA3-256 instead of SHA3-512.");
    println!("Use -3 to calculate SHA3-384 instead of SHA3-512.");
    println!("Use -b to print binary output rather than hex-encoded string.");
}
