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

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

use getargs::{Opt, Options};
use hex::DisplayHex;
use syd::{err::SydResult, hash::HashAlgorithm};

// Set global allocator to mimalloc.
#[global_allocator]
static GLOBAL: mimalloc2_rust::GlobalMiMalloc = mimalloc2_rust::GlobalMiMalloc;

fn main() -> SydResult<ExitCode> {
    syd::set_sigpipe_dfl()?;

    // Configure mimalloc.
    syd::configure_mimalloc();

    let args = env::args().skip(1).collect::<Vec<_>>();
    let args: &'static Vec<String> = Box::leak(Box::new(args));
    let mut opts = Options::new(args.iter().map(String::as_str));
    let mut func = HashAlgorithm::Sha512;
    let mut bino = false; // Binary output?

    while let Some(opt) = opts.next_opt()? {
        match opt {
            Opt::Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Opt::Short('b') => {
                bino = true;
            }
            Opt::Short('x') => {
                bino = false;
            }
            Opt::Short('1') => {
                func = HashAlgorithm::Sha1;
            }
            Opt::Short('2') => {
                func = HashAlgorithm::Sha256;
            }
            Opt::Short('3') => {
                func = HashAlgorithm::Sha384;
            }
            Opt::Short('5') => {
                func = HashAlgorithm::Sha512;
            }
            Opt::Short('m') => {
                func = HashAlgorithm::Md5;
            }
            Opt::Short(opt) => {
                eprintln!("Unknown option: -{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
            Opt::Long(opt) => {
                eprintln!("Unknown option: --{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
        }
    }

    match opts.positionals().next() {
        None | Some("-") => {
            if bino {
                // Binary output for standard input
                std::io::stdout().write_all(&syd::hash::hash(std::io::stdin().lock(), func)?)?;
            } else {
                // Hexadecimal output for standard input
                println!(
                    "{:x}",
                    syd::hash::hash(std::io::stdin().lock(), func)?.as_hex()
                );
            }
        }
        Some(path) => {
            let file = BufReader::new(File::open(path)?);
            if bino {
                // Binary output for file input
                std::io::stdout().write_all(&syd::hash::hash(file, func)?)?;
            } else {
                // Hexadecimal output for file input
                println!("{:x} {path}", syd::hash::hash(file, func)?.as_hex());
            }
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-sha [-bhm1235] <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 -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.");
}
