//
// Syd: rock-solid unikernel
// src/syd-hex.rs: Hexadecimal encode/decode standard input.
//
// Copyright (c) 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0-or-later

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

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

// 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 encode = true;

    while let Some(opt) = opts.next_opt()? {
        match opt {
            Opt::Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Opt::Short('d') => {
                encode = false;
            }
            Opt::Short('e') => {
                encode = true;
            }
            Opt::Short(opt) => {
                eprintln!("Unknown option: -{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
            Opt::Long(opt) => {
                eprintln!("Unknown option: --{opt}!");
                help();
                return Ok(ExitCode::FAILURE);
            }
        }
    }

    // Lock stdin for efficient reading
    #[allow(clippy::disallowed_methods)]
    let mut input: Box<dyn BufRead> = match opts.positionals().next() {
        None | Some("-") => Box::new(std::io::stdin().lock()),
        Some(path) => Box::new(BufReader::new(File::open(path)?)),
    };

    // Process the input in chunks to avoid loading it all into memory at once
    let mut buffer = [0; 64 * 1024]; // 64KB buffer size.
    while let Ok(count) = input.read(&mut buffer[..]) {
        if count == 0 {
            break; // End of input
        }
        if encode {
            // Hex-encode and write the chunk
            let encoded = buffer[..count].to_lower_hex_string();
            print!("{encoded}");
        } else {
            // Hex-decode and write the chunk
            #[allow(clippy::disallowed_methods)]
            match Vec::from_hex(std::str::from_utf8(&buffer[..count])?) {
                Ok(decoded) => std::io::stdout().write_all(&decoded)?,
                Err(error) => {
                    eprintln!("Error decoding hex: {error}");
                    return Ok(ExitCode::FAILURE);
                }
            }
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-hex [-hde] <file|->");
    println!("Given a file, hex-encode and print.");
    println!("Given no positional arguments, hex-encode standard input.");
    println!("Use -d to hex-decode rather than hex-encode.");
}
